Compare commits

...

60 Commits

Author SHA1 Message Date
Alberto Iannaccone
96b5edf427 fix IDE updater commands (#872)
* fix IDE updater commands

* reinitialise autoupdate when preferences change

* fix typo + add i18n strings
2022-03-01 16:34:43 +00:00
Alberto Iannaccone
a5a6a0b611 Go to download page when automatic update fails (#871)
* add preference to set a custom update url

* go to download page when update fails

* fix i18n check
2022-03-01 08:24:29 +00:00
Alberto Iannaccone
2a27a14a68 put Linux build files inside a folder before zipping (#870)
* add preference to set a custom update url

* put linux build inside a folder before zipping
2022-03-01 08:23:56 +00:00
Alberto Iannaccone
f2d492b5dc show represented file on MacOS (#868) 2022-03-01 08:17:05 +00:00
Alberto Iannaccone
5979e5aad2 add preference to set a custom update url (#865) 2022-02-28 14:04:54 +00:00
Alberto Iannaccone
baa9b5f7ab Automatically check for updates only once (#863)
* Automatically check for updates only once

* set windows version to 2019 on CI
2022-02-24 14:04:36 +00:00
Francesco Stasi
481497e384 Disable autodownload of updates on startup (#860) 2022-02-24 10:43:10 +00:00
Mark Sujew
0207778373 Enable opening the IDE from finder/explorer (#835)
* Enable opening the IDE from finder/explorer

* Make opening windows from args a bit more lenient
2022-02-23 16:39:27 +01:00
Francesco Stasi
d79f32efd7 bump vscode-arduino-tools (#859) 2022-02-23 16:07:40 +01:00
Francesco Stasi
3ab03dd62f Avoid duplicated yaml entries (#858) 2022-02-23 15:55:04 +01:00
Mark Sujew
bc3cb0c230 Save preferences in sequence (#856) 2022-02-23 11:08:19 +01:00
Alberto Iannaccone
473cb11053 Remove target section from electron-builder config (#853)
* remove target section from electron-builder config

* do not modify zip structure before moving to artifcats folder
2022-02-22 11:14:11 +00:00
Alberto Iannaccone
0a87fd00f3 IDE updater bugfixes (#846)
* IDE updater assorted bugfix

- add linux AppImage target
- fix hardcoded if condition that causes to always show the update dialog
- fix redundant test build version
- recalculate sha512 after notarization on macOS

* boost notarization speed

* recalculate artifacts hash
2022-02-21 21:40:46 +00:00
Alberto Iannaccone
9b1f15def8 upgrade IDE to rc4 (#841) 2022-02-17 10:39:39 +00:00
Alberto Iannaccone
77b430675d fix generation of updater channel files in CI (#840) 2022-02-17 09:29:56 +00:00
Alberto Iannaccone
f660058c75 Check for IDE update at startup (#797)
* Remove check for updates on startup setting

* Remove useless exported function

* Update template-package.json used to package IDE

* Add function to get channel file during packaging step

* Add updates check

* move ide updater on backend

* configure updater options

* add auto update preferences

* TMP check updates on start and download

* index on check-update-startup: fcb8f6e TMP check updates on start and download

* set version to skip on local storage

* add IDE setting to toggle update check on start-up

* comment out check for updates on startup and auto update settings

* Update Theia to 1.22.1

* updated CI

* download changelog and show it in IDE updater dialog

* remove useless file

* remove useless code

* add i18n to updater dialog

* fix i18n

* refactor UpdateInfo typing

* add macos zip to artifacts

* Simply use `--ignore-engines`

* Use correct --ignore-engines

* Fix semver#valid call

* Use C++17

* updated documentation

* add update channel preference

* update updater url

* updated documentation

* Fix the C++ version

* Build flag for cpp

* add disclaimer with correct node version

* Update `electron-builder`

* Fix `Electron.Menu` issue

* Skip electron rebuild

* Rebuild native dependencies beforehand

* Use resolutions section

* Update template-package.json as well

* move ide-updater to electron application

* refactor ide-updater service

* update yarn.lock

* update i18n

* Revert "Add gRPC user agent (#834)"

This reverts commit 5ab3a747a6.

* fix ide download url

* update latest file in CI

* fix i18n check

Co-authored-by: Silvano Cerza <silvanocerza@gmail.com>
Co-authored-by: Francesco Stasi <f.stasi@me.com>
Co-authored-by: Mark Sujew <msujew@yahoo.de>
2022-02-15 17:01:19 +00:00
Silvano Cerza
9ecff86bbe Fix version retrieval in node process (#837) 2022-02-15 16:52:13 +01:00
Silvano Cerza
5ab3a747a6 Add gRPC user agent (#834) 2022-02-14 12:39:48 +01:00
Silvano Cerza
877c1a1559 Fix board options not shown for manually installed platforms (#826) 2022-02-14 10:12:18 +01:00
Alberto Iannaccone
2f9bf86d75 update arduino-cli to 0.21.0 (#820) 2022-02-11 14:50:56 +00:00
Mark Sujew
112153fb96 Update Theia to 1.22.1 (#791) 2022-02-11 15:25:35 +01:00
Mark Sujew
69ac1f4779 Open all closed workspaces on startup (#780) 2022-02-11 10:57:44 +01:00
Ben
a20899ff43 When a new port is connected and checking to connect to it because previously connected board matches the name / fqbn, also check that the protocol matches. (#792) 2022-02-01 14:35:21 +01:00
Silvano Cerza
ef2be1c086 Small code fix 2022-01-31 17:29:56 +01:00
Silvano Cerza
af33dce0f6 Solve ports conflicts with same address and different protocol 2022-01-31 17:29:56 +01:00
Silvano Cerza
b3b22795f8 Fix compose-changelog.js overwriting itself when called with no arguments 2022-01-27 18:42:34 +01:00
Silvano Cerza
8a0454db51 Fix compose full changelog workflow 2022-01-27 18:10:30 +01:00
Silvano Cerza
f1a5d87ab2 Full changelog is now created from separate workflow 2022-01-27 16:56:03 +01:00
Silvano Cerza
cf0a2161af Add step to generate full changelog on release 2022-01-27 16:56:03 +01:00
Silvano Cerza
dcebd863cc Changelog file is now written to file 2022-01-27 16:56:03 +01:00
Silvano Cerza
e8477b14f3 Fix substitutions issues with compose-changelog script 2022-01-27 16:56:03 +01:00
Alberto Iannaccone
0230071b5f add script to compose full changelog 2022-01-27 16:56:03 +01:00
Alberto Iannaccone
1d88263c85 update ls to 0.6.0and clangd to 13.0.0 (#738) 2022-01-24 16:21:19 +00:00
Francesco Stasi
a71ac4c44d Update BUILDING.md 2022-01-21 10:47:12 +01:00
per1234
66fc27e58c Remove stray brace from compilation error output
An extra brace was inadvertently introduced into a template literal used to format output text in the event of an error
during compilation. This caused the text to end in a pointless `}`

For example:

```
Compilation error: exit status 1}
```

After this change, the output text is as expected:

```
Compilation error: exit status 1
```
2022-01-17 02:46:40 -08:00
per1234
bc365f4a8d Correct minor typos in UI text and documentation 2022-01-17 02:16:36 -08:00
per1234
a5891f9884 Update development docs for current repository
The original location of the project repository was `bcmi-labs/arduino-editor` and some of the internal development
documentation for the project contains references to the repository.

This documentation was not updated at the time the repository was moved to the current home in `arduino/arduino-ide`.
2022-01-17 02:16:08 -08:00
Francesco Stasi
fcdf16a937 Update BUILDING.md 2022-01-14 12:12:17 +01:00
github-actions[bot]
e0b6dbbf2a Updated translation files (#723)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-01-13 17:03:42 +01:00
Francesco Stasi
9529e78647 Improve build instructions (#706) 2022-01-13 17:02:45 +01:00
Francesco Stasi
51da3c0668 Version 2.0.0-rc3 2021-12-22 16:44:17 +01:00
Francesco Stasi
c00d3d33dd Merge remote-tracking branch 'origin/i18n/translations-update' 2021-12-22 16:43:22 +01:00
Francesco Stasi
cfa9b8aea6 bump serial plotter to 0.0.17 2021-12-22 11:32:44 +01:00
per1234
6106e9ff1a Use major version ref of carlosperate/download-file-action
The `carlosperate/download-file-action` action is used in the GitHub Actions workflows as a convenient way to download
external resources.

A major version ref has been added to that repository. It will always point to the latest release of the "1" major
version series. This means it is no longer necessary to do a full pin of the action version in use as before.

Use of the major version ref will cause the workflow to use a stable version of the action, while also benefiting from
ongoing development to the action up until such time as a new major release of an action is made. At that time we would
need to evaluate whether any changes to the workflow are required by the breaking change that triggered the major
release before manually updating the major ref (e.g., uses: `carlosperate/download-file-action@v2`). I think this
approach strikes the right balance between stability and maintainability for these workflows.
2021-12-21 01:19:29 -08:00
Francesco Stasi
b1d9f65a0d bump serial plotter version (#698) 2021-12-20 15:49:16 +01:00
Francesco Stasi
f4008100e1 Correctly transform uint8array to string (#696)
* correctly transform uint8array to string

* export function
2021-12-20 14:56:38 +01:00
Francesco Stasi
11a6959a24 serial monitor lines not to wrap (#697) 2021-12-20 14:56:26 +01:00
github-actions[bot]
3c6e11832b Updated translation files 2021-12-20 02:19:55 +00:00
Alberto Iannaccone
c064673ce1 Close serial port connection before flashing firmware (#688) 2021-12-15 09:31:12 +00:00
Silvano Cerza
cc5764e536 Update README.md
Co-authored-by: per1234 <accounts@perglass.com>
2021-12-14 17:47:31 +01:00
Silvano Cerza
9131f2d09e Update README.md with translations project link 2021-12-14 17:47:31 +01:00
Alberto Iannaccone
0b6fc0b973 Version 2.0.0-rc2 2021-12-13 11:04:35 +01:00
Alberto Iannaccone
c91fe2d775 bump arduino-language-server to 0.5.0 (#679) 2021-12-13 09:55:50 +00:00
github-actions[bot]
bbded57ae4 Updated translation files (#638)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2021-12-13 09:20:03 +01:00
Francesco Stasi
a8ae0bb4e0 workaround: stop discoveries before install/uninstall boards/libs (#674) 2021-12-10 17:03:24 +01:00
Francesco Stasi
49d12d99ff IDE to run CLI with auto assigned port (#673)
* get daemon port from CLI stdout

* config-service to use CLI daemon port

* updating LS

* fixed tests

* fix upload blocked when selectedBoard.port is undefined

* bump arduino-cli to 0.20.2

Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
2021-12-09 15:08:26 +01:00
Francesco Stasi
767b09d2f1 Fix upload and serial (#661)
* get serial connection status from BE

* handle serial connect in the BE

* allow breakpoints on vscode (windows)

* Timeout on config change to prevent serial busy

* serial-service tests
2021-12-07 17:38:43 +01:00
Alberto Iannaccone
88397931c5 Automatically install 'Arduino_BuiltIn' library at first startup (#663) 2021-12-06 15:56:17 +00:00
Silvano Cerza
5ddab1ded7 Remove gRPC error code from error notifications 2021-12-06 09:58:17 +01:00
Francesco Stasi
f0d9894a16 Fix notification icons (#642) 2021-11-30 17:24:29 +01:00
139 changed files with 13794 additions and 3526 deletions

View File

@@ -15,15 +15,15 @@ on:
env:
JOB_TRANSFER_ARTIFACT: build-artifacts
CHANGELOG_ARTIFACTS: changelog
jobs:
build:
if: github.repository == 'arduino/arduino-ide'
strategy:
matrix:
config:
- os: windows-latest
- os: windows-2019
- os: ubuntu-18.04 # https://github.com/arduino/arduino-ide/issues/259
- os: macos-latest
runs-on: ${{ matrix.config.os }}
@@ -33,16 +33,16 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Install Node.js 12.x
- name: Install Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: '12.14.1'
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- name: Install Python 2.7
- name: Install Python 3.x
uses: actions/setup-python@v2
with:
python-version: '2.7'
python-version: '3.x'
- name: Package
shell: bash
@@ -50,35 +50,36 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AC_USERNAME: ${{ secrets.AC_USERNAME }}
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
AC_TEAM_ID: ${{ secrets.AC_TEAM_ID }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
IS_NIGHTLY: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }}
IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') }}
IS_FORK: ${{ github.event.pull_request.head.repo.fork == true }}
run: |
# See: https://www.electron.build/code-signing
if [ $IS_FORK = true ]; then
echo "Skipping the app signing: building from a fork."
else
if [ "${{ runner.OS }}" = "macOS" ]; then
export CSC_LINK="${{ runner.temp }}/signing_certificate.p12"
# APPLE_SIGNING_CERTIFICATE_P12 secret was produced by following the procedure from:
# https://www.kencochrane.com/2020/08/01/build-and-sign-golang-binaries-for-macos-with-github-actions/#exporting-the-developer-certificate
echo "${{ secrets.APPLE_SIGNING_CERTIFICATE_P12 }}" | base64 --decode > "$CSC_LINK"
# See: https://www.electron.build/code-signing
if [ $IS_FORK = true ]; then
echo "Skipping the app signing: building from a fork."
else
if [ "${{ runner.OS }}" = "macOS" ]; then
export CSC_LINK="${{ runner.temp }}/signing_certificate.p12"
# APPLE_SIGNING_CERTIFICATE_P12 secret was produced by following the procedure from:
# https://www.kencochrane.com/2020/08/01/build-and-sign-golang-binaries-for-macos-with-github-actions/#exporting-the-developer-certificate
echo "${{ secrets.APPLE_SIGNING_CERTIFICATE_P12 }}" | base64 --decode > "$CSC_LINK"
export CSC_KEY_PASSWORD="${{ secrets.KEYCHAIN_PASSWORD }}"
export CSC_KEY_PASSWORD="${{ secrets.KEYCHAIN_PASSWORD }}"
elif [ "${{ runner.OS }}" = "Windows" ]; then
export CSC_LINK="${{ runner.temp }}/signing_certificate.pfx"
npm config set msvs_version 2017 --global
echo "${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PFX }}" | base64 --decode > "$CSC_LINK"
elif [ "${{ runner.OS }}" = "Windows" ]; then
export CSC_LINK="${{ runner.temp }}/signing_certificate.pfx"
npm config set msvs_version 2017 --global
echo "${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PFX }}" | base64 --decode > "$CSC_LINK"
export CSC_KEY_PASSWORD="${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }}"
fi
export CSC_KEY_PASSWORD="${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }}"
fi
fi
yarn --cwd ./electron/packager/
yarn --cwd ./electron/packager/ package
yarn --cwd ./electron/packager/
yarn --cwd ./electron/packager/ package
- name: Upload [GitHub Actions]
uses: actions/upload-artifact@v2
@@ -95,15 +96,19 @@ jobs:
strategy:
matrix:
artifact:
- path: "*Linux_64bit.zip"
name: Linux_X86-64
- path: "*macOS_64bit.dmg"
name: macOS
- path: "*Windows_64bit.exe"
- path: '*Linux_64bit.zip'
name: Linux_X86-64_zip
- path: '*Linux_64bit.AppImage'
name: Linux_X86-64_app_image
- path: '*macOS_64bit.dmg'
name: macOS_dmg
- path: '*macOS_64bit.zip'
name: macOS_zip
- path: '*Windows_64bit.exe'
name: Windows_X86-64_interactive_installer
- path: "*Windows_64bit.msi"
- path: '*Windows_64bit.msi'
name: Windows_X86-64_MSI
- path: "*Windows_64bit.zip"
- path: '*Windows_64bit.zip'
name: Windows_X86-64_zip
steps:
@@ -112,7 +117,7 @@ jobs:
with:
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
path: ${{ env.JOB_TRANSFER_ARTIFACT }}
- name: Upload tester build artifact
uses: actions/upload-artifact@v2
with:
@@ -135,24 +140,24 @@ jobs:
env:
IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') }}
run: |
export LATEST_TAG=$(git describe --abbrev=0)
export GIT_LOG=$(git log --pretty=" - %s [%h]" $LATEST_TAG..HEAD | sed 's/ *$//g')
if [ "$IS_RELEASE" = true ]; then
export BODY=$(echo -e "$GIT_LOG")
else
export LATEST_TAG_WITH_LINK=$(echo "[$LATEST_TAG](https://github.com/arduino/arduino-ide/releases/tag/$LATEST_TAG)")
if [ -z "$GIT_LOG" ]; then
export BODY="There were no changes since version $LATEST_TAG_WITH_LINK."
else
export BODY=$(echo -e "Changes since version $LATEST_TAG_WITH_LINK:\n$GIT_LOG")
fi
export LATEST_TAG=$(git describe --abbrev=0)
export GIT_LOG=$(git log --pretty=" - %s [%h]" $LATEST_TAG..HEAD | sed 's/ *$//g')
if [ "$IS_RELEASE" = true ]; then
export BODY=$(echo -e "$GIT_LOG")
else
export LATEST_TAG_WITH_LINK=$(echo "[$LATEST_TAG](https://github.com/arduino/arduino-ide/releases/tag/$LATEST_TAG)")
if [ -z "$GIT_LOG" ]; then
export BODY="There were no changes since version $LATEST_TAG_WITH_LINK."
else
export BODY=$(echo -e "Changes since version $LATEST_TAG_WITH_LINK:\n$GIT_LOG")
fi
echo -e "$BODY"
OUTPUT_SAFE_BODY="${BODY//'%'/'%25'}"
OUTPUT_SAFE_BODY="${OUTPUT_SAFE_BODY//$'\n'/'%0A'}"
OUTPUT_SAFE_BODY="${OUTPUT_SAFE_BODY//$'\r'/'%0D'}"
echo "::set-output name=BODY::$OUTPUT_SAFE_BODY"
echo "$BODY" > CHANGELOG.txt
fi
echo -e "$BODY"
OUTPUT_SAFE_BODY="${BODY//'%'/'%25'}"
OUTPUT_SAFE_BODY="${OUTPUT_SAFE_BODY//$'\n'/'%0A'}"
OUTPUT_SAFE_BODY="${OUTPUT_SAFE_BODY//$'\r'/'%0D'}"
echo "::set-output name=BODY::$OUTPUT_SAFE_BODY"
echo "$BODY" > CHANGELOG.txt
- name: Upload Changelog [GitHub Actions]
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
@@ -175,9 +180,9 @@ jobs:
- name: Publish Nightly [S3]
uses: docker://plugins/s3
env:
PLUGIN_SOURCE: "${{ env.JOB_TRANSFER_ARTIFACT }}/*"
PLUGIN_STRIP_PREFIX: "${{ env.JOB_TRANSFER_ARTIFACT }}/"
PLUGIN_TARGET: "/arduino-ide/nightly"
PLUGIN_SOURCE: '${{ env.JOB_TRANSFER_ARTIFACT }}/*'
PLUGIN_STRIP_PREFIX: '${{ env.JOB_TRANSFER_ARTIFACT }}/'
PLUGIN_TARGET: '/arduino-ide/nightly'
PLUGIN_BUCKET: ${{ secrets.DOWNLOADS_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -211,9 +216,9 @@ jobs:
- name: Publish Release [S3]
uses: docker://plugins/s3
env:
PLUGIN_SOURCE: "${{ env.JOB_TRANSFER_ARTIFACT }}/*"
PLUGIN_STRIP_PREFIX: "${{ env.JOB_TRANSFER_ARTIFACT }}/"
PLUGIN_TARGET: "/arduino-ide"
PLUGIN_SOURCE: '${{ env.JOB_TRANSFER_ARTIFACT }}/*'
PLUGIN_STRIP_PREFIX: '${{ env.JOB_TRANSFER_ARTIFACT }}/'
PLUGIN_TARGET: '/arduino-ide'
PLUGIN_BUCKET: ${{ secrets.DOWNLOADS_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

View File

@@ -25,10 +25,10 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Node.js 12.x
- name: Install Node.js 14.x
uses: actions/setup-node@v2
with:
node-version: '12.14.1'
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies

View File

@@ -0,0 +1,45 @@
name: Compose full changelog
on:
release:
types: [created, edited]
env:
CHANGELOG_ARTIFACTS: changelog
jobs:
create-changelog:
if: github.repository == 'arduino/arduino-ide'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Get Tag
id: tag_name
run: |
echo ::set-output name=TAG_NAME::${GITHUB_REF#refs/tags/}
- name: Create full changelog
id: full-changelog
run: |
mkdir "${{ github.workspace }}/${{ env.CHANGELOG_ARTIFACTS }}"
# Get the changelog file name to build
CHANGELOG_FILE_NAME="${{ steps.tag_name.outputs.TAG_NAME }}-$(date --iso-8601=s).md"
# Create manifest file pointing to latest changelog file name
echo "$CHANGELOG_FILE_NAME" >> "${{ github.workspace }}/${{ env.CHANGELOG_ARTIFACTS }}/latest.txt"
# Compose changelog
yarn run compose-changelog "${{ github.workspace }}/${{ env.CHANGELOG_ARTIFACTS }}/$CHANGELOG_FILE_NAME"
- name: Publish Changelog [S3]
uses: docker://plugins/s3
env:
PLUGIN_SOURCE: '${{ env.CHANGELOG_ARTIFACTS }}/*'
PLUGIN_STRIP_PREFIX: '${{ env.CHANGELOG_ARTIFACTS }}/'
PLUGIN_TARGET: '/arduino-ide/changelog'
PLUGIN_BUCKET: ${{ secrets.DOWNLOADS_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

View File

@@ -12,10 +12,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Install Node.js 12.x
- name: Install Node.js 14.x
uses: actions/setup-node@v2
with:
node-version: '12.14.1'
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies

View File

@@ -12,10 +12,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Install Node.js 12.x
- name: Install Node.js 14.x
uses: actions/setup-node@v2
with:
node-version: '12.14.1'
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies

View File

@@ -31,7 +31,7 @@ jobs:
- name: Download JSON schema for labels configuration file
id: download-schema
uses: carlosperate/download-file-action@v1.0.3
uses: carlosperate/download-file-action@v1
with:
file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json
location: ${{ runner.temp }}/label-configuration-schema
@@ -66,7 +66,7 @@ jobs:
steps:
- name: Download
uses: carlosperate/download-file-action@v1.0.3
uses: carlosperate/download-file-action@v1
with:
file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }}

20
.vscode/launch.json vendored
View File

@@ -8,10 +8,6 @@
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd",
"env": {
"NODE_ENV": "development",
"NODE_PRESERVE_SYMLINKS": "1"
}
},
"cwd": "${workspaceFolder}/electron-app",
"protocol": "inspector",
@@ -41,6 +37,13 @@
"internalConsoleOptions": "openOnSessionStart",
"outputCapture": "std"
},
{
"type": "chrome",
"request": "attach",
"name": "Attach to Electron Frontend",
"port": 9222,
"webRoot": "${workspaceFolder}/electron-app"
},
{
"type": "node",
"request": "launch",
@@ -108,5 +111,14 @@
"program": "${workspaceRoot}/electron/packager/index.js",
"cwd": "${workspaceFolder}/electron/packager"
}
],
"compounds": [
{
"name": "Launch Electron Backend & Frontend",
"configurations": [
"App (Electron)",
"Attach to Electron Frontend"
]
}
]
}

View File

@@ -40,22 +40,31 @@ The _frontend_ is running as an Electron renderer process and can invoke service
## Build from source
If youre familiar with TypeScript, the [Theia IDE](https://theia-ide.org/), and if you want to contribute to the
project, you should be able to build the Arduino IDE locally. Please refer to the [Theia IDE prerequisites](https://github.com/theia-ide/theia/blob/master/doc/) documentation for the setup instructions.
project, you should be able to build the Arduino IDE locally.
Please refer to the [Theia IDE prerequisites](https://github.com/theia-ide/theia/blob/master/doc/) documentation for the setup instructions.
> **Note**: Node.js 14 must be used instead of the version 12 recommended at the link above.
### Build
```sh
yarn
```
Once you have all the tools installed, you can build the editor following these steps
### Rebuild the native dependencies
```sh
yarn rebuild:electron
```
1. Install the dependencies and build
```sh
yarn
```
### Start
```sh
yarn start
```
2. Rebuild the dependencies
```sh
yarn rebuild:browser
```
3. Rebuild the electron dependencies
```sh
yarn rebuild:electron
```
4. Start the application
```sh
yarn start
```
### CI
@@ -117,7 +126,7 @@ git add . \
git tag -a 0.2.0 -m "0.2.0" \
&& git push origin 0.2.0
```
- The release build starts automatically and uploads the artifacts with the changelog to the [release page](https://github.com/arduino/arduino-ide/releases).
- The release build starts automatically and uploads the artifacts with the changelog to the [release page](https://github.com/arduino/arduino-ide/releases).
- If you do not want to release the `EXE` and `MSI` installers, wipe them manually.
- If you do not like the generated changelog, modify it and update the GH release.

View File

@@ -15,29 +15,30 @@ The Arduino IDE 2.x is a major rewrite, sharing no code with the IDE 1.x. It is
## Download
You can download the latest version from the [software download page on the Arduino website](https://www.arduino.cc/en/software#experimental-software).
### Nightly builds
These builds are generated every day at 03:00 GMT from the `main` branch and
should be considered unstable:
Platform | 32 bit | 64 bit |
--------- | ------------------------ | ------------------------------------------------------------------------------------------------------ |
Linux | | [Nightly Linux 64 bit] |
Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...] |
Windows | | [Nightly Windows 64 bit installer]<br />[Nightly Windows 64 bit MSI]<br />[Nightly Windows 64 bit ZIP] |
macOS | | [Nightly macOS 64 bit] |
| Platform | 32 bit | 64 bit |
| --------- | ------------------------ | ------------------------------------------------------------------------------------------------------ |
| Linux | | [Nightly Linux 64 bit] |
| Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...] |
| Windows | | [Nightly Windows 64 bit installer]<br />[Nightly Windows 64 bit MSI]<br />[Nightly Windows 64 bit ZIP] |
| macOS | | [Nightly macOS 64 bit] |
[🚧 Work in progress...]: https://github.com/arduino/arduino-ide/issues/107
[Nightly Linux 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Linux_64bit.zip
[Nightly Windows 64 bit installer]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.exe
[Nightly Windows 64 bit MSI]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.msi
[Nightly Windows 64 bit ZIP]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.zip
[Nightly macOS 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_macOS_64bit.dmg
[🚧 work in progress...]: https://github.com/arduino/arduino-ide/issues/107
[nightly linux 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Linux_64bit.zip
[nightly windows 64 bit installer]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.exe
[nightly windows 64 bit msi]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.msi
[nightly windows 64 bit zip]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.zip
[nightly macos 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_macOS_64bit.dmg
> These links return an HTTP `302: Found` response, redirecting to latest
generated builds by replacing `latest` with the latest available build
date, using the format YYYYMMDD (i.e for 2019/Aug/06 `latest` is
replaced with `20190806`)
> generated builds by replacing `latest` with the latest available build
> date, using the format YYYYMMDD (i.e for 2019/Aug/06 `latest` is
> replaced with `20190806`)
## Support
@@ -47,8 +48,8 @@ If you need assistance, see the [Help Center](https://support.arduino.cc/hc/en-u
If you want to report an issue, you can submit it to the [issue tracker](https://github.com/arduino/arduino-ide/issues) of this repository. A few rules apply:
* Before posting, please check if the same problem has been already reported by someone else to avoid duplicates.
* Remember to include as much detail as you can about your hardware set-up, code and steps for reproducing the issue. Make sure you're using an original Arduino board.
- Before posting, please check if the same problem has been already reported by someone else to avoid duplicates.
- Remember to include as much detail as you can about your hardware set-up, code and steps for reproducing the issue. Make sure you're using an original Arduino board.
### Security
@@ -64,10 +65,13 @@ Contributions are very welcome! You can browse the list of open issues to see wh
This repository contains the main code, but two more repositories are included during the build process:
* [vscode-arduino-tools](https://github.com/arduino/vscode-arduino-tools): provides support for the language server and the debugger
* [arduino-language-server](https://github.com/arduino/arduino-language-server): provides the language server that parses Arduino code
- [vscode-arduino-tools](https://github.com/arduino/vscode-arduino-tools): provides support for the language server and the debugger
- [arduino-language-server](https://github.com/arduino/arduino-language-server): provides the language server that parses Arduino code
See the [BUILDING.md](BUILDING.md) for a technical overview of the application and instructions for building the code.
You can help with the translation of the Arduino IDE to your language here: [Arduino IDE on Transifex](https://www.transifex.com/arduino-1/ide2/dashboard/).
## Donations
This open source code was written by the Arduino team and is maintained on a daily basis with the help of the community. We invest a considerable amount of time in development, testing and optimization. Please consider [donating](https://www.arduino.cc/en/donate/) or [sponsoring](https://github.com/sponsors/arduino) to support our work, as well as [buying original Arduino boards](https://store.arduino.cc/) which is the best way to make sure our effort can continue in the long term.

View File

@@ -1,11 +1,12 @@
{
"name": "arduino-ide-extension",
"version": "2.0.0-rc1",
"version": "2.0.0-rc4",
"description": "An extension for Theia building the Arduino IDE",
"license": "AGPL-3.0-or-later",
"scripts": {
"prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn copy-serial-plotter && yarn clean && yarn download-examples && yarn build && yarn test",
"clean": "rimraf lib",
"compose-changelog": "node ./scripts/compose-changelog.js",
"download-cli": "node ./scripts/download-cli.js",
"download-fwuploader": "node ./scripts/download-fwuploader.js",
"copy-serial-plotter": "npx ncp ../node_modules/arduino-serial-plotter-webapp ./build/arduino-serial-plotter-webapp",
@@ -19,24 +20,24 @@
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
},
"dependencies": {
"arduino-serial-plotter-webapp": "0.0.15",
"@grpc/grpc-js": "^1.3.7",
"@theia/application-package": "1.19.0",
"@theia/core": "1.19.0",
"@theia/editor": "1.19.0",
"@theia/editor-preview": "1.19.0",
"@theia/filesystem": "1.19.0",
"@theia/git": "1.19.0",
"@theia/keymaps": "1.19.0",
"@theia/markers": "1.19.0",
"@theia/monaco": "1.19.0",
"@theia/navigator": "1.19.0",
"@theia/outline-view": "1.19.0",
"@theia/output": "1.19.0",
"@theia/preferences": "1.19.0",
"@theia/search-in-workspace": "1.19.0",
"@theia/terminal": "1.19.0",
"@theia/workspace": "1.19.0",
"@theia/application-package": "1.22.1",
"@theia/core": "1.22.1",
"@theia/editor": "1.22.1",
"@theia/editor-preview": "1.22.1",
"@theia/electron": "1.22.1",
"@theia/filesystem": "1.22.1",
"@theia/git": "1.22.1",
"@theia/keymaps": "1.22.1",
"@theia/markers": "1.22.1",
"@theia/monaco": "1.22.1",
"@theia/navigator": "1.22.1",
"@theia/outline-view": "1.22.1",
"@theia/output": "1.22.1",
"@theia/preferences": "1.22.1",
"@theia/search-in-workspace": "1.22.1",
"@theia/terminal": "1.22.1",
"@theia/workspace": "1.22.1",
"@tippyjs/react": "^4.2.5",
"@types/atob": "^2.1.2",
"@types/auth0-js": "^9.14.0",
@@ -53,10 +54,10 @@
"@types/ps-tree": "^1.1.0",
"@types/react-select": "^3.0.0",
"@types/react-tabs": "^2.3.2",
"@types/sinon": "^7.5.2",
"@types/temp": "^0.8.34",
"@types/which": "^1.3.1",
"ajv": "^6.5.3",
"arduino-serial-plotter-webapp": "0.0.17",
"async-mutex": "^0.3.0",
"atob": "^2.1.2",
"auth0-js": "^9.14.0",
@@ -64,6 +65,7 @@
"css-element-queries": "^1.2.0",
"dateformat": "^3.0.3",
"deepmerge": "2.0.1",
"electron-updater": "^4.6.5",
"fuzzy": "^0.1.3",
"glob": "^7.1.6",
"google-protobuf": "^3.11.4",
@@ -81,6 +83,7 @@
"ps-tree": "^1.2.0",
"query-string": "^7.0.1",
"react-disable": "^0.1.0",
"react-markdown": "^8.0.0",
"react-select": "^3.0.4",
"react-tabs": "^3.1.2",
"react-window": "^1.8.6",
@@ -93,10 +96,13 @@
"which": "^1.3.1"
},
"devDependencies": {
"@octokit/rest": "^18.12.0",
"@types/chai": "^4.2.7",
"@types/chai-string": "^1.4.2",
"@types/mocha": "^5.2.7",
"@types/react-window": "^1.8.5",
"@types/sinon": "^10.0.6",
"@types/sinon-chai": "^3.2.6",
"chai": "^4.2.0",
"chai-string": "^1.5.0",
"decompress": "^4.2.0",
@@ -109,7 +115,8 @@
"moment": "^2.24.0",
"protoc": "^1.0.4",
"shelljs": "^0.8.3",
"sinon": "^9.0.1",
"sinon": "^12.0.1",
"sinon-chai": "^3.7.0",
"typemoq": "^2.1.0",
"uuid": "^3.2.1",
"yargs": "^11.1.0"
@@ -149,10 +156,16 @@
],
"arduino": {
"cli": {
"version": "0.20.1"
"version": "0.21.0"
},
"fwuploader": {
"version": "2.0.0"
},
"clangd": {
"version": "13.0.0"
},
"languageServer": {
"version": "0.6.0"
}
}
}
}

View File

@@ -0,0 +1,116 @@
// @ts-check
(async () => {
const { Octokit } = require('@octokit/rest');
const fs = require('fs');
const path = require('path');
const octokit = new Octokit({
userAgent: 'Arduino IDE compose-changelog.js',
});
const response = await octokit.rest.repos
.listReleases({
owner: 'arduino',
repo: 'arduino-ide',
})
.catch((err) => {
console.error(err);
process.exit(1);
});
const releases = response.data;
let fullChangelog = releases.reduce((acc, item, index) => {
// Process each line separately
const body = item.body.split('\n').map(processLine).join('\n');
// item.name is the name of the release changelog
return (
acc +
`## ${item.name}\n\n${body}${
index !== releases.length - 1 ? '\n\n---\n\n' : '\n'
}`
);
}, '');
const args = process.argv.slice(2);
if (args.length == 0) {
console.error('Missing argument to destination file');
process.exit(1);
}
const changelogFile = path.resolve(args[0]);
await fs.writeFile(
changelogFile,
fullChangelog,
{
flag: 'w+',
},
(err) => {
if (err) {
console.error(err);
process.exit(1);
}
console.log('Changelog written to', changelogFile);
}
);
})();
// processLine applies different substitutions to line string.
// We're assuming that there are no more than one substitution
// per line to be applied.
const processLine = (line) => {
// Check if a link with one of the following format exists:
// * [#123](https://github.com/arduino/arduino-ide/pull/123)
// * [#123](https://github.com/arduino/arduino-ide/issues/123)
// * [#123](https://github.com/arduino/arduino-ide/pull/123/)
// * [#123](https://github.com/arduino/arduino-ide/issues/123/)
// If it does return the line as is.
let r =
/(\(|\[)#\d+(\)|\])(\(|\[)https:\/\/github\.com\/arduino\/arduino-ide\/(pull|issues)\/(\d+)\/?(\)|\])/gm;
if (r.test(line)) {
return line;
}
// Check if a issue or PR link with the following format exists:
// * #123
// If it does it's changed to:
// * [#123](https://github.com/arduino/arduino-ide/pull/123)
r = /(?<![\w\d\/_]{1})#((\d)+)(?![\w\d\/_]{1})/gm;
if (r.test(line)) {
return line.replace(
r,
`[#$1](https://github.com/arduino/arduino-ide/pull/$1)`
);
}
// Check if a link with one of the following format exists:
// * https://github.com/arduino/arduino-ide/pull/123
// * https://github.com/arduino/arduino-ide/issues/123
// * https://github.com/arduino/arduino-ide/pull/123/
// * https://github.com/arduino/arduino-ide/issues/123/
// If it does it's changed respectively to:
// * [#123](https://github.com/arduino/arduino-ide/pull/123)
// * [#123](https://github.com/arduino/arduino-ide/issues/123)
// * [#123](https://github.com/arduino/arduino-ide/pull/123/)
// * [#123](https://github.com/arduino/arduino-ide/issues/123/)
r =
/(https:\/\/github\.com\/arduino\/arduino-ide\/(pull|issues)\/(\d+)\/?)/gm;
if (r.test(line)) {
return line.replace(r, `[#$3]($1)`);
}
// Check if a link with the following format exists:
// * https://github.com/arduino/arduino-ide/compare/2.0.0-rc2...2.0.0-rc3
// * https://github.com/arduino/arduino-ide/compare/2.0.0-rc2...2.0.0-rc3/
// If it does it's changed to:
// * [`2.0.0-rc2...2.0.0-rc3`](https://github.com/arduino/arduino-ide/compare/2.0.0-rc2...2.0.0-rc3)
r =
/(https:\/\/github\.com\/arduino\/arduino-ide\/compare\/([^\/]*))\/?\s?/gm;
if (r.test(line)) {
return line.replace(r, '[`$2`]($1)');
}
// If nothing matches just return the line as is
return line;
};

View File

@@ -4,13 +4,38 @@
// - https://downloads.arduino.cc/arduino-language-server/clangd/clangd_${VERSION}_${SUFFIX}
(() => {
const DEFAULT_ALS_VERSION = '0.5.0-rc2';
const DEFAULT_CLANGD_VERSION = 'snapshot_20210124';
const path = require('path');
const shell = require('shelljs');
const downloader = require('./downloader');
const [DEFAULT_ALS_VERSION, DEFAULT_CLANGD_VERSION] = (() => {
const pkg = require(path.join(__dirname, '..', 'package.json'));
if (!pkg) return undefined;
const { arduino } = pkg;
if (!arduino) return undefined;
const { languageServer, clangd } = arduino;
if (!languageServer) return undefined;
if (!clangd) return undefined;
return [languageServer.version, clangd.version];
})();
if (!DEFAULT_ALS_VERSION) {
shell.echo(
`Could not retrieve Arduino Language Server version info from the 'package.json'.`
);
shell.exit(1);
}
if (!DEFAULT_CLANGD_VERSION) {
shell.echo(
`Could not retrieve clangd version info from the 'package.json'.`
);
shell.exit(1);
}
const yargs = require('yargs')
.option('ls-version', {
alias: 'lv',
@@ -20,7 +45,7 @@
.option('clangd-version', {
alias: 'cv',
default: DEFAULT_CLANGD_VERSION,
choices: ['snapshot_20210124'],
choices: [DEFAULT_CLANGD_VERSION, 'snapshot_20210124'],
describe: `The version of 'clangd' to download. Defaults to ${DEFAULT_CLANGD_VERSION}.`,
})
.option('force-download', {
@@ -35,32 +60,32 @@
const clangdVersion = yargs['clangd-version'];
const force = yargs['force-download'];
const { platform, arch } = process;
const platformArch = platform + '-' + arch;
const build = path.join(__dirname, '..', 'build');
const lsExecutablePath = path.join(
build,
`arduino-language-server${platform === 'win32' ? '.exe' : ''}`
);
let clangdExecutablePath, lsSuffix, clangdSuffix;
let clangdExecutablePath, lsSuffix, clangdPrefix;
switch (platform) {
case 'darwin':
clangdExecutablePath = path.join(build, 'bin', 'clangd');
switch (platformArch) {
case 'darwin-x64':
clangdExecutablePath = path.join(build, 'clangd');
lsSuffix = 'macOS_64bit.tar.gz';
clangdPrefix = 'mac';
clangdSuffix = 'macOS_64bit';
break;
case 'linux':
clangdExecutablePath = path.join(build, 'bin', 'clangd');
case 'linux-x64':
clangdExecutablePath = path.join(build, 'clangd');
lsSuffix = 'Linux_64bit.tar.gz';
clangdPrefix = 'linux';
clangdSuffix = 'Linux_64bit';
break;
case 'win32':
clangdExecutablePath = path.join(build, 'bin', 'clangd.exe');
case 'win32-x64':
clangdExecutablePath = path.join(build, 'clangd.exe');
lsSuffix = 'Windows_64bit.zip';
clangdPrefix = 'windows';
clangdSuffix = 'Windows_64bit';
break;
}
if (!lsSuffix) {
if (!lsSuffix || !clangdSuffix) {
shell.echo(
`The arduino-language-server is not available for ${platform} ${arch}.`
);
@@ -74,7 +99,7 @@
}_${lsSuffix}`;
downloader.downloadUnzipAll(alsUrl, build, lsExecutablePath, force);
const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd-${clangdPrefix}-${clangdVersion}.zip`;
const clangdUrl = `https://downloads.arduino.cc/tools/clangd_${clangdVersion}_${clangdSuffix}.tar.bz2`;
downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, {
strip: 1,
}); // `strip`: the new clangd (12.x) is zipped into a folder, so we have to strip the outmost folder.

View File

@@ -5,16 +5,17 @@ const download = require('download');
const decompress = require('decompress');
const unzip = require('decompress-unzip');
const untargz = require('decompress-targz');
const untarbz2 = require('decompress-tarbz2');
process.on('unhandledRejection', (reason, _) => {
shell.echo(String(reason));
shell.exit(1);
throw reason;
shell.echo(String(reason));
shell.exit(1);
throw reason;
});
process.on('uncaughtException', error => {
shell.echo(String(error));
shell.exit(1);
throw error;
process.on('uncaughtException', (error) => {
shell.echo(String(error));
shell.exit(1);
throw error;
});
/**
@@ -23,55 +24,62 @@ process.on('uncaughtException', error => {
* @param filePrefix {string} Prefix of the file name found in the archive
* @param force {boolean} Whether to download even if the target file exists. `false` by default.
*/
exports.downloadUnzipFile = async (url, targetFile, filePrefix, force = false) => {
if (fs.existsSync(targetFile) && !force) {
shell.echo(`Skipping download because file already exists: ${targetFile}`);
return;
}
if (!fs.existsSync(path.dirname(targetFile))) {
if (shell.mkdir('-p', path.dirname(targetFile)).code !== 0) {
shell.echo('Could not create new directory.');
shell.exit(1);
}
exports.downloadUnzipFile = async (
url,
targetFile,
filePrefix,
force = false
) => {
if (fs.existsSync(targetFile) && !force) {
shell.echo(`Skipping download because file already exists: ${targetFile}`);
return;
}
if (!fs.existsSync(path.dirname(targetFile))) {
if (shell.mkdir('-p', path.dirname(targetFile)).code !== 0) {
shell.echo('Could not create new directory.');
shell.exit(1);
}
}
const downloads = path.join(__dirname, '..', 'downloads');
if (shell.rm('-rf', targetFile, downloads).code !== 0) {
shell.exit(1);
}
const downloads = path.join(__dirname, '..', 'downloads');
if (shell.rm('-rf', targetFile, downloads).code !== 0) {
shell.exit(1);
}
shell.echo(`>>> Downloading from '${url}'...`);
const data = await download(url);
shell.echo(`<<< Download succeeded.`);
shell.echo(`>>> Downloading from '${url}'...`);
const data = await download(url);
shell.echo(`<<< Download succeeded.`);
shell.echo('>>> Decompressing...');
const files = await decompress(data, downloads, {
plugins: [
unzip(),
untargz()
]
});
if (files.length === 0) {
shell.echo('Error ocurred while decompressing the archive.');
shell.exit(1);
}
const fileIndex = files.findIndex(f => f.path.startsWith(filePrefix));
if (fileIndex === -1) {
shell.echo(`The downloaded artifact does not contain any file with prefix ${filePrefix}.`);
shell.exit(1);
}
shell.echo('<<< Decompressing succeeded.');
shell.echo('>>> Decompressing...');
const files = await decompress(data, downloads, {
plugins: [unzip(), untargz(), untarbz2()],
});
if (files.length === 0) {
shell.echo('Error ocurred while decompressing the archive.');
shell.exit(1);
}
const fileIndex = files.findIndex((f) => f.path.startsWith(filePrefix));
if (fileIndex === -1) {
shell.echo(
`The downloaded artifact does not contain any file with prefix ${filePrefix}.`
);
shell.exit(1);
}
shell.echo('<<< Decompressing succeeded.');
if (shell.mv('-f', path.join(downloads, files[fileIndex].path), targetFile).code !== 0) {
shell.echo(`Could not move file to target path: ${targetFile}`);
shell.exit(1);
}
if (!fs.existsSync(targetFile)) {
shell.echo(`Could not find file: ${targetFile}`);
shell.exit(1);
}
shell.echo(`Done: ${targetFile}`);
}
if (
shell.mv('-f', path.join(downloads, files[fileIndex].path), targetFile)
.code !== 0
) {
shell.echo(`Could not move file to target path: ${targetFile}`);
shell.exit(1);
}
if (!fs.existsSync(targetFile)) {
shell.echo(`Could not find file: ${targetFile}`);
shell.exit(1);
}
shell.echo(`Done: ${targetFile}`);
};
/**
* @param url {string} Download URL
@@ -79,42 +87,45 @@ exports.downloadUnzipFile = async (url, targetFile, filePrefix, force = false) =
* @param targetFile {string} Path to the main file expected after decompressing
* @param force {boolean} Whether to download even if the target file exists
*/
exports.downloadUnzipAll = async (url, targetDir, targetFile, force, decompressOptions = undefined) => {
if (fs.existsSync(targetFile) && !force) {
shell.echo(`Skipping download because file already exists: ${targetFile}`);
return;
}
if (!fs.existsSync(targetDir)) {
if (shell.mkdir('-p', targetDir).code !== 0) {
shell.echo('Could not create new directory.');
shell.exit(1);
}
exports.downloadUnzipAll = async (
url,
targetDir,
targetFile,
force,
decompressOptions = undefined
) => {
if (fs.existsSync(targetFile) && !force) {
shell.echo(`Skipping download because file already exists: ${targetFile}`);
return;
}
if (!fs.existsSync(targetDir)) {
if (shell.mkdir('-p', targetDir).code !== 0) {
shell.echo('Could not create new directory.');
shell.exit(1);
}
}
shell.echo(`>>> Downloading from '${url}'...`);
const data = await download(url);
shell.echo(`<<< Download succeeded.`);
shell.echo(`>>> Downloading from '${url}'...`);
const data = await download(url);
shell.echo(`<<< Download succeeded.`);
shell.echo('>>> Decompressing...');
let options = {
plugins: [
unzip(),
untargz()
]
};
if (decompressOptions) {
options = Object.assign(options, decompressOptions)
}
const files = await decompress(data, targetDir, options);
if (files.length === 0) {
shell.echo('Error ocurred while decompressing the archive.');
shell.exit(1);
}
shell.echo('<<< Decompressing succeeded.');
shell.echo('>>> Decompressing...');
let options = {
plugins: [unzip(), untargz(), untarbz2()],
};
if (decompressOptions) {
options = Object.assign(options, decompressOptions);
}
const files = await decompress(data, targetDir, options);
if (files.length === 0) {
shell.echo('Error ocurred while decompressing the archive.');
shell.exit(1);
}
shell.echo('<<< Decompressing succeeded.');
if (!fs.existsSync(targetFile)) {
shell.echo(`Could not find file: ${targetFile}`);
shell.exit(1);
}
shell.echo(`Done: ${targetFile}`);
}
if (!fs.existsSync(targetFile)) {
shell.echo(`Could not find file: ${targetFile}`);
shell.exit(1);
}
shell.echo(`Done: ${targetFile}`);
};

View File

@@ -1,12 +1,12 @@
import { inject, injectable, postConstruct } from 'inversify';
import * as React from 'react';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import {
BoardsService,
Port,
SketchesService,
ExecutableService,
Sketch,
LibraryService,
} from '../common/protocol';
import { Mutex } from 'async-mutex';
import {
@@ -68,8 +68,11 @@ import { ArduinoPreferences } from './arduino-preferences';
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
import { SaveAsSketch } from './contributions/save-as-sketch';
import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution';
import { IDEUpdaterDialog } from './dialogs/ide-updater/ide-updater-dialog';
import { IDEUpdater } from '../common/protocol/ide-updater';
const INIT_AVR_PACKAGES = 'initializedAvrPackages';
const INIT_LIBS_AND_PACKAGES = 'initializedLibsAndPackages';
export const SKIP_IDE_VERSION = 'skipIDEVersion';
@injectable()
export class ArduinoFrontendContribution
@@ -78,8 +81,7 @@ export class ArduinoFrontendContribution
TabBarToolbarContribution,
CommandContribution,
MenuContribution,
ColorContribution
{
ColorContribution {
@inject(ILogger)
protected logger: ILogger;
@@ -89,6 +91,9 @@ export class ArduinoFrontendContribution
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(LibraryService)
protected readonly libraryService: LibraryService;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
@@ -154,6 +159,12 @@ export class ArduinoFrontendContribution
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
@inject(IDEUpdater)
protected readonly updater: IDEUpdater;
@inject(IDEUpdaterDialog)
protected readonly updaterDialog: IDEUpdaterDialog;
protected invalidConfigPopup:
| Promise<void | 'No' | 'Yes' | undefined>
| undefined;
@@ -161,15 +172,26 @@ export class ArduinoFrontendContribution
@postConstruct()
protected async init(): Promise<void> {
const notFirstStartup = await this.localStorageService.getData(
INIT_AVR_PACKAGES
);
if (!notFirstStartup) {
await this.localStorageService.setData(INIT_AVR_PACKAGES, true);
const isFirstStartup = !(await this.localStorageService.getData(
INIT_LIBS_AND_PACKAGES
));
if (isFirstStartup) {
await this.localStorageService.setData(INIT_LIBS_AND_PACKAGES, true);
const avrPackage = await this.boardsService.getBoardPackage({
id: 'arduino:avr',
});
avrPackage && (await this.boardsService.install({ item: avrPackage }));
const builtInLibrary = (
await this.libraryService.search({
query: 'Arduino_BuiltIn',
})
)[0];
!!avrPackage && (await this.boardsService.install({ item: avrPackage }));
!!builtInLibrary &&
(await this.libraryService.install({
item: builtInLibrary,
installDependencies: true,
}));
}
if (!window.navigator.onLine) {
// tslint:disable-next-line:max-line-length
@@ -201,7 +223,7 @@ export class ArduinoFrontendContribution
? nls.localize(
'arduino/common/selectedOn',
'on {0}',
Port.toString(selectedPort)
selectedPort.address
)
: nls.localize('arduino/common/notConnected', '[not connected]'),
className: 'arduino-selected-port',
@@ -237,7 +259,7 @@ export class ArduinoFrontendContribution
});
}
onStart(app: FrontendApplication): void {
async onStart(app: FrontendApplication): Promise<void> {
// Initialize all `pro-mode` widgets. This is a NOOP if in normal mode.
for (const viewContribution of [
this.fileNavigatorContributions,
@@ -252,6 +274,31 @@ export class ArduinoFrontendContribution
viewContribution.initializeLayout(app);
}
}
this.updater
.init(
this.arduinoPreferences.get('arduino.ide.updateChannel'),
this.arduinoPreferences.get('arduino.ide.updateBaseUrl')
)
.then(() => this.updater.checkForUpdates(true))
.then(async (updateInfo) => {
if (!updateInfo) return;
const versionToSkip = await this.localStorageService.getData<string>(
SKIP_IDE_VERSION
);
if (versionToSkip === updateInfo.version) return;
this.updaterDialog.open(updateInfo);
})
.catch((e) => {
this.messageService.error(
nls.localize(
'arduino/ide-updater/errorCheckingForUpdates',
'Error while checking for Arduino IDE updates.\n{0}',
e.message
)
);
});
const start = async ({ selectedBoard }: BoardsConfig.Config) => {
if (selectedBoard) {
const { name, fqbn } = selectedBoard;
@@ -262,11 +309,25 @@ export class ArduinoFrontendContribution
};
this.boardsServiceClientImpl.onBoardsConfigChanged(start);
this.arduinoPreferences.onPreferenceChanged((event) => {
if (
event.preferenceName === 'arduino.language.log' &&
event.newValue !== event.oldValue
) {
start(this.boardsServiceClientImpl.boardsConfig);
if (event.newValue !== event.oldValue) {
switch (event.preferenceName) {
case 'arduino.language.log':
start(this.boardsServiceClientImpl.boardsConfig);
break;
case 'arduino.window.zoomLevel':
if (typeof event.newValue === 'number') {
const webContents = remote.getCurrentWebContents();
webContents.setZoomLevel(event.newValue || 0);
}
break;
case 'arduino.ide.updateChannel':
case 'arduino.ide.updateBaseUrl':
this.updater.init(
this.arduinoPreferences.get('arduino.ide.updateChannel'),
this.arduinoPreferences.get('arduino.ide.updateBaseUrl')
);
break;
}
}
});
this.arduinoPreferences.ready.then(() => {
@@ -274,16 +335,7 @@ export class ArduinoFrontendContribution
const zoomLevel = this.arduinoPreferences.get('arduino.window.zoomLevel');
webContents.setZoomLevel(zoomLevel);
});
this.arduinoPreferences.onPreferenceChanged((event) => {
if (
event.preferenceName === 'arduino.window.zoomLevel' &&
typeof event.newValue === 'number' &&
event.newValue !== event.oldValue
) {
const webContents = remote.getCurrentWebContents();
webContents.setZoomLevel(event.newValue || 0);
}
});
app.shell.leftPanelHandler.removeBottomMenu('settings-menu');
}
@@ -359,7 +411,7 @@ export class ArduinoFrontendContribution
'arduino.languageserver.start',
{
lsPath,
cliDaemonAddr: `localhost:${config.daemon.port}`,
cliDaemonAddr: `localhost:${config.daemon.port}`, // TODO: verify if this port is coming from the BE
clangdPath,
log: currentSketchPath ? currentSketchPath : log,
cliDaemonInstance: '1',

View File

@@ -262,6 +262,19 @@ import {
UserFieldsDialogWidget,
} from './dialogs/user-fields/user-fields-dialog';
import { nls } from '@theia/core/lib/common';
import { IDEUpdaterCommands } from './ide-updater/ide-updater-commands';
import {
IDEUpdater,
IDEUpdaterClient,
IDEUpdaterPath,
} from '../common/protocol/ide-updater';
import { IDEUpdaterClientImpl } from './ide-updater/ide-updater-client-impl';
import {
IDEUpdaterDialog,
IDEUpdaterDialogProps,
IDEUpdaterDialogWidget,
} from './dialogs/ide-updater/ide-updater-dialog';
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider';
const ElementQueries = require('css-element-queries/src/ElementQueries');
@@ -407,8 +420,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(SerialService)
.toDynamicValue((context) => {
const connection = context.container.get(WebSocketConnectionProvider);
const client =
context.container.get<SerialServiceClient>(SerialServiceClient);
const client = context.container.get<SerialServiceClient>(
SerialServiceClient
);
return connection.createProxy(SerialServicePath, client);
})
.inSingletonScope();
@@ -472,12 +486,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
.inSingletonScope();
rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope();
rebind(TabBarToolbarFactory).toFactory(
({ container: parentContainer }) =>
() => {
const container = parentContainer.createChild();
container.bind(TabBarToolbar).toSelf().inSingletonScope();
return container.get(TabBarToolbar);
}
({ container: parentContainer }) => () => {
const container = parentContainer.createChild();
container.bind(TabBarToolbar).toSelf().inSingletonScope();
return container.get(TabBarToolbar);
}
);
bind(OutputWidget).toSelf().inSingletonScope();
rebind(TheiaOutputWidget).toService(OutputWidget);
@@ -642,13 +655,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Enable the dirty indicator on uncloseable widgets.
rebind(TabBarRendererFactory).toFactory((context) => () => {
const contextMenuRenderer =
context.container.get<ContextMenuRenderer>(ContextMenuRenderer);
const contextMenuRenderer = context.container.get<ContextMenuRenderer>(
ContextMenuRenderer
);
const decoratorService = context.container.get<TabBarDecoratorService>(
TabBarDecoratorService
);
const iconThemeService =
context.container.get<IconThemeService>(IconThemeService);
const iconThemeService = context.container.get<IconThemeService>(
IconThemeService
);
return new TabBarRenderer(
contextMenuRenderer,
decoratorService,
@@ -756,9 +771,32 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
title: 'UploadCertificate',
});
bind(IDEUpdaterDialogWidget).toSelf().inSingletonScope();
bind(IDEUpdaterDialog).toSelf().inSingletonScope();
bind(IDEUpdaterDialogProps).toConstantValue({
title: 'IDEUpdater',
});
bind(UserFieldsDialogWidget).toSelf().inSingletonScope();
bind(UserFieldsDialog).toSelf().inSingletonScope();
bind(UserFieldsDialogProps).toConstantValue({
title: 'UserFields',
});
bind(IDEUpdaterCommands).toSelf().inSingletonScope();
bind(CommandContribution).toService(IDEUpdaterCommands);
// Frontend binding for the IDE Updater service
bind(IDEUpdaterClientImpl).toSelf().inSingletonScope();
bind(IDEUpdaterClient).toService(IDEUpdaterClientImpl);
bind(IDEUpdater)
.toDynamicValue((context) => {
const client = context.container.get(IDEUpdaterClientImpl);
return ElectronIpcConnectionProvider.createProxy(
context.container,
IDEUpdaterPath,
client
);
})
.inSingletonScope();
});

View File

@@ -9,6 +9,11 @@ import {
import { nls } from '@theia/core/lib/common';
import { CompilerWarningLiterals, CompilerWarnings } from '../common/protocol';
export enum UpdateChannel {
Stable = 'stable',
Nightly = 'nightly',
}
export const ArduinoConfigSchema: PreferenceSchema = {
type: 'object',
properties: {
@@ -64,13 +69,22 @@ export const ArduinoConfigSchema: PreferenceSchema = {
),
default: 0,
},
'arduino.ide.autoUpdate': {
type: 'boolean',
'arduino.ide.updateChannel': {
type: 'string',
enum: Object.values(UpdateChannel) as UpdateChannel[],
default: UpdateChannel.Stable,
description: nls.localize(
'arduino/preferences/ide.autoUpdate',
'True to enable automatic update checks. The IDE will check for updates automatically and periodically.'
'arduino/preferences/ide.updateChannel',
"Release channel to get updated from. 'stable' is the stable release, 'nightly' is the latest development build."
),
},
'arduino.ide.updateBaseUrl': {
type: 'string',
default: 'https://downloads.arduino.cc/arduino-ide',
description: nls.localize(
'arduino/preferences/ide.updateBaseUrl',
`The base URL where to download updates from. Defaults to 'https://downloads.arduino.cc/arduino-ide'`
),
default: true,
},
'arduino.board.certificates': {
type: 'string',
@@ -171,7 +185,8 @@ export interface ArduinoConfiguration {
'arduino.upload.verify': boolean;
'arduino.window.autoScale': boolean;
'arduino.window.zoomLevel': number;
'arduino.ide.autoUpdate': boolean;
'arduino.ide.updateChannel': UpdateChannel;
'arduino.ide.updateBaseUrl': string;
'arduino.board.certificates': string;
'arduino.sketchbook.showAllFiles': boolean;
'arduino.cloud.enabled': boolean;
@@ -188,16 +203,10 @@ export interface ArduinoConfiguration {
export const ArduinoPreferences = Symbol('ArduinoPreferences');
export type ArduinoPreferences = PreferenceProxy<ArduinoConfiguration>;
export function createArduinoPreferences(
preferences: PreferenceService
): ArduinoPreferences {
return createPreferenceProxy(preferences, ArduinoConfigSchema);
}
export function bindArduinoPreferences(bind: interfaces.Bind): void {
bind(ArduinoPreferences).toDynamicValue((ctx) => {
const preferences = ctx.container.get<PreferenceService>(PreferenceService);
return createArduinoPreferences(preferences);
return createPreferenceProxy(preferences, ArduinoConfigSchema);
});
bind(PreferenceContribution).toConstantValue({
schema: ArduinoConfigSchema,

View File

@@ -1,5 +1,4 @@
import { toUnix } from 'upath';
import URI from '@theia/core/lib/common/uri';
import { URI } from '@theia/core/shared/vscode-uri';
import { isWindows } from '@theia/core/lib/common/os';
import { notEmpty } from '@theia/core/lib/common/objects';
import { MaybePromise } from '@theia/core/lib/common/types';
@@ -61,12 +60,8 @@ export class ArduinoWorkspaceRootResolver {
// - https://github.com/eclipse-theia/theia/blob/8196e9dcf9c8de8ea0910efeb5334a974f426966/packages/workspace/src/browser/workspace-service.ts#L423
protected hashToUri(hash: string | undefined): string | undefined {
if (hash && hash.length > 1 && hash.startsWith('#')) {
const path = hash.slice(1); // Trim the leading `#`.
return new URI(
toUnix(path.slice(isWindows && hash.startsWith('/') ? 1 : 0))
)
.withScheme('file')
.toString();
const path = decodeURI(hash.slice(1)).replace(/\\/g, '/'); // Trim the leading `#`, decode the URI and replace Windows separators
return URI.file(path.slice(isWindows && hash.startsWith('/') ? 1 : 0)).toString();
}
return undefined;
}

View File

@@ -84,7 +84,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
),
nls.localize(
'arduino/board/configDialog2',
'If you only select a Board you will be able just to compile, but not to upload your sketch.'
'If you only select a Board you will be able to compile, but not to upload your sketch.'
),
]) {
const p = document.createElement('div');

View File

@@ -167,7 +167,7 @@ export class BoardsConfig extends React.Component<
this.queryPorts(Promise.resolve(ports)).then(({ knownPorts }) => {
let { selectedPort } = this.state;
// If the currently selected port is not available anymore, unset the selected port.
if (removedPorts.some((port) => Port.equals(port, selectedPort))) {
if (removedPorts.some((port) => Port.sameAs(port, selectedPort))) {
selectedPort = undefined;
}
this.setState({ knownPorts, selectedPort }, () =>
@@ -213,11 +213,11 @@ export class BoardsConfig extends React.Component<
} else if (left.protocol === right.protocol) {
// We show ports, including those that have guessed
// or unrecognized boards, so we must sort those too.
const leftBoard = this.availableBoards.find((board) =>
Port.sameAs(board.port, left)
const leftBoard = this.availableBoards.find(
(board) => board.port === left
);
const rightBoard = this.availableBoards.find((board) =>
Port.sameAs(board.port, right)
const rightBoard = this.availableBoards.find(
(board) => board.port === right
);
if (leftBoard && !rightBoard) {
return -1;
@@ -348,10 +348,10 @@ export class BoardsConfig extends React.Component<
<div className="ports list">
{ports.map((port) => (
<Item<Port>
key={Port.toString(port)}
key={`${port.id}`}
item={port}
label={Port.toString(port)}
selected={Port.equals(this.state.selectedPort, port)}
selected={Port.sameAs(this.state.selectedPort, port)}
onClick={this.selectPort}
/>
))}
@@ -410,7 +410,7 @@ export namespace BoardsConfig {
return options.default;
}
const { name } = selectedBoard;
return `${name}${port ? ' at ' + Port.toString(port) : ''}`;
return `${name}${port ? ` at ${port.address}` : ''}`;
}
export function setConfig(

View File

@@ -1,7 +1,6 @@
import { injectable, inject, named } from 'inversify';
import { ILogger } from '@theia/core/lib/common/logger';
import { deepClone } from '@theia/core/lib/common/objects';
import { MaybePromise } from '@theia/core/lib/common/types';
import { Event, Emitter } from '@theia/core/lib/common/event';
import {
FrontendApplicationContribution,
@@ -11,7 +10,6 @@ import { notEmpty } from '../../common/utils';
import {
BoardsService,
ConfigOption,
Installable,
BoardDetails,
Programmer,
} from '../../common/protocol';
@@ -36,16 +34,12 @@ export class BoardsDataStore implements FrontendApplicationContribution {
onStart(): void {
this.notificationCenter.onPlatformInstalled(async ({ item }) => {
const { installedVersion: version } = item;
if (!version) {
return;
}
let shouldFireChanged = false;
for (const fqbn of item.boards
.map(({ fqbn }) => fqbn)
.filter(notEmpty)
.filter((fqbn) => !!fqbn)) {
const key = this.getStorageKey(fqbn, version);
const key = this.getStorageKey(fqbn);
let data = await this.storageService.getData<
ConfigOption[] | undefined
>(key);
@@ -72,33 +66,20 @@ export class BoardsDataStore implements FrontendApplicationContribution {
async appendConfigToFqbn(
fqbn: string | undefined,
boardsPackageVersion: MaybePromise<
Installable.Version | undefined
> = this.getBoardsPackageVersion(fqbn)
): Promise<string | undefined> {
if (!fqbn) {
return undefined;
}
const { configOptions } = await this.getData(fqbn, boardsPackageVersion);
const { configOptions } = await this.getData(fqbn);
return ConfigOption.decorate(fqbn, configOptions);
}
async getData(
fqbn: string | undefined,
boardsPackageVersion: MaybePromise<
Installable.Version | undefined
> = this.getBoardsPackageVersion(fqbn)
): Promise<BoardsDataStore.Data> {
async getData(fqbn: string | undefined): Promise<BoardsDataStore.Data> {
if (!fqbn) {
return BoardsDataStore.Data.EMPTY;
}
const version = await boardsPackageVersion;
if (!version) {
return BoardsDataStore.Data.EMPTY;
}
const key = this.getStorageKey(fqbn, version);
const key = this.getStorageKey(fqbn);
let data = await this.storageService.getData<
BoardsDataStore.Data | undefined
>(key, undefined);
@@ -124,25 +105,16 @@ export class BoardsDataStore implements FrontendApplicationContribution {
fqbn,
selectedProgrammer,
}: { fqbn: string; selectedProgrammer: Programmer },
boardsPackageVersion: MaybePromise<
Installable.Version | undefined
> = this.getBoardsPackageVersion(fqbn)
): Promise<boolean> {
const data = deepClone(await this.getData(fqbn, boardsPackageVersion));
const data = deepClone(await this.getData(fqbn));
const { programmers } = data;
if (!programmers.find((p) => Programmer.equals(selectedProgrammer, p))) {
return false;
}
const version = await boardsPackageVersion;
if (!version) {
return false;
}
await this.setData({
fqbn,
data: { ...data, selectedProgrammer },
version,
});
this.fireChanged();
return true;
@@ -153,12 +125,9 @@ export class BoardsDataStore implements FrontendApplicationContribution {
fqbn,
option,
selectedValue,
}: { fqbn: string; option: string; selectedValue: string },
boardsPackageVersion: MaybePromise<
Installable.Version | undefined
> = this.getBoardsPackageVersion(fqbn)
}: { fqbn: string; option: string; selectedValue: string }
): Promise<boolean> {
const data = deepClone(await this.getData(fqbn, boardsPackageVersion));
const data = deepClone(await this.getData(fqbn));
const { configOptions } = data;
const configOption = configOptions.find((c) => c.option === option);
if (!configOption) {
@@ -176,12 +145,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
if (!updated) {
return false;
}
const version = await boardsPackageVersion;
if (!version) {
return false;
}
await this.setData({ fqbn, data, version });
await this.setData({ fqbn, data });
this.fireChanged();
return true;
}
@@ -189,18 +153,16 @@ export class BoardsDataStore implements FrontendApplicationContribution {
protected async setData({
fqbn,
data,
version,
}: {
fqbn: string;
data: BoardsDataStore.Data;
version: Installable.Version;
}): Promise<void> {
const key = this.getStorageKey(fqbn, version);
const key = this.getStorageKey(fqbn);
return this.storageService.setData(key, data);
}
protected getStorageKey(fqbn: string, version: Installable.Version): string {
return `.arduinoIDE-configOptions-${version}-${fqbn}`;
protected getStorageKey(fqbn: string): string {
return `.arduinoIDE-configOptions-${fqbn}`;
}
protected async getBoardDetailsSafe(
@@ -231,21 +193,6 @@ export class BoardsDataStore implements FrontendApplicationContribution {
protected fireChanged(): void {
this.onChangedEmitter.fire();
}
protected async getBoardsPackageVersion(
fqbn: string | undefined
): Promise<Installable.Version | undefined> {
if (!fqbn) {
return undefined;
}
const boardsPackage = await this.boardsService.getContainerBoardPackage({
fqbn,
});
if (!boardsPackage) {
return undefined;
}
return boardsPackage.installedVersion;
}
}
export namespace BoardsDataStore {

View File

@@ -185,8 +185,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
const selectedAvailableBoard = AvailableBoard.is(selectedBoard)
? selectedBoard
: this._availableBoards.find((availableBoard) =>
Board.sameAs(availableBoard, selectedBoard)
);
Board.sameAs(availableBoard, selectedBoard)
);
if (
selectedAvailableBoard &&
selectedAvailableBoard.selected &&
@@ -230,7 +230,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
)) {
if (
this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn &&
this.latestValidBoardsConfig.selectedBoard.name === board.name
this.latestValidBoardsConfig.selectedBoard.name === board.name &&
this.latestValidBoardsConfig.selectedPort.protocol === board.port?.protocol
) {
this.boardsConfig = {
...this.latestValidBoardsConfig,
@@ -244,7 +245,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
}
set boardsConfig(config: BoardsConfig.Config) {
this.doSetBoardsConfig(config);
this.setBoardsConfig(config);
this.saveState().finally(() =>
this.reconcileAvailableBoards().finally(() =>
this.onBoardsConfigChangedEmitter.fire(this._boardsConfig)
@@ -256,7 +257,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
return this._boardsConfig;
}
protected doSetBoardsConfig(config: BoardsConfig.Config): void {
protected setBoardsConfig(config: BoardsConfig.Config): void {
this.logger.info('Board config changed: ', JSON.stringify(config));
this._boardsConfig = config;
this.latestBoardsConfig = this._boardsConfig;
@@ -370,19 +371,19 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
const find = (needle: Board & { port: Port }, haystack: AvailableBoard[]) =>
haystack.find(
(board) =>
Board.equals(needle, board) && Port.equals(needle.port, board.port)
Board.equals(needle, board) && Port.sameAs(needle.port, board.port)
);
const timeoutTask =
!!timeout && timeout > 0
? new Promise<void>((_, reject) =>
setTimeout(
() => reject(new Error(`Timeout after ${timeout} ms.`)),
timeout
)
setTimeout(
() => reject(new Error(`Timeout after ${timeout} ms.`)),
timeout
)
)
: new Promise<void>(() => {
/* never */
});
/* never */
});
const waitUntilTask = new Promise<void>((resolve) => {
let candidate = find(what, this.availableBoards);
if (candidate) {
@@ -409,7 +410,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
Port.sameAs(port, this.boardsConfig.selectedPort)
)
) {
this.doSetBoardsConfig({
this.setBoardsConfig({
selectedBoard: this.boardsConfig.selectedBoard,
selectedPort: undefined,
});
@@ -533,9 +534,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
protected getLastSelectedBoardOnPortKey(port: Port | string): string {
// TODO: we lose the port's `protocol` info (`serial`, `network`, etc.) here if the `port` is a `string`.
return `last-selected-board-on-port:${
typeof port === 'string' ? port : Port.toString(port)
}`;
return `last-selected-board-on-port:${typeof port === 'string' ? port : port.address
}`;
}
protected async loadState(): Promise<void> {

View File

@@ -0,0 +1,28 @@
import * as React from 'react';
export type ProgressBarProps = {
percent?: number;
showPercentage?: boolean;
};
export default function ProgressBar({
percent = 0,
showPercentage = false,
}: ProgressBarProps): React.ReactElement {
const roundedPercent = Math.round(percent);
return (
<div className="progress-bar">
<div className="progress-bar--outer">
<div
className="progress-bar--inner"
style={{ width: `${roundedPercent}%` }}
/>
</div>
{showPercentage && (
<div className="progress-bar--percentage">
<div className="progress-bar--percentage-text">{roundedPercent}%</div>
</div>
)}
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { inject, injectable } from 'inversify';
import * as moment from 'moment';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { isOSX, isWindows } from '@theia/core/lib/common/os';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';

View File

@@ -1,5 +1,5 @@
import { inject, injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { ArduinoMenus } from '../menu/arduino-menus';
import {
SketchContribution,

View File

@@ -1,5 +1,5 @@
import { inject, injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import URI from '@theia/core/lib/common/uri';
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';

View File

@@ -1,5 +1,5 @@
import { injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import * as dateFormat from 'dateformat';
import URI from '@theia/core/lib/common/uri';
import { ArduinoMenus } from '../menu/arduino-menus';

View File

@@ -1,5 +1,5 @@
import { inject, injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
import {
DisposableCollection,
@@ -204,10 +204,9 @@ PID: ${PID}`;
const packageLabel =
packageName +
`${
manuallyInstalled
? nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)')
: ''
`${manuallyInstalled
? nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)')
: ''
}`;
// Platform submenu
const platformMenuPath = [...boardsPackagesGroup, packageId];
@@ -255,8 +254,8 @@ PID: ${PID}`;
protocolOrder: number,
ports: AvailablePorts
) => {
const addresses = Object.keys(ports);
if (!addresses.length) {
const portIDs = Object.keys(ports);
if (!portIDs.length) {
return;
}
@@ -279,27 +278,26 @@ PID: ${PID}`;
// First we show addresses with recognized boards connected,
// then all the rest.
const sortedAddresses = Object.keys(ports);
sortedAddresses.sort((left: string, right: string): number => {
const sortedIDs = Object.keys(ports).sort((left: string, right: string): number => {
const [, leftBoards] = ports[left];
const [, rightBoards] = ports[right];
return rightBoards.length - leftBoards.length;
});
for (let i = 0; i < sortedAddresses.length; i++) {
const address = sortedAddresses[i];
const [port, boards] = ports[address];
let label = `${address}`;
for (let i = 0; i < sortedIDs.length; i++) {
const portID = sortedIDs[i];
const [port, boards] = ports[portID];
let label = `${port.address}`;
if (boards.length) {
const boardsList = boards.map((board) => board.name).join(', ');
label = `${label} (${boardsList})`;
}
const id = `arduino-select-port--${address}`;
const id = `arduino-select-port--${portID}`;
const command = { id };
const handler = {
execute: () => {
if (
!Port.equals(
!Port.sameAs(
port,
this.boardsServiceProvider.boardsConfig.selectedPort
)
@@ -312,7 +310,7 @@ PID: ${PID}`;
}
},
isToggled: () =>
Port.equals(
Port.sameAs(
port,
this.boardsServiceProvider.boardsConfig.selectedPort
),

View File

@@ -48,7 +48,6 @@ export class BurnBootloader extends SketchContribution {
}
async burnBootloader(): Promise<void> {
await this.serialConnection.disconnect();
try {
const { boardsConfig } = this.boardsServiceClientImpl;
const port = boardsConfig.selectedPort;
@@ -79,11 +78,15 @@ export class BurnBootloader extends SketchContribution {
}
);
} catch (e) {
this.messageService.error(e.toString());
} finally {
if (this.serialConnection.isSerialOpen()) {
await this.serialConnection.connect();
let errorMessage = "";
if (typeof e === "string") {
errorMessage = e;
} else {
errorMessage = e.toString();
}
this.messageService.error(errorMessage);
} finally {
await this.serialConnection.reconnectAfterUpload();
}
}
}

View File

@@ -1,6 +1,6 @@
import { inject, injectable } from 'inversify';
import { toArray } from '@phosphor/algorithm';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';

View File

@@ -13,6 +13,7 @@ import {
KeybindingRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
import { IDEUpdaterCommands } from '../ide-updater/ide-updater-commands';
@injectable()
export class Help extends Contribution {
@@ -115,6 +116,10 @@ export class Help extends Contribution {
commandId: Help.Commands.VISIT_ARDUINO.id,
order: '6',
});
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
commandId: IDEUpdaterCommands.CHECK_FOR_UPDATES.id,
order: '7',
});
}
registerKeybindings(registry: KeybindingRegistry): void {

View File

@@ -1,5 +1,5 @@
import { injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import URI from '@theia/core/lib/common/uri';
import { ArduinoMenus } from '../menu/arduino-menus';
import {

View File

@@ -1,5 +1,5 @@
import { inject, injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { MaybePromise } from '@theia/core/lib/common/types';
import { Widget, ContextMenuRenderer } from '@theia/core/lib/browser';
import {
@@ -190,7 +190,7 @@ export class OpenSketch extends SketchContribution {
],
message: nls.localize(
'arduino/sketch/movingMsg',
'The file "{0}" needs to be inside a sketch folder named as "{1}".\nCreate this folder, move the file, and continue?',
'The file "{0}" needs to be inside a sketch folder named "{1}".\nCreate this folder, move the file, and continue?',
nameWithExt,
name
),

View File

@@ -1,5 +1,5 @@
import { injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { isOSX } from '@theia/core/lib/common/os';
import {
Contribution,

View File

@@ -1,5 +1,5 @@
import { injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import * as dateFormat from 'dateformat';
import { ArduinoMenus } from '../menu/arduino-menus';
import {

View File

@@ -45,7 +45,7 @@ export namespace UploadFirmware {
id: 'arduino-upload-firmware-open',
label: nls.localize(
'arduino/firmware/updater',
'WiFi101 / WiFiNINA Firmware Updater'
'WiFi101 / WiFiNINA Firmware Updater'
),
category: 'Arduino',
};

View File

@@ -63,7 +63,9 @@ export class UploadSketch extends SketchContribution {
if (!fqbn) {
return '';
}
const address = boardsConfig.selectedBoard?.port?.address;
const address =
boardsConfig.selectedBoard?.port?.address ||
boardsConfig.selectedPort?.address;
if (!address) {
return '';
}
@@ -210,7 +212,7 @@ export class UploadSketch extends SketchContribution {
if (!sketch) {
return;
}
await this.serialConnection.disconnect();
try {
const { boardsConfig } = this.boardsServiceClientImpl;
const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] =
@@ -277,32 +279,18 @@ export class UploadSketch extends SketchContribution {
{ timeout: 3000 }
);
} catch (e) {
this.messageService.error(e.toString());
let errorMessage = '';
if (typeof e === 'string') {
errorMessage = e;
} else {
errorMessage = e.toString();
}
this.messageService.error(errorMessage);
} finally {
this.uploadInProgress = false;
this.onDidChangeEmitter.fire();
if (
this.serialConnection.isSerialOpen() &&
this.serialConnection.serialConfig
) {
const { board, port } = this.serialConnection.serialConfig;
try {
await this.boardsServiceClientImpl.waitUntilAvailable(
Object.assign(board, { port }),
10_000
);
await this.serialConnection.connect();
} catch (waitError) {
this.messageService.error(
nls.localize(
'arduino/sketch/couldNotConnectToSerial',
'Could not reconnect to serial port. {0}',
waitError.toString()
)
);
}
}
setTimeout(() => this.serialConnection.reconnectAfterUpload(), 5000);
}
}
}

View File

@@ -127,7 +127,13 @@ export class VerifySketch extends SketchContribution {
{ timeout: 3000 }
);
} catch (e) {
this.messageService.error(e.toString());
let errorMessage = "";
if (typeof e === "string") {
errorMessage = e;
} else {
errorMessage = e.toString();
}
this.messageService.error(errorMessage);
} finally {
this.verifyInProgress = false;
this.onDidChangeEmitter.fire();

View File

@@ -15,6 +15,47 @@ export namespace ResponseResultProvider {
export const JSON: ResponseResultProvider = (response) => response.json();
}
export function Utf8ArrayToStr(array: Uint8Array): string {
let out, i, c;
let char2, char3;
out = '';
const len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12:
case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
);
break;
}
}
return out;
}
type ResourceType = 'f' | 'd';
@injectable()
@@ -275,9 +316,7 @@ export class CreateApi {
// parse the secret file
const secrets = (
typeof content === 'string'
? content
: new TextDecoder().decode(content)
typeof content === 'string' ? content : Utf8ArrayToStr(content)
)
.split(/\r?\n/)
.reduce((prev, curr) => {
@@ -341,7 +380,7 @@ export class CreateApi {
const headers = await this.headers();
let data: string =
typeof content === 'string' ? content : new TextDecoder().decode(content);
typeof content === 'string' ? content : Utf8ArrayToStr(content);
data = await this.toggleSecretsInclude(posixPath, data, 'remove');
const payload = { data: btoa(data) };

View File

@@ -201,7 +201,7 @@ export const FirmwareUploaderComponent = ({
<i className="fa fa-info status-icon" />
{nls.localize(
'arduino/firmware/successfullyInstalled',
'Firmware succesfully installed.'
'Firmware successfully installed.'
)}
</div>
)}

View File

@@ -0,0 +1,210 @@
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { nls } from '@theia/core/lib/common';
import { shell } from 'electron';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import ReactMarkdown from 'react-markdown';
import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
import ProgressBar from '../../components/ProgressBar';
export type IDEUpdaterComponentProps = {
updateInfo: UpdateInfo;
windowService: WindowService;
downloadFinished?: boolean;
downloadStarted?: boolean;
progress?: ProgressInfo;
error?: Error;
onDownload: () => void;
onClose: () => void;
onSkipVersion: () => void;
onCloseAndInstall: () => void;
};
export const IDEUpdaterComponent = ({
updateInfo: { version, releaseNotes },
downloadStarted = false,
downloadFinished = false,
windowService,
progress,
error,
onDownload,
onClose,
onSkipVersion,
onCloseAndInstall,
}: IDEUpdaterComponentProps): React.ReactElement => {
const changelogDivRef = React.useRef() as React.MutableRefObject<
HTMLDivElement
>;
React.useEffect(() => {
if (!!releaseNotes) {
let changelog: string;
if (typeof releaseNotes === 'string') changelog = releaseNotes;
else
changelog = releaseNotes.reduce((acc, item) => {
return item.note ? (acc += `${item.note}\n\n`) : acc;
}, '');
ReactDOM.render(
<ReactMarkdown
components={{
a: ({ href, children, ...props }) => (
<a onClick={() => href && shell.openExternal(href)} {...props}>
{children}
</a>
),
}}
>
{changelog}
</ReactMarkdown>,
changelogDivRef.current
);
}
}, [releaseNotes]);
const closeButton = (
<button onClick={onClose} type="button" className="theia-button secondary">
{nls.localize('arduino/ide-updater/notNowButton', 'Not now')}
</button>
);
const DownloadCompleted: () => React.ReactElement = () => (
<div className="ide-updater-dialog--downloaded">
<div>
{nls.localize(
'arduino/ide-updater/versionDownloaded',
'Arduino IDE {0} has been downloaded.',
version
)}
</div>
<div>
{nls.localize(
'arduino/ide-updater/closeToInstallNotice',
'Close the software and install the update on your machine.'
)}
</div>
<div className="buttons-container">
{closeButton}
<button
onClick={onCloseAndInstall}
type="button"
className="theia-button close-and-install"
>
{nls.localize(
'arduino/ide-updater/closeAndInstallButton',
'Close and Install'
)}
</button>
</div>
</div>
);
const DownloadStarted: () => React.ReactElement = () => (
<div className="ide-updater-dialog--downloading">
<div>
{nls.localize(
'arduino/ide-updater/downloadingNotice',
'Downloading the latest version of the Arduino IDE.'
)}
</div>
<ProgressBar percent={progress?.percent} showPercentage />
</div>
);
const PreDownload: () => React.ReactElement = () => (
<div className="ide-updater-dialog--pre-download">
<div className="ide-updater-dialog--logo-container">
<div className="ide-updater-dialog--logo"></div>
</div>
<div className="ide-updater-dialog--new-version-text dialogSection">
<div className="dialogRow">
<div className="bold">
{nls.localize(
'arduino/ide-updater/updateAvailable',
'Update Available'
)}
</div>
</div>
<div className="dialogRow">
{nls.localize(
'arduino/ide-updater/newVersionAvailable',
'A new version of Arduino IDE ({0}) is available for download.',
version
)}
</div>
{releaseNotes && (
<div className="dialogRow">
<div className="changelog-container" ref={changelogDivRef} />
</div>
)}
<div className="buttons-container">
<button
onClick={onSkipVersion}
type="button"
className="theia-button secondary skip-version"
>
{nls.localize(
'arduino/ide-updater/skipVersionButton',
'Skip Version'
)}
</button>
<div className="push"></div>
{closeButton}
<button
onClick={onDownload}
type="button"
className="theia-button primary"
>
{nls.localize('arduino/ide-updater/downloadButton', 'Download')}
</button>
</div>
</div>
</div>
);
const onGoToDownloadClick = (
event: React.SyntheticEvent<HTMLAnchorElement, Event>
) => {
const { target } = event.nativeEvent;
if (target instanceof HTMLAnchorElement) {
event.nativeEvent.preventDefault();
windowService.openNewWindow(target.href, { external: true });
onClose();
}
};
const GoToDownloadPage: () => React.ReactElement = () => (
<div className="ide-updater-dialog--go-to-download-page">
<div>
{nls.localize(
'arduino/ide-updater/goToDownloadPage',
"An update for the Arduino IDE is available, but we're not able to download and install it automatically. Please go to the download page and download the latest version from there."
)}
</div>
<div className="buttons-container">
{closeButton}
<a
className="theia-button primary"
href="https://www.arduino.cc/en/software#experimental-software"
onClick={onGoToDownloadClick}
>
{nls.localize(
'arduino/ide-updater/goToDownloadButton',
'Go To Download'
)}
</a>
</div>
</div>
);
return (
<div className="ide-updater-dialog--content">
{!!error ? (
<GoToDownloadPage />
) : downloadFinished ? (
<DownloadCompleted />
) : downloadStarted ? (
<DownloadStarted />
) : (
<PreDownload />
)}
</div>
);
};

View File

@@ -0,0 +1,173 @@
import * as React from 'react';
import { inject, injectable } from 'inversify';
import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../../theia/dialogs/dialogs';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { nls } from '@theia/core';
import { IDEUpdaterComponent } from './ide-updater-component';
import {
IDEUpdater,
IDEUpdaterClient,
ProgressInfo,
UpdateInfo,
} from '../../../common/protocol/ide-updater';
import { LocalStorageService } from '@theia/core/lib/browser';
import { SKIP_IDE_VERSION } from '../../arduino-frontend-contribution';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
@injectable()
export class IDEUpdaterDialogWidget extends ReactWidget {
protected isOpen = new Object();
updateInfo: UpdateInfo;
progressInfo: ProgressInfo | undefined;
error: Error | undefined;
downloadFinished: boolean;
downloadStarted: boolean;
onClose: () => void;
@inject(IDEUpdater)
protected readonly updater: IDEUpdater;
@inject(IDEUpdaterClient)
protected readonly updaterClient: IDEUpdaterClient;
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
@inject(WindowService)
protected windowService: WindowService;
init(updateInfo: UpdateInfo, onClose: () => void): void {
this.updateInfo = updateInfo;
this.progressInfo = undefined;
this.error = undefined;
this.downloadStarted = false;
this.downloadFinished = false;
this.onClose = onClose;
this.updaterClient.onError((e) => {
this.error = e;
this.update();
});
this.updaterClient.onDownloadProgressChanged((e) => {
this.progressInfo = e;
this.update();
});
this.updaterClient.onDownloadFinished((e) => {
this.downloadFinished = true;
this.update();
});
}
async onSkipVersion(): Promise<void> {
this.localStorageService.setData<string>(
SKIP_IDE_VERSION,
this.updateInfo.version
);
this.close();
}
close(): void {
super.close();
this.onClose();
}
onDispose(): void {
if (this.downloadStarted && !this.downloadFinished)
this.updater.stopDownload();
}
async onDownload(): Promise<void> {
this.progressInfo = undefined;
this.downloadStarted = true;
this.error = undefined;
this.updater.downloadUpdate();
this.update();
}
onCloseAndInstall(): void {
this.updater.quitAndInstall();
}
protected render(): React.ReactNode {
return !!this.updateInfo ? (
<form>
<IDEUpdaterComponent
updateInfo={this.updateInfo}
windowService={this.windowService}
downloadStarted={this.downloadStarted}
downloadFinished={this.downloadFinished}
progress={this.progressInfo}
error={this.error}
onClose={this.close.bind(this)}
onSkipVersion={this.onSkipVersion.bind(this)}
onDownload={this.onDownload.bind(this)}
onCloseAndInstall={this.onCloseAndInstall.bind(this)}
/>
</form>
) : null;
}
}
@injectable()
export class IDEUpdaterDialogProps extends DialogProps {}
@injectable()
export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
@inject(IDEUpdaterDialogWidget)
protected readonly widget: IDEUpdaterDialogWidget;
constructor(
@inject(IDEUpdaterDialogProps)
protected readonly props: IDEUpdaterDialogProps
) {
super({
title: nls.localize(
'arduino/ide-updater/ideUpdaterDialog',
'Software Update'
),
});
this.contentNode.classList.add('ide-updater-dialog');
this.acceptButton = undefined;
}
get value(): UpdateInfo {
return this.widget.updateInfo;
}
protected onAfterAttach(msg: Message): void {
if (this.widget.isAttached) {
Widget.detach(this.widget);
}
Widget.attach(this.widget, this.contentNode);
super.onAfterAttach(msg);
this.update();
}
async open(
data: UpdateInfo | undefined = undefined
): Promise<UpdateInfo | undefined> {
if (data && data.version) {
this.widget.init(data, this.close.bind(this));
return super.open();
}
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.widget.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.widget.activate();
}
close(): void {
this.widget.dispose();
super.close();
}
}

View File

@@ -260,18 +260,6 @@ export class SettingsComponent extends React.Component<
'Verify code after upload'
)}
</label>
<label className="flex-line">
<input
type="checkbox"
checked={this.state.checkForUpdates}
onChange={this.checkForUpdatesDidChange}
disabled={true}
/>
{nls.localize(
'arduino/preferences/checkForUpdates',
'Check for updates on startup'
)}
</label>
<label className="flex-line">
<input
type="checkbox"
@@ -444,7 +432,9 @@ export class SettingsComponent extends React.Component<
);
}
protected noopKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
protected noopKeyDown = (
event: React.KeyboardEvent<HTMLInputElement>
): void => {
if (this.isControlKey(event)) {
return;
}
@@ -454,7 +444,7 @@ export class SettingsComponent extends React.Component<
protected numbersOnlyKeyDown = (
event: React.KeyboardEvent<HTMLInputElement>
) => {
): void => {
if (this.isControlKey(event)) {
return;
}
@@ -466,7 +456,7 @@ export class SettingsComponent extends React.Component<
}
};
protected browseSketchbookDidClick = async () => {
protected browseSketchbookDidClick = async (): Promise<void> => {
const uri = await this.props.fileDialogService.showOpenDialog({
title: nls.localize(
'arduino/preferences/newSketchbookLocation',
@@ -483,7 +473,7 @@ export class SettingsComponent extends React.Component<
}
};
protected editAdditionalUrlDidClick = async () => {
protected editAdditionalUrlDidClick = async (): Promise<void> => {
const additionalUrls = await new AdditionalUrlsDialog(
this.state.additionalUrls,
this.props.windowService
@@ -495,7 +485,7 @@ export class SettingsComponent extends React.Component<
protected editorFontSizeDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
const { value } = event.target;
if (value) {
this.setState({ editorFontSize: parseInt(value, 10) });
@@ -504,7 +494,7 @@ export class SettingsComponent extends React.Component<
protected additionalUrlsDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({
additionalUrls: event.target.value.split(',').map((url) => url.trim()),
});
@@ -512,13 +502,13 @@ export class SettingsComponent extends React.Component<
protected autoScaleInterfaceDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ autoScaleInterface: event.target.checked });
};
protected interfaceScaleDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
const { value } = event.target;
const percentage = parseInt(value, 10);
if (isNaN(percentage)) {
@@ -532,31 +522,25 @@ export class SettingsComponent extends React.Component<
protected verifyAfterUploadDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ verifyAfterUpload: event.target.checked });
};
protected checkForUpdatesDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
this.setState({ checkForUpdates: event.target.checked });
};
protected sketchbookShowAllFilesDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ sketchbookShowAllFiles: event.target.checked });
};
protected autoSaveDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ autoSave: event.target.checked ? 'on' : 'off' });
};
protected quickSuggestionsOtherDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
// need to persist react events through lifecycle https://reactjs.org/docs/events.html#event-pooling
const newVal = event.target.checked ? true : false;
@@ -570,7 +554,9 @@ export class SettingsComponent extends React.Component<
});
};
protected themeDidChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
protected themeDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
): void => {
const { selectedIndex } = event.target.options;
const theme = ThemeService.get().getThemes()[selectedIndex];
if (theme) {
@@ -580,14 +566,14 @@ export class SettingsComponent extends React.Component<
protected languageDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
): void => {
const selectedLanguage = event.target.value;
this.setState({ currentLanguage: selectedLanguage });
};
protected compilerWarningsDidChange = (
event: React.ChangeEvent<HTMLSelectElement>
) => {
): void => {
const { selectedIndex } = event.target.options;
const compilerWarnings = CompilerWarningLiterals[selectedIndex];
if (compilerWarnings) {
@@ -597,26 +583,28 @@ export class SettingsComponent extends React.Component<
protected verboseOnCompileDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ verboseOnCompile: event.target.checked });
};
protected verboseOnUploadDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
this.setState({ verboseOnUpload: event.target.checked });
};
protected sketchpathDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
const sketchbookPath = event.target.value;
if (sketchbookPath) {
this.setState({ sketchbookPath });
}
};
protected noProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
protected noProxyDidChange = (
event: React.ChangeEvent<HTMLInputElement>
): void => {
if (event.target.checked) {
this.setState({ network: 'none' });
} else {
@@ -626,7 +614,7 @@ export class SettingsComponent extends React.Component<
protected manualProxyDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (event.target.checked) {
this.setState({ network: Network.Default() });
} else {
@@ -636,7 +624,7 @@ export class SettingsComponent extends React.Component<
protected httpProtocolDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.protocol = event.target.checked ? 'http' : 'socks';
@@ -646,7 +634,7 @@ export class SettingsComponent extends React.Component<
protected socksProtocolDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.protocol = event.target.checked ? 'socks' : 'http';
@@ -656,7 +644,7 @@ export class SettingsComponent extends React.Component<
protected hostnameDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.hostname = event.target.value;
@@ -664,7 +652,9 @@ export class SettingsComponent extends React.Component<
}
};
protected portDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
protected portDidChange = (
event: React.ChangeEvent<HTMLInputElement>
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.port = event.target.value;
@@ -674,7 +664,7 @@ export class SettingsComponent extends React.Component<
protected usernameDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.username = event.target.value;
@@ -684,7 +674,7 @@ export class SettingsComponent extends React.Component<
protected passwordDidChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
): void => {
if (this.state.network !== 'none') {
const network = this.cloneProxySettings;
network.password = event.target.value;

View File

@@ -1,7 +1,7 @@
import { injectable, inject, postConstruct } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { Emitter } from '@theia/core/lib/common/event';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { Deferred, timeout } from '@theia/core/lib/common/promise-util';
import { deepClone } from '@theia/core/lib/common/objects';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { ThemeService } from '@theia/core/lib/browser/theming';
@@ -18,24 +18,22 @@ import {
import { nls } from '@theia/core/lib/common';
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization';
const EDITOR_SETTING = 'editor';
const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`;
const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
const ARDUINO_SETTING = 'arduino';
const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
// const IDE_SETTING = `${ARDUINO_SETTING}.ide`;
const COMPILE_SETTING = `${ARDUINO_SETTING}.compile`;
const UPLOAD_SETTING = `${ARDUINO_SETTING}.upload`;
const SKETCHBOOK_SETTING = `${ARDUINO_SETTING}.sketchbook`;
const AUTO_SCALE_SETTING = `${WINDOW_SETTING}.autoScale`;
const ZOOM_LEVEL_SETTING = `${WINDOW_SETTING}.zoomLevel`;
// const AUTO_UPDATE_SETTING = `${IDE_SETTING}.autoUpdate`;
const COMPILE_VERBOSE_SETTING = `${COMPILE_SETTING}.verbose`;
const COMPILE_WARNINGS_SETTING = `${COMPILE_SETTING}.warnings`;
const UPLOAD_VERBOSE_SETTING = `${UPLOAD_SETTING}.verbose`;
const UPLOAD_VERIFY_SETTING = `${UPLOAD_SETTING}.verify`;
const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
export const EDITOR_SETTING = 'editor';
export const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
export const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`;
export const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
export const ARDUINO_SETTING = 'arduino';
export const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
export const COMPILE_SETTING = `${ARDUINO_SETTING}.compile`;
export const UPLOAD_SETTING = `${ARDUINO_SETTING}.upload`;
export const SKETCHBOOK_SETTING = `${ARDUINO_SETTING}.sketchbook`;
export const AUTO_SCALE_SETTING = `${WINDOW_SETTING}.autoScale`;
export const ZOOM_LEVEL_SETTING = `${WINDOW_SETTING}.zoomLevel`;
export const COMPILE_VERBOSE_SETTING = `${COMPILE_SETTING}.verbose`;
export const COMPILE_WARNINGS_SETTING = `${COMPILE_SETTING}.warnings`;
export const UPLOAD_VERBOSE_SETTING = `${UPLOAD_SETTING}.verbose`;
export const UPLOAD_VERIFY_SETTING = `${UPLOAD_SETTING}.verify`;
export const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
export interface Settings extends Index {
editorFontSize: number; // `editor.fontSize`
@@ -48,7 +46,6 @@ export interface Settings extends Index {
autoScaleInterface: boolean; // `arduino.window.autoScale`
interfaceScale: number; // `arduino.window.zoomLevel` https://github.com/eclipse-theia/theia/issues/8751
checkForUpdates?: boolean; // `arduino.ide.autoUpdate`
verboseOnCompile: boolean; // `arduino.compile.verbose`
compilerWarnings: CompilerWarnings; // `arduino.compile.warnings`
verboseOnUpload: boolean; // `arduino.upload.verbose`
@@ -93,7 +90,6 @@ export class SettingsService {
@postConstruct()
protected async init(): Promise<void> {
await this.appStateService.reachedState('ready'); // Hack for https://github.com/eclipse-theia/theia/issues/8993
const settings = await this.loadSettings();
this._settings = deepClone(settings);
this.ready.resolve();
@@ -110,7 +106,6 @@ export class SettingsService {
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
@@ -135,7 +130,6 @@ export class SettingsService {
}),
this.preferenceService.get<boolean>(AUTO_SCALE_SETTING, true),
this.preferenceService.get<number>(ZOOM_LEVEL_SETTING, 0),
// this.preferenceService.get<string>(AUTO_UPDATE_SETTING, true),
this.preferenceService.get<boolean>(COMPILE_VERBOSE_SETTING, true),
this.preferenceService.get<any>(COMPILE_WARNINGS_SETTING, 'None'),
this.preferenceService.get<boolean>(UPLOAD_VERBOSE_SETTING, true),
@@ -154,7 +148,6 @@ export class SettingsService {
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
@@ -224,6 +217,11 @@ export class SettingsService {
}
}
private async savePreference(name: string, value: unknown): Promise<void> {
await this.preferenceService.set(name, value, PreferenceScope.User);
await timeout(5);
}
async save(): Promise<string | true> {
await this.ready.promise;
const {
@@ -234,7 +232,6 @@ export class SettingsService {
quickSuggestions,
autoScaleInterface,
interfaceScale,
// checkForUpdates,
verboseOnCompile,
compilerWarnings,
verboseOnUpload,
@@ -253,70 +250,29 @@ export class SettingsService {
(config as any).network = network;
(config as any).locale = currentLanguage;
await Promise.all([
this.preferenceService.set(
'editor.fontSize',
editorFontSize,
PreferenceScope.User
),
this.preferenceService.set(
'workbench.colorTheme',
themeId,
PreferenceScope.User
),
this.preferenceService.set(
'editor.autoSave',
autoSave,
PreferenceScope.User
),
this.preferenceService.set(
'editor.quickSuggestions',
quickSuggestions,
PreferenceScope.User
),
this.preferenceService.set(
AUTO_SCALE_SETTING,
autoScaleInterface,
PreferenceScope.User
),
this.preferenceService.set(
ZOOM_LEVEL_SETTING,
interfaceScale,
PreferenceScope.User
),
// this.preferenceService.set(AUTO_UPDATE_SETTING, checkForUpdates, PreferenceScope.User),
this.preferenceService.set(
COMPILE_VERBOSE_SETTING,
verboseOnCompile,
PreferenceScope.User
),
this.preferenceService.set(
COMPILE_WARNINGS_SETTING,
compilerWarnings,
PreferenceScope.User
),
this.preferenceService.set(
UPLOAD_VERBOSE_SETTING,
verboseOnUpload,
PreferenceScope.User
),
this.preferenceService.set(
UPLOAD_VERIFY_SETTING,
verifyAfterUpload,
PreferenceScope.User
),
this.preferenceService.set(
SHOW_ALL_FILES_SETTING,
sketchbookShowAllFiles,
PreferenceScope.User
),
this.configService.setConfiguration(config),
]);
await this.savePreference('editor.fontSize', editorFontSize);
await this.savePreference('workbench.colorTheme', themeId);
await this.savePreference('editor.autoSave', autoSave);
await this.savePreference('editor.quickSuggestions', quickSuggestions);
await this.savePreference(AUTO_SCALE_SETTING, autoScaleInterface);
await this.savePreference(ZOOM_LEVEL_SETTING, interfaceScale);
await this.savePreference(ZOOM_LEVEL_SETTING, interfaceScale);
await this.savePreference(COMPILE_VERBOSE_SETTING, verboseOnCompile);
await this.savePreference(COMPILE_WARNINGS_SETTING, compilerWarnings);
await this.savePreference(UPLOAD_VERBOSE_SETTING, verboseOnUpload);
await this.savePreference(UPLOAD_VERIFY_SETTING, verifyAfterUpload);
await this.savePreference(SHOW_ALL_FILES_SETTING, sketchbookShowAllFiles);
await this.configService.setConfiguration(config);
this.onDidChangeEmitter.fire(this._settings);
// after saving all the settings, if we need to change the language we need to perform a reload
if (currentLanguage !== nls.locale) {
window.localStorage.setItem(nls.localeId, currentLanguage);
// Only reload if the language differs from the current locale. `nls.locale === undefined` signals english as well
if (currentLanguage !== nls.locale && !(currentLanguage === 'en' && nls.locale === undefined)) {
if (currentLanguage === 'en') {
window.localStorage.removeItem(nls.localeId);
} else {
window.localStorage.setItem(nls.localeId, currentLanguage);
}
window.location.reload();
}

View File

@@ -0,0 +1,40 @@
import { Emitter } from '@theia/core';
import { injectable } from '@theia/core/shared/inversify';
import { UpdateInfo, ProgressInfo } from 'electron-updater';
import { IDEUpdaterClient } from '../../common/protocol/ide-updater';
@injectable()
export class IDEUpdaterClientImpl implements IDEUpdaterClient {
protected readonly onErrorEmitter = new Emitter<Error>();
protected readonly onCheckingForUpdateEmitter = new Emitter<void>();
protected readonly onUpdateAvailableEmitter = new Emitter<UpdateInfo>();
protected readonly onUpdateNotAvailableEmitter = new Emitter<UpdateInfo>();
protected readonly onDownloadProgressEmitter = new Emitter<ProgressInfo>();
protected readonly onDownloadFinishedEmitter = new Emitter<UpdateInfo>();
readonly onError = this.onErrorEmitter.event;
readonly onCheckingForUpdate = this.onCheckingForUpdateEmitter.event;
readonly onUpdateAvailable = this.onUpdateAvailableEmitter.event;
readonly onUpdateNotAvailable = this.onUpdateNotAvailableEmitter.event;
readonly onDownloadProgressChanged = this.onDownloadProgressEmitter.event;
readonly onDownloadFinished = this.onDownloadFinishedEmitter.event;
notifyError(message: Error): void {
this.onErrorEmitter.fire(message);
}
notifyCheckingForUpdate(message: void): void {
this.onCheckingForUpdateEmitter.fire(message);
}
notifyUpdateAvailable(message: UpdateInfo): void {
this.onUpdateAvailableEmitter.fire(message);
}
notifyUpdateNotAvailable(message: UpdateInfo): void {
this.onUpdateNotAvailableEmitter.fire(message);
}
notifyDownloadProgressChanged(message: ProgressInfo): void {
this.onDownloadProgressEmitter.fire(message);
}
notifyDownloadFinished(message: UpdateInfo): void {
this.onDownloadFinishedEmitter.fire(message);
}
}

View File

@@ -0,0 +1,60 @@
import {
Command,
CommandContribution,
CommandRegistry,
MessageService,
nls,
} from '@theia/core';
import { injectable, inject } from 'inversify';
import { IDEUpdater, UpdateInfo } from '../../common/protocol/ide-updater';
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
@injectable()
export class IDEUpdaterCommands implements CommandContribution {
constructor(
@inject(IDEUpdater)
private readonly updater: IDEUpdater,
@inject(MessageService)
protected readonly messageService: MessageService,
@inject(IDEUpdaterDialog)
protected readonly updaterDialog: IDEUpdaterDialog
) {}
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(IDEUpdaterCommands.CHECK_FOR_UPDATES, {
execute: this.checkForUpdates.bind(this),
});
}
async checkForUpdates(initialCheck?: boolean): Promise<UpdateInfo | void> {
try {
const updateInfo = await this.updater.checkForUpdates(initialCheck);
if (!!updateInfo) {
this.updaterDialog.open(updateInfo);
} else {
this.messageService.info(
nls.localize(
'arduino/ide-updater/noUpdatesAvailable',
'There are no recent updates available for the Arduino IDE'
)
);
}
return updateInfo;
} catch (e) {
this.messageService.error(
nls.localize(
'arduino/ide-updater/errorCheckingForUpdates',
'Error while checking for Arduino IDE updates.\n{0}',
e.message
)
);
}
}
}
export namespace IDEUpdaterCommands {
export const CHECK_FOR_UPDATES: Command = {
id: 'arduino-ide-check-for-updates',
category: 'Arduino',
label: 'Check for Arduino IDE updates',
};
}

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { injectable, inject } from 'inversify';
import { AbstractViewContribution } from '@theia/core/lib/browser';
import { AbstractViewContribution, codicon } from '@theia/core/lib/browser';
import { MonitorWidget } from './monitor-widget';
import { MenuModelRegistry, Command, CommandRegistry } from '@theia/core';
import {
@@ -32,7 +32,7 @@ export namespace SerialMonitor {
{
id: 'serial-monitor-clear-output',
label: 'Clear Output',
iconClass: 'clear-all',
iconClass: codicon('clear-all'),
},
'vscode/output.contribution/clearOutput.label'
);

View File

@@ -12,7 +12,7 @@ import {
import { SerialConfig } from '../../../common/protocol/serial-service';
import { ArduinoSelect } from '../../widgets/arduino-select';
import { SerialModel } from '../serial-model';
import { Serial, SerialConnectionManager } from '../serial-connection-manager';
import { SerialConnectionManager } from '../serial-connection-manager';
import { SerialMonitorSendInput } from './serial-monitor-send-input';
import { SerialMonitorOutput } from './serial-monitor-send-output';
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
@@ -57,9 +57,7 @@ export class MonitorWidget extends ReactWidget {
this.scrollOptions = undefined;
this.toDispose.push(this.clearOutputEmitter);
this.toDispose.push(
Disposable.create(() =>
this.serialConnection.closeSerial(Serial.Type.Monitor)
)
Disposable.create(() => this.serialConnection.closeWStoBE())
);
}
@@ -83,7 +81,7 @@ export class MonitorWidget extends ReactWidget {
protected onAfterAttach(msg: Message): void {
super.onAfterAttach(msg);
this.serialConnection.openSerial(Serial.Type.Monitor);
this.serialConnection.openWSToBE();
}
onCloseRequest(msg: Message): void {
@@ -171,7 +169,7 @@ export class MonitorWidget extends ReactWidget {
<div className="head">
<div className="send">
<SerialMonitorSendInput
serialConfig={this.serialConnection.serialConfig}
serialConnection={this.serialConnection}
resolveFocus={this.onFocusResolved}
onSend={this.onSend}
/>

View File

@@ -1,18 +1,20 @@
import * as React from 'react';
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
import { Board, Port } from '../../../common/protocol/boards-service';
import { SerialConfig } from '../../../common/protocol/serial-service';
import { Board } from '../../../common/protocol/boards-service';
import { isOSX } from '@theia/core/lib/common/os';
import { nls } from '@theia/core/lib/common';
import { DisposableCollection, nls } from '@theia/core/lib/common';
import { SerialConnectionManager } from '../serial-connection-manager';
import { SerialPlotter } from '../plotter/protocol';
export namespace SerialMonitorSendInput {
export interface Props {
readonly serialConfig?: SerialConfig;
readonly serialConnection: SerialConnectionManager;
readonly onSend: (text: string) => void;
readonly resolveFocus: (element: HTMLElement | undefined) => void;
}
export interface State {
text: string;
connected: boolean;
}
}
@@ -20,20 +22,45 @@ export class SerialMonitorSendInput extends React.Component<
SerialMonitorSendInput.Props,
SerialMonitorSendInput.State
> {
protected toDisposeBeforeUnmount = new DisposableCollection();
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
super(props);
this.state = { text: '' };
this.state = { text: '', connected: false };
this.onChange = this.onChange.bind(this);
this.onSend = this.onSend.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}
componentDidMount(): void {
this.props.serialConnection.isBESerialConnected().then((connected) => {
this.setState({ connected });
});
this.toDisposeBeforeUnmount.pushAll([
this.props.serialConnection.onRead(({ messages }) => {
if (
messages.command ===
SerialPlotter.Protocol.Command.MIDDLEWARE_CONFIG_CHANGED &&
'connected' in messages.data
) {
this.setState({ connected: messages.data.connected });
}
}),
]);
}
componentWillUnmount(): void {
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
this.toDisposeBeforeUnmount.dispose();
}
render(): React.ReactNode {
return (
<input
ref={this.setRef}
type="text"
className={`theia-input ${this.props.serialConfig ? '' : 'warning'}`}
className={`theia-input ${this.state.connected ? '' : 'warning'}`}
placeholder={this.placeholder}
value={this.state.text}
onChange={this.onChange}
@@ -43,8 +70,8 @@ export class SerialMonitorSendInput extends React.Component<
}
protected get placeholder(): string {
const { serialConfig } = this.props;
if (!serialConfig) {
const serialConfig = this.props.serialConnection.getConfig();
if (!this.state.connected || !serialConfig) {
return nls.localize(
'arduino/serial/notConnected',
'Not connected. Select a board and a port to connect automatically.'
@@ -53,12 +80,14 @@ export class SerialMonitorSendInput extends React.Component<
const { board, port } = serialConfig;
return nls.localize(
'arduino/serial/message',
"Message ({0} + Enter to send message to '{1}' on '{2}'",
"Message ({0} + Enter to send message to '{1}' on '{2}')",
isOSX ? '⌘' : nls.localize('vscode/keybindingLabels/ctrlKey', 'Ctrl'),
Board.toString(board, {
useFqbn: false,
}),
Port.toString(port)
board
? Board.toString(board, {
useFqbn: false,
})
: 'unknown',
port ? port.address : 'unknown'
);
}

View File

@@ -43,6 +43,7 @@ export class SerialMonitorOutput extends React.Component<
itemCount={this.state.lines.length}
itemSize={18}
width={'100%'}
style={{ whiteSpace: 'nowrap' }}
ref={this.listRef}
>
{Row}

View File

@@ -10,9 +10,9 @@ import { SerialModel } from '../serial-model';
import { ArduinoMenus } from '../../menu/arduino-menus';
import { Contribution } from '../../contributions/contribution';
import { Endpoint, FrontendApplication } from '@theia/core/lib/browser';
import { ipcRenderer } from '@theia/core/shared/electron';
import { SerialConfig, Status } from '../../../common/protocol';
import { Serial, SerialConnectionManager } from '../serial-connection-manager';
import { ipcRenderer } from '@theia/electron/shared/electron';
import { SerialConfig } from '../../../common/protocol';
import { SerialConnectionManager } from '../serial-connection-manager';
import { SerialPlotter } from './protocol';
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
const queryString = require('query-string');
@@ -51,10 +51,8 @@ export class PlotterFrontendContribution extends Contribution {
ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => {
if (!!this.window) {
this.window = null;
await this.serialConnection.closeSerial(Serial.Type.Plotter);
}
});
return super.onStart(app);
}
@@ -77,17 +75,15 @@ export class PlotterFrontendContribution extends Contribution {
this.window.focus();
return;
}
const status = await this.serialConnection.openSerial(Serial.Type.Plotter);
const wsPort = this.serialConnection.getWsPort();
if (Status.isOK(status) && wsPort) {
if (wsPort) {
this.open(wsPort);
} else {
this.serialConnection.closeSerial(Serial.Type.Plotter);
this.messageService.error(`Couldn't open serial plotter`);
}
}
protected open(wsPort: number): void {
protected async open(wsPort: number): Promise<void> {
const initConfig: Partial<SerialPlotter.Config> = {
baudrates: SerialConfig.BaudRates.map((b) => b),
currentBaudrate: this.model.baudRate,
@@ -95,7 +91,7 @@ export class PlotterFrontendContribution extends Contribution {
darkTheme: this.themeService.getCurrentTheme().type === 'dark',
wsPort,
interpolate: this.model.interpolate,
connected: this.serialConnection.connected,
connected: await this.serialConnection.isBESerialConnected(),
serialPort: this.boardsServiceProvider.boardsConfig.selectedPort?.address,
};
const urlWithParams = queryString.stringifyUrl(

View File

@@ -1,5 +1,4 @@
import { injectable, inject } from 'inversify';
import { deepClone } from '@theia/core/lib/common/objects';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { MessageService } from '@theia/core/lib/common/message-service';
import {
@@ -11,7 +10,6 @@ import {
} from '../../common/protocol/serial-service';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import {
Port,
Board,
BoardsService,
} from '../../common/protocol/boards-service';
@@ -23,8 +21,6 @@ import { nls } from '@theia/core/lib/common/nls';
@injectable()
export class SerialConnectionManager {
protected _state: Serial.State = [];
protected _connected = false;
protected config: Partial<SerialConfig> = {
board: undefined,
port: undefined,
@@ -62,7 +58,9 @@ export class SerialConnectionManager {
protected readonly boardsServiceProvider: BoardsServiceProvider,
@inject(MessageService) protected messageService: MessageService,
@inject(ThemeService) protected readonly themeService: ThemeService,
@inject(CoreService) protected readonly core: CoreService
@inject(CoreService) protected readonly core: CoreService,
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider
) {
this.serialServiceClient.onWebSocketChanged(
this.handleWebSocketChanged.bind(this)
@@ -89,8 +87,11 @@ export class SerialConnectionManager {
);
// Handles the `baudRate` changes by reconnecting if required.
this.serialModel.onChange(({ property }) => {
if (property === 'baudRate' && this.connected) {
this.serialModel.onChange(async ({ property }) => {
if (
property === 'baudRate' &&
(await this.serialService.isSerialPortOpen())
) {
const { boardsConfig } = this.boardsServiceProvider;
this.handleBoardConfigChange(boardsConfig);
}
@@ -114,8 +115,8 @@ export class SerialConnectionManager {
}
/**
* Set the config passing only the properties that has changed. If some has changed and the serial is open,
* we try to reconnect
* Updated the config in the BE passing only the properties that has changed.
* BE will create a new connection if needed.
*
* @param newConfig the porperties of the config that has changed
*/
@@ -127,17 +128,16 @@ export class SerialConnectionManager {
this.config = { ...this.config, [key]: newConfig[key] };
}
});
if (
configHasChanged &&
this.isSerialOpen() &&
!(await this.core.isUploading())
) {
if (configHasChanged) {
this.serialService.updateWsConfigParam({
currentBaudrate: this.config.baudRate,
serialPort: this.config.port?.address,
});
await this.disconnect();
await this.connect();
if (isSerialConfig(this.config)) {
this.serialService.setSerialConfig(this.config);
}
}
}
@@ -149,134 +149,56 @@ export class SerialConnectionManager {
return this.wsPort;
}
isWebSocketConnected(): boolean {
return !!this.webSocket?.url;
}
protected handleWebSocketChanged(wsPort: number): void {
this.wsPort = wsPort;
}
/**
* When the serial is open and the frontend is connected to the serial, we create the websocket here
*/
protected createWsConnection(): boolean {
if (this.wsPort) {
try {
this.webSocket = new WebSocket(`ws://localhost:${this.wsPort}`);
this.webSocket.onmessage = (res) => {
const messages = JSON.parse(res.data);
this.onReadEmitter.fire({ messages });
};
return true;
} catch {
return false;
}
}
return false;
}
/**
* Sets the types of connections needed by the client.
*
* @param newState The array containing the list of desired connections.
* If the previuos state was empty and 'newState' is not, it tries to reconnect to the serial service
* If the provios state was NOT empty and now it is, it disconnects to the serial service
* @returns The status of the operation
*/
protected async setState(newState: Serial.State): Promise<Status> {
const oldState = deepClone(this._state);
let status = Status.OK;
if (this.isSerialOpen(oldState) && !this.isSerialOpen(newState)) {
status = await this.disconnect();
} else if (!this.isSerialOpen(oldState) && this.isSerialOpen(newState)) {
if (await this.core.isUploading()) {
this.messageService.error(`Cannot open serial port when uploading`);
return Status.NOT_CONNECTED;
}
status = await this.connect();
}
this._state = newState;
return status;
}
protected get state(): Serial.State {
return this._state;
}
isSerialOpen(state?: Serial.State): boolean {
return (state ? state : this._state).length > 0;
}
get serialConfig(): SerialConfig | undefined {
return isSerialConfig(this.config)
? (this.config as SerialConfig)
: undefined;
}
get connected(): boolean {
return this._connected;
async isBESerialConnected(): Promise<boolean> {
return await this.serialService.isSerialPortOpen();
}
set connected(c: boolean) {
this._connected = c;
this.serialService.updateWsConfigParam({ connected: c });
this.onConnectionChangedEmitter.fire(this._connected);
}
/**
* Called when a client opens the serial from the GUI
*
* @param type could be either 'Monitor' or 'Plotter'. If it's 'Monitor' we also connect to the websocket and
* listen to the message events
* @returns the status of the operation
*/
async openSerial(type: Serial.Type): Promise<Status> {
openWSToBE(): void {
if (!isSerialConfig(this.config)) {
this.messageService.error(
`Please select a board and a port to open the serial connection.`
);
return Status.NOT_CONNECTED;
}
if (this.state.includes(type)) return Status.OK;
const newState = deepClone(this.state);
newState.push(type);
const status = await this.setState(newState);
if (Status.isOK(status) && type === Serial.Type.Monitor)
this.createWsConnection();
return status;
}
/**
* Called when a client closes the serial from the GUI
*
* @param type could be either 'Monitor' or 'Plotter'. If it's 'Monitor' we close the websocket connection
* @returns the status of the operation
*/
async closeSerial(type: Serial.Type): Promise<Status> {
const index = this.state.indexOf(type);
let status = Status.OK;
if (index >= 0) {
const newState = deepClone(this.state);
newState.splice(index, 1);
status = await this.setState(newState);
if (
Status.isOK(status) &&
type === Serial.Type.Monitor &&
this.webSocket
) {
this.webSocket.close();
this.webSocket = undefined;
if (!this.webSocket && this.wsPort) {
try {
this.webSocket = new WebSocket(`ws://localhost:${this.wsPort}`);
this.webSocket.onmessage = (res) => {
const messages = JSON.parse(res.data);
this.onReadEmitter.fire({ messages });
};
} catch {
this.messageService.error(`Unable to connect to websocket`);
}
}
}
closeWStoBE(): void {
if (this.webSocket) {
try {
this.webSocket.close();
this.webSocket = undefined;
} catch {
this.messageService.error(`Unable to close websocket`);
}
}
return status;
}
/**
* Handles error on the SerialServiceClient and try to reconnect, eventually
*/
handleError(error: SerialError): void {
if (!this.connected) return;
async handleError(error: SerialError): Promise<void> {
if (!(await this.serialService.isSerialPortOpen())) return;
const { code, config } = error;
const { board, port } = config;
const options = { timeout: 3000 };
@@ -294,7 +216,7 @@ export class SerialConnectionManager {
nls.localize(
'arduino/serial/connectionBusy',
'Connection failed. Serial port is busy: {0}',
Port.toString(port)
port.address
),
options
);
@@ -309,7 +231,7 @@ export class SerialConnectionManager {
Board.toString(board, {
useFqbn: false,
}),
Port.toString(port)
port.address
),
options
);
@@ -321,7 +243,7 @@ export class SerialConnectionManager {
'arduino/serial/unexpectedError',
'Unexpected error. Reconnecting {0} on port {1}.',
Board.toString(board),
Port.toString(port)
port.address
),
options
);
@@ -329,9 +251,8 @@ export class SerialConnectionManager {
break;
}
}
this.connected = false;
if (this.isSerialOpen()) {
if ((await this.serialService.clientsAttached()) > 0) {
if (this.serialErrors.length >= 10) {
this.messageService.warn(
nls.localize(
@@ -340,7 +261,7 @@ export class SerialConnectionManager {
Board.toString(board, {
useFqbn: false,
}),
Port.toString(port)
port.address
)
);
this.serialErrors.length = 0;
@@ -354,68 +275,40 @@ export class SerialConnectionManager {
this.messageService.warn(
nls.localize(
'arduino/serial/reconnect',
'Reconnecting {0} to {1} in {2] seconds...',
'Reconnecting {0} to {1} in {2} seconds...',
Board.toString(board, {
useFqbn: false,
}),
Port.toString(port),
port.address,
attempts.toString()
)
);
this.reconnectTimeout = window.setTimeout(
() => this.connect(),
() => this.reconnectAfterUpload(),
timeout
);
}
}
}
async connect(): Promise<Status> {
if (this.connected) return Status.ALREADY_CONNECTED;
if (!isSerialConfig(this.config)) return Status.NOT_CONNECTED;
console.info(
`>>> Creating serial connection for ${Board.toString(
this.config.board
)} on port ${Port.toString(this.config.port)}...`
);
const connectStatus = await this.serialService.connect(this.config);
if (Status.isOK(connectStatus)) {
this.connected = true;
console.info(
`<<< Serial connection created for ${Board.toString(this.config.board, {
useFqbn: false,
})} on port ${Port.toString(this.config.port)}.`
async reconnectAfterUpload(): Promise<void> {
try {
if (isSerialConfig(this.config)) {
await this.boardsServiceClientImpl.waitUntilAvailable(
Object.assign(this.config.board, { port: this.config.port }),
10_000
);
this.serialService.connectSerialIfRequired();
}
} catch (waitError) {
this.messageService.error(
nls.localize(
'arduino/sketch/couldNotConnectToSerial',
'Could not reconnect to serial port. {0}',
waitError.toString()
)
);
}
return Status.isOK(connectStatus);
}
async disconnect(): Promise<Status> {
if (!this.connected) {
return Status.OK;
}
console.log('>>> Disposing existing serial connection...');
const status = await this.serialService.disconnect();
if (Status.isOK(status)) {
this.connected = false;
console.log(
`<<< Disposed serial connection. Was: ${Serial.Config.toString(
this.config
)}`
);
this.wsPort = undefined;
} else {
console.warn(
`<<< Could not dispose serial connection. Activate connection: ${Serial.Config.toString(
this.config
)}`
);
}
return status;
}
/**
@@ -424,7 +317,7 @@ export class SerialConnectionManager {
* It is a NOOP if connected.
*/
async send(data: string): Promise<Status> {
if (!this.connected) {
if (!(await this.serialService.isSerialPortOpen())) {
return Status.NOT_CONNECTED;
}
return new Promise<Status>((resolve) => {
@@ -438,7 +331,7 @@ export class SerialConnectionManager {
return this.onConnectionChangedEmitter.event;
}
get onRead(): Event<{ messages: string[] }> {
get onRead(): Event<{ messages: any }> {
return this.onReadEmitter.event;
}
@@ -453,23 +346,11 @@ export class SerialConnectionManager {
}
export namespace Serial {
export enum Type {
Monitor = 'Monitor',
Plotter = 'Plotter',
}
/**
* The state represents which types of connections are needed by the client, and it should match whether the Serial Monitor
* or the Serial Plotter are open or not in the GUI. It's an array cause it's possible to have both, none or only one of
* them open
*/
export type State = Serial.Type[];
export namespace Config {
export function toString(config: Partial<SerialConfig>): string {
if (!isSerialConfig(config)) return '';
const { board, port } = config;
return `${Board.toString(board)} ${Port.toString(port)}`;
return `${Board.toString(board)} ${port.address}`;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,78 @@
.ide-updater-dialog {
width: 546px;
}
.ide-updater-dialog .bold {
font-weight: bold;
}
.ide-updater-dialog--pre-download {
display: flex;
}
.ide-updater-dialog--logo-container {
margin-right: 28px;
}
.ide-updater-dialog--logo {
background: url('./ide-logo.png') round;
width: 52px;
height: 52px;
}
.dialogContent.ide-updater-dialog
.ide-updater-dialog--content
.ide-updater-dialog--new-version-text.dialogSection {
margin-top: 0;
}
.ide-updater-dialog .changelog-container {
background: white;
border: 1px solid #dae3e3;
border-radius: 2px;
font-size: 12px;
height: 180px;
overflow: auto;
padding: 0 12px;
cursor: text;
}
.ide-updater-dialog .changelog-container a {
color: #018184;
}
.ide-updater-dialog .changelog-container a:hover {
text-decoration: underline;
cursor: pointer;
}
.ide-updater-dialog .changelog-container code {
background: #ecf1f1;
border-radius: 2px;
padding: 0 2px;
}
.ide-updater-dialog .changelog-container a code {
color: #018184;
}
.ide-updater-dialog .buttons-container {
display: flex;
justify-content: flex-end;
margin-top: 28px;
}
.ide-updater-dialog .buttons-container a.theia-button {
text-decoration: none;
display: flex;
align-items: center;
justify-content: center;
}
.ide-updater-dialog .buttons-container a.theia-button:hover {
color: var(--theia-button-foreground);
}
.ide-updater-dialog .buttons-container .push {
margin-right: auto;
}

View File

@@ -9,6 +9,7 @@
@import './editor.css';
@import './settings-dialog.css';
@import './firmware-uploader-dialog.css';
@import './ide-updater-dialog.css';
@import './certificate-uploader-dialog.css';
@import './user-fields-dialog.css';
@import './debug.css';
@@ -16,6 +17,7 @@
@import './cloud-sketchbook.css';
@import './fonts.css';
@import './custom-codicon.css';
@import './progress-bar.css';
.theia-input.warning:focus {
outline-width: 1px;

View File

@@ -0,0 +1,32 @@
.progress-bar {
margin-top: 20px;
}
.progress-bar--outer {
background: #e5e5e5;
border-radius: 11px;
height: 6px;
position: relative;
overflow: hidden;
}
.progress-bar--inner {
transition: width 1s;
height: 100%;
background: #008184;
border-radius: 11px;
}
.progress-bar--percentage {
align-items: flex-end;
display: flex;
height: 40px;
justify-content: center;
margin-top: 10px;
width: 100%;
}
.progress-bar--percentage-text {
font-size: 14px;
line-height: 24px;
}

View File

@@ -62,9 +62,15 @@ export class DebugSessionManager extends TheiaDebugSessionManager {
}
);
}
// TODO: remove as https://github.com/eclipse-theia/theia/issues/10164 is fixed
async terminateSessions(): Promise<void> {
await super.terminateSessions();
this.destroy(this.currentSession?.id);
async terminateSession(session?: DebugSession): Promise<void> {
if (!session) {
this.updateCurrentSession(this._currentSession);
session = this._currentSession;
}
// The cortex-debug extension does not respond to close requests
// So we simply terminate the debug session immediately
// Alternatively the `super.terminateSession` call will terminate it after 5 seconds without a response
await this.debug.terminateDebugSession(session!.id);
await super.terminateSession(session);
}
}

View File

@@ -2,6 +2,7 @@ import * as React from 'react';
import { NotificationComponent } from './notification-component';
import { NotificationCenterComponent as TheiaNotificationCenterComponent } from '@theia/messages/lib/browser/notification-center-component';
import { nls } from '@theia/core/lib/common';
import { codicon } from '@theia/core/lib/browser';
const PerfectScrollbar = require('react-perfect-scrollbar');
@@ -28,7 +29,7 @@ export class NotificationCenterComponent extends TheiaNotificationCenterComponen
<div className="theia-notification-center-header-actions">
<ul className="theia-notification-actions">
<li
className="collapse"
className={codicon('chevron-down', true)}
title={nls.localize(
'vscode/notificationsStatus/hideNotifications',
'Hide Notification Center'
@@ -36,7 +37,7 @@ export class NotificationCenterComponent extends TheiaNotificationCenterComponen
onClick={this.onHide}
/>
<li
className="clear-all"
className={codicon('clear-all', true)}
title={nls.localize(
'vscode/notificationsCommands/clearAllNotifications',
'Clear All'

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { NotificationComponent as TheiaNotificationComponent } from '@theia/messages/lib/browser/notification-component';
import { nls } from '@theia/core/lib/common';
import { codicon } from '@theia/core/lib/browser';
export class NotificationComponent extends TheiaNotificationComponent {
render(): React.ReactNode {
@@ -15,7 +16,7 @@ export class NotificationComponent extends TheiaNotificationComponent {
>
<div className="theia-notification-list-item-content-main">
<div
className={`theia-notification-icon theia-notification-icon-${type}`}
className={`theia-notification-icon ${codicon(type)} ${type}`}
/>
<div className="theia-notification-message">
<span
@@ -26,7 +27,11 @@ export class NotificationComponent extends TheiaNotificationComponent {
<ul className="theia-notification-actions">
{expandable && (
<li
className={collapsed ? 'expand' : 'collapse'}
className={
codicon('chevron-down') + collapsed
? ' expand'
: ' collapse'
}
title={
collapsed
? nls.localize('theia/messages/expand', 'Expand')
@@ -38,7 +43,7 @@ export class NotificationComponent extends TheiaNotificationComponent {
)}
{!this.isProgress && (
<li
className="clear"
className={codicon('close', true)}
title={nls.localize('vscode/abstractTree/clear', 'Clear')}
data-message-id={messageId}
onClick={this.onClear}

View File

@@ -1,5 +1,5 @@
import { inject, injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import URI from '@theia/core/lib/common/uri';
import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler';
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';

View File

@@ -1,3 +1,4 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { injectable, inject } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { EditorWidget } from '@theia/editor/lib/browser';
@@ -18,6 +19,7 @@ import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver';
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { BoardsConfig } from '../../boards/boards-config';
import { nls } from '@theia/core/lib/common';
import { URI as VSCodeUri } from '@theia/core/shared/vscode-uri';
@injectable()
export class WorkspaceService extends TheiaWorkspaceService {
@@ -67,7 +69,7 @@ export class WorkspaceService extends TheiaWorkspaceService {
this.workspaceUri = (async () => {
try {
const hash = window.location.hash;
const [recentWorkspaces, recentSketches] = await Promise.all([
const [recentWorkspacesPaths, recentSketches] = await Promise.all([
this.server.getRecentWorkspaces(),
this.sketchService
.getSketches({})
@@ -75,6 +77,10 @@ export class WorkspaceService extends TheiaWorkspaceService {
SketchContainer.toArray(container).map((s) => s.uri)
),
]);
// On Dindows, `getRecentWorkspaces` returns only file paths, not URIs as expected by the `isValid` method.
const recentWorkspaces = recentWorkspacesPaths.map((e) =>
VSCodeUri.file(e).toString()
);
const toOpen = await new ArduinoWorkspaceRootResolver({
isValid: this.isValid.bind(this),
}).resolve({ hash, recentWorkspaces, recentSketches });
@@ -124,6 +130,8 @@ export class WorkspaceService extends TheiaWorkspaceService {
}: FocusTracker.IChangedArgs<Widget>): void {
if (newValue instanceof EditorWidget) {
const { uri } = newValue.editor;
const currentWindow = remote.getCurrentWindow();
currentWindow.setRepresentedFilename(uri.path.toString());
if (Sketch.isSketchFile(uri.toString())) {
this.updateTitle();
} else {

View File

@@ -1,4 +1,4 @@
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { inject, injectable } from 'inversify';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
@@ -80,7 +80,7 @@ export class SketchbookWidgetContribution
}
onStart(): void {
this.shell.currentChanged.connect(() =>
this.shell.onDidChangeCurrentWidget(() =>
this.onCurrentWidgetChangedHandler()
);

View File

@@ -2,4 +2,5 @@ export const ArduinoDaemonPath = '/services/arduino-daemon';
export const ArduinoDaemon = Symbol('ArduinoDaemon');
export interface ArduinoDaemon {
isRunning(): Promise<boolean>;
getPort(): Promise<string>;
}

View File

@@ -7,13 +7,13 @@ export type AvailablePorts = Record<string, [Port, Array<Board>]>;
export namespace AvailablePorts {
export function byProtocol(availablePorts: AvailablePorts): Map<string, AvailablePorts> {
const grouped = new Map<string, AvailablePorts>();
for (const address of Object.keys(availablePorts)) {
const [port, boards] = availablePorts[address];
for (const portID of Object.keys(availablePorts)) {
const [port, boards] = availablePorts[portID];
let ports = grouped.get(port.protocol);
if (!ports) {
ports = {} as AvailablePorts;
}
ports[address] = [port, boards];
ports[portID] = [port, boards];
grouped.set(port.protocol, ports);
}
return grouped;
@@ -43,7 +43,7 @@ export namespace AttachedBoardsChangeEvent {
const visitedDetachedPorts: Port[] = [];
for (const board of attached.boards) {
const port = board.port
? ` on ${Port.toString(board.port, { useLabel: true })}`
? ` on ${Port.toString(board.port)}`
: '';
rows.push(` - Attached board: ${Board.toString(board)}${port}`);
if (board.port) {
@@ -52,7 +52,7 @@ export namespace AttachedBoardsChangeEvent {
}
for (const board of detached.boards) {
const port = board.port
? ` from ${Port.toString(board.port, { useLabel: true })}`
? ` from ${Port.toString(board.port)}`
: '';
rows.push(` - Detached board: ${Board.toString(board)}${port}`);
if (board.port) {
@@ -62,18 +62,14 @@ export namespace AttachedBoardsChangeEvent {
for (const port of attached.ports) {
if (!visitedAttachedPorts.find((p) => Port.sameAs(port, p))) {
rows.push(
` - New port is available on ${Port.toString(port, {
useLabel: true,
})}`
` - New port is available on ${Port.toString(port)}`
);
}
}
for (const port of detached.ports) {
if (!visitedDetachedPorts.find((p) => Port.sameAs(port, p))) {
rows.push(
` - Port is no longer available on ${Port.toString(port, {
useLabel: true,
})}`
` - Port is no longer available on ${Port.toString(port)}`
);
}
}
@@ -147,12 +143,14 @@ export interface BoardsService
}
export interface Port {
// id is the combination of address and protocol
// formatted like "<address>|<protocol>" used
// to univocally recognize a port
readonly id: string;
readonly address: string;
readonly addressLabel: string;
readonly protocol: string;
/**
* Optional label for the protocol. For example: `Serial Port (USB)`.
*/
readonly label?: string;
readonly protocolLabel: string;
}
export namespace Port {
export function is(arg: any): arg is Port {
@@ -165,14 +163,8 @@ export namespace Port {
);
}
export function toString(
port: Port,
options: { useLabel: boolean } = { useLabel: false }
): string {
if (options.useLabel && port.label) {
return `${port.address} ${port.label}`;
}
return port.address;
export function toString(port: Port): string {
return `${port.addressLabel} ${port.protocolLabel}`;
}
export function compare(left: Port, right: Port): number {
@@ -192,29 +184,12 @@ export namespace Port {
return naturalCompare(left.address!, right.address!);
}
export function equals(
export function sameAs(
left: Port | undefined,
right: Port | undefined
): boolean {
if (left && right) {
return (
left.address === right.address &&
left.protocol === right.protocol &&
(left.label || '') === (right.label || '')
);
}
return left === right;
}
export function sameAs(
left: Port | undefined,
right: Port | string | undefined
) {
if (left && right) {
if (typeof right === 'string') {
return left.address === right;
}
return left.address === right.address;
return left.address === right.address && left.protocol === right.protocol;
}
return false;
}

View File

@@ -0,0 +1,71 @@
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { Event } from '@theia/core/lib/common/event';
import { UpdateChannel } from '../../browser/arduino-preferences';
export interface ProgressInfo {
total: number;
delta: number;
transferred: number;
percent: number;
bytesPerSecond: number;
}
export interface ReleaseNoteInfo {
readonly version: string;
readonly note: string | null;
}
export interface BlockMapDataHolder {
size?: number;
blockMapSize?: number;
readonly sha512: string;
readonly isAdminRightsRequired?: boolean;
}
export interface UpdateFileInfo extends BlockMapDataHolder {
url: string;
}
export type UpdateInfo = {
readonly version: string;
readonly files: Array<UpdateFileInfo>;
releaseName?: string | null;
releaseNotes?: string | Array<ReleaseNoteInfo> | null;
releaseDate: string;
readonly stagingPercentage?: number;
};
export interface ProgressInfo {
total: number;
delta: number;
transferred: number;
percent: number;
bytesPerSecond: number;
}
export const IDEUpdaterPath = '/services/ide-updater';
export const IDEUpdater = Symbol('IDEUpdater');
export interface IDEUpdater extends JsonRpcServer<IDEUpdaterClient> {
init(channel: UpdateChannel, baseUrl: string): Promise<void>;
checkForUpdates(initialCheck?: boolean): Promise<UpdateInfo | void>;
downloadUpdate(): Promise<void>;
quitAndInstall(): void;
stopDownload(): void;
disconnectClient(client: IDEUpdaterClient): void;
}
export const IDEUpdaterClient = Symbol('IDEUpdaterClient');
export interface IDEUpdaterClient {
onError: Event<Error>;
onCheckingForUpdate: Event<void>;
onUpdateAvailable: Event<UpdateInfo>;
onUpdateNotAvailable: Event<UpdateInfo>;
onDownloadProgressChanged: Event<ProgressInfo>;
onDownloadFinished: Event<UpdateInfo>;
notifyError(message: Error): void;
notifyCheckingForUpdate(message: void): void;
notifyUpdateAvailable(message: UpdateInfo): void;
notifyUpdateNotAvailable(message: UpdateInfo): void;
notifyDownloadProgressChanged(message: ProgressInfo): void;
notifyDownloadFinished(message: UpdateInfo): void;
}

View File

@@ -18,15 +18,22 @@ export namespace Status {
export const ALREADY_CONNECTED: ErrorStatus = {
message: 'Already connected.',
};
export const CONFIG_MISSING: ErrorStatus = {
message: 'Serial Config missing.',
};
}
export const SerialServicePath = '/services/serial';
export const SerialService = Symbol('SerialService');
export interface SerialService extends JsonRpcServer<SerialServiceClient> {
connect(config: SerialConfig): Promise<Status>;
disconnect(): Promise<Status>;
clientsAttached(): Promise<number>;
setSerialConfig(config: SerialConfig): Promise<void>;
sendMessageToSerial(message: string): Promise<Status>;
updateWsConfigParam(config: Partial<SerialPlotter.Config>): Promise<void>;
isSerialPortOpen(): Promise<boolean>;
connectSerialIfRequired(): Promise<void>;
disconnect(reason?: SerialError): Promise<Status>;
uploadInProgress: boolean;
}
export interface SerialConfig {

View File

@@ -1,5 +1,5 @@
import { inject, injectable, postConstruct } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import {
ConnectionStatus,

View File

@@ -1,5 +1,5 @@
import { injectable } from 'inversify';
import { remote } from 'electron';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { isOSX } from '@theia/core/lib/common/os';
import { Keybinding } from '@theia/core/lib/common/keybinding';
import {
@@ -15,7 +15,6 @@ import {
ArduinoMenus,
PlaceholderMenuNode,
} from '../../../browser/menu/arduino-menus';
import electron = require('@theia/core/shared/electron');
@injectable()
export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
@@ -35,9 +34,9 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
await this.preferencesService.ready;
const createdMenuBar = this.createElectronMenuBar();
if (isOSX) {
electron.remote.Menu.setApplicationMenu(createdMenuBar);
remote.Menu.setApplicationMenu(createdMenuBar);
} else {
electron.remote.getCurrentWindow().setMenu(createdMenuBar);
remote.getCurrentWindow().setMenu(createdMenuBar);
}
}
@@ -81,7 +80,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
protected createOSXMenu(): Electron.MenuItemConstructorOptions {
const { submenu } = super.createOSXMenu();
const label = 'Arduino IDE';
if (!!submenu && !(submenu instanceof remote.Menu)) {
if (!!submenu && Array.isArray(submenu)) {
const [, , /* about */ /* preferences */ ...rest] = submenu;
const about = this.fillMenuTemplate(
[],

View File

@@ -2,7 +2,10 @@ import { ContainerModule } from 'inversify';
import { JsonRpcConnectionHandler } from '@theia/core/lib/common/messaging/proxy-factory';
import { ElectronConnectionHandler } from '@theia/core/lib/electron-common/messaging/electron-connection-handler';
import { ElectronMainWindowService } from '@theia/core/lib/electron-common/electron-main-window-service';
import { ElectronMainApplication as TheiaElectronMainApplication } from '@theia/core/lib/electron-main/electron-main-application';
import {
ElectronMainApplication as TheiaElectronMainApplication,
ElectronMainApplicationContribution,
} from '@theia/core/lib/electron-main/electron-main-application';
import {
SplashService,
splashServicePath,
@@ -10,6 +13,12 @@ import {
import { SplashServiceImpl } from './splash/splash-service-impl';
import { ElectronMainApplication } from './theia/electron-main-application';
import { ElectronMainWindowServiceImpl } from './theia/electron-main-window-service';
import {
IDEUpdater,
IDEUpdaterClient,
IDEUpdaterPath,
} from '../common/protocol/ide-updater';
import { IDEUpdaterImpl } from './ide-updater/ide-updater-impl';
export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ElectronMainApplication).toSelf().inSingletonScope();
@@ -28,4 +37,23 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
)
)
.inSingletonScope();
// IDE updater bindings
bind(IDEUpdaterImpl).toSelf().inSingletonScope();
bind(IDEUpdater).toService(IDEUpdaterImpl);
bind(ElectronMainApplicationContribution).toService(IDEUpdater);
bind(ElectronConnectionHandler)
.toDynamicValue(
(context) =>
new JsonRpcConnectionHandler<IDEUpdaterClient>(
IDEUpdaterPath,
(client) => {
const server = context.container.get<IDEUpdater>(IDEUpdater);
server.setClient(client);
client.onDidCloseConnection(() => server.disconnectClient(client));
return server;
}
)
)
.inSingletonScope();
});

View File

@@ -0,0 +1,131 @@
import { injectable } from '@theia/core/shared/inversify';
import { UpdateInfo, CancellationToken, autoUpdater } from 'electron-updater';
import fetch, { Response } from 'node-fetch';
import { UpdateChannel } from '../../browser/arduino-preferences';
import {
IDEUpdater,
IDEUpdaterClient,
} from '../../common/protocol/ide-updater';
const CHANGELOG_BASE_URL = 'https://downloads.arduino.cc/arduino-ide/changelog';
@injectable()
export class IDEUpdaterImpl implements IDEUpdater {
private isAlreadyChecked = false;
private updater = autoUpdater;
private cancellationToken?: CancellationToken;
protected theiaFEClient?: IDEUpdaterClient;
protected clients: Array<IDEUpdaterClient> = [];
constructor() {
this.updater.on('checking-for-update', (e) => {
this.clients.forEach((c) => c.notifyCheckingForUpdate(e));
});
this.updater.on('update-available', (e) => {
this.clients.forEach((c) => c.notifyUpdateAvailable(e));
});
this.updater.on('update-not-available', (e) => {
this.clients.forEach((c) => c.notifyUpdateNotAvailable(e));
});
this.updater.on('download-progress', (e) => {
this.clients.forEach((c) => c.notifyDownloadProgressChanged(e));
});
this.updater.on('update-downloaded', (e) => {
this.clients.forEach((c) => c.notifyDownloadFinished(e));
});
this.updater.on('error', (e) => {
this.clients.forEach((c) => c.notifyError(e));
});
}
async init(channel: UpdateChannel, baseUrl: string): Promise<void> {
this.updater.autoDownload = false;
this.updater.channel = channel;
this.updater.setFeedURL({
provider: 'generic',
url: `${baseUrl}/${channel === UpdateChannel.Nightly ? 'nightly' : ''}`,
channel,
});
}
setClient(client: IDEUpdaterClient | undefined): void {
if (client) this.clients.push(client);
}
async checkForUpdates(initialCheck?: boolean): Promise<UpdateInfo | void> {
if (initialCheck) {
if (this.isAlreadyChecked) return Promise.resolve();
this.isAlreadyChecked = true;
}
const {
updateInfo,
cancellationToken,
} = await this.updater.checkForUpdates();
this.cancellationToken = cancellationToken;
if (this.updater.currentVersion.compare(updateInfo.version) === -1) {
/*
'latest.txt' points to the latest changelog that has been generated by the CI,
so we need to make a first GET request to get the filename of the changelog
and a second GET to the actual changelog file
*/
try {
let response: Response | null = await fetch(
`${CHANGELOG_BASE_URL}/latest.txt`
);
const latestChangelogFileName = response.ok
? await response.text()
: null;
response = latestChangelogFileName
? await fetch(`${CHANGELOG_BASE_URL}/${latestChangelogFileName}`)
: null;
const changelog = response?.ok ? await response?.text() : null;
// We only want to see the release notes of newer versions
const currentVersionIndex = changelog?.indexOf(
`\r\n\r\n---\r\n\r\n## ${this.updater.currentVersion}\r\n\r\n`
);
const newChangelog =
currentVersionIndex && currentVersionIndex > 0
? changelog?.slice(0, currentVersionIndex)
: changelog;
updateInfo.releaseNotes = newChangelog;
} catch {
/*
if the request for the changelog fails, we'll just avoid to show it
to the user, but we will still show the update info
*/
}
return updateInfo;
}
}
async downloadUpdate(): Promise<void> {
try {
await this.updater.downloadUpdate(this.cancellationToken);
} catch (e) {
if (e.message === 'cancelled') return;
this.clients.forEach((c) => c.notifyError(e));
}
}
stopDownload(): void {
this.cancellationToken?.cancel();
}
quitAndInstall(): void {
this.updater.quitAndInstall();
}
disconnectClient(client: IDEUpdaterClient): void {
const index = this.clients.indexOf(client);
if (index !== -1) {
this.clients.splice(index, 1);
}
}
dispose(): void {
this.clients.forEach(this.disconnectClient.bind(this));
}
}

View File

@@ -1,29 +1,44 @@
import { inject, injectable } from 'inversify';
import {
app,
BrowserWindow,
BrowserWindowConstructorOptions,
screen,
} from 'electron';
import { app, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, screen, Event as ElectronEvent } from '@theia/core/electron-shared/electron';
import { fork } from 'child_process';
import { AddressInfo } from 'net';
import { join } from 'path';
import { join, dirname } from 'path';
import * as fs from 'fs-extra';
import { initSplashScreen } from '../splash/splash-screen';
import { MaybePromise } from '@theia/core/lib/common/types';
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
import {
ElectronMainApplication as TheiaElectronMainApplication,
ElectronMainExecutionParams,
TheiaBrowserWindowOptions,
} from '@theia/core/lib/electron-main/electron-main-application';
import { SplashServiceImpl } from '../splash/splash-service-impl';
import { ipcMain } from '@theia/core/shared/electron';
import { URI } from '@theia/core/shared/vscode-uri';
import * as electronRemoteMain from '@theia/core/electron-shared/@electron/remote/main';
import { Deferred } from '@theia/core/lib/common/promise-util';
import * as os from '@theia/core/lib/common/os';
app.commandLine.appendSwitch('disable-http-cache');
interface WorkspaceOptions {
file: string
x: number
y: number
width: number
height: number
isMaximized: boolean
isFullScreen: boolean
time: number
}
const WORKSPACES = 'workspaces';
@injectable()
export class ElectronMainApplication extends TheiaElectronMainApplication {
protected _windows: BrowserWindow[] = [];
protected startup = false;
protected openFilePromise = new Deferred();
@inject(SplashServiceImpl)
protected readonly splashService: SplashServiceImpl;
@@ -33,9 +48,106 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
// See: https://github.com/electron-userland/electron-builder/issues/2468
// Regression in Theia: https://github.com/eclipse-theia/theia/issues/8701
app.on('ready', () => app.setName(config.applicationName));
this.attachFileAssociations();
return super.start(config);
}
attachFileAssociations() {
// OSX: register open-file event
if (os.isOSX) {
app.on('open-file', async (event, uri) => {
event.preventDefault();
if (uri.endsWith('.ino') && await fs.pathExists(uri)) {
this.openFilePromise.reject();
await this.openSketch(dirname(uri));
}
});
setTimeout(() => this.openFilePromise.resolve(), 500);
} else {
this.openFilePromise.resolve();
}
}
protected async isValidSketchPath(uri: string): Promise<boolean | undefined> {
return typeof uri === 'string' && await fs.pathExists(uri);
}
protected async launch(params: ElectronMainExecutionParams): Promise<void> {
try {
// When running on MacOS, we either have to wait until
// 1. The `open-file` command has been received by the app, rejecting the promise
// 2. A short timeout resolves the promise automatically, falling back to the usual app launch
await this.openFilePromise.promise;
} catch {
// Application has received the `open-file` event and will skip the default application launch
return;
}
if (!os.isOSX && await this.launchFromArgs(params)) {
// Application has received a file in its arguments and will skip the default application launch
return;
}
this.startup = true;
const workspaces: WorkspaceOptions[] | undefined = this.electronStore.get(WORKSPACES);
let useDefault = true;
if (workspaces && workspaces.length > 0) {
for (const workspace of workspaces) {
if (await this.isValidSketchPath(workspace.file)) {
useDefault = false;
await this.openSketch(workspace);
}
}
}
this.startup = false;
if (useDefault) {
super.launch(params);
}
}
protected async launchFromArgs(params: ElectronMainExecutionParams): Promise<boolean> {
// Copy to prevent manipulation of original array
const argCopy = [...params.argv];
let uri: string | undefined;
for (const possibleUri of argCopy) {
if (possibleUri.endsWith('.ino') && await this.isValidSketchPath(possibleUri)) {
uri = possibleUri;
break;
}
}
if (uri) {
await this.openSketch(dirname(uri));
return true;
}
return false;
}
protected async openSketch(workspace: WorkspaceOptions | string): Promise<BrowserWindow> {
const options = await this.getLastWindowOptions();
let file: string;
if (typeof workspace === 'object') {
options.x = workspace.x;
options.y = workspace.y;
options.width = workspace.width;
options.height = workspace.height;
options.isMaximized = workspace.isMaximized;
options.isFullScreen = workspace.isFullScreen;
file = workspace.file;
} else {
file = workspace;
}
const [uri, electronWindow] = await Promise.all([this.createWindowUri(), this.createWindow(options)]);
electronWindow.loadURL(uri.withFragment(encodeURI(file)).toString(true));
return electronWindow;
}
protected avoidOverlap(options: TheiaBrowserWindowOptions): TheiaBrowserWindowOptions {
if (this.startup) {
return options;
}
return super.avoidOverlap(options);
}
protected getTitleBarStyle(): 'native' | 'custom' {
return 'native';
}
@@ -50,6 +162,14 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
});
}
protected async onSecondInstance(event: ElectronEvent, argv: string[], cwd: string): Promise<void> {
if (!os.isOSX && await this.launchFromArgs({ cwd, argv, secondInstance: true })) {
// Application has received a file in its arguments
return;
}
super.onSecondInstance(event, argv, cwd);
}
/**
* Use this rather than creating `BrowserWindow` instances from scratch, since some security parameters need to be set, this method will do it.
*
@@ -108,7 +228,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
electronWindow.webContents.on(
'new-window',
(event, url, frameName, disposition, options, additionalFeatures) => {
(event, url, frameName, disposition, options) => {
if (frameName === 'serialPlotter') {
event.preventDefault();
Object.assign(options, {
@@ -148,10 +268,12 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
}
}
});
this.attachClosedWorkspace(electronWindow);
this.attachReadyToShow(electronWindow);
this.attachSaveWindowState(electronWindow);
this.attachGlobalShortcuts(electronWindow);
this.restoreMaximizedState(electronWindow, options);
electronRemoteMain.enable(electronWindow.webContents);
return electronWindow;
}
@@ -218,6 +340,44 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
}
}
protected closedWorkspaces: WorkspaceOptions[] = [];
protected attachClosedWorkspace(window: BrowserWindow): void {
// Since the `before-quit` event is only fired when closing the *last* window
// We need to keep track of recently closed windows/workspaces manually
window.on('close', () => {
const url = window.webContents.getURL();
const workspace = URI.parse(url).fragment;
if (workspace) {
const workspaceUri = URI.file(workspace);
const bounds = window.getNormalBounds();
this.closedWorkspaces.push({
...bounds,
isMaximized: window.isMaximized(),
isFullScreen: window.isFullScreen(),
file: workspaceUri.fsPath,
time: Date.now()
})
}
});
}
protected onWillQuit(event: Electron.Event): void {
// Only add workspaces which were closed within the last second (1000 milliseconds)
const threshold = Date.now() - 1000;
const visited = new Set<string>();
const workspaces = this.closedWorkspaces.filter(e => {
if (e.time < threshold || visited.has(e.file)) {
return false;
}
visited.add(e.file);
return true;
}).sort((a, b) => a.file.localeCompare(b.file));
this.electronStore.set(WORKSPACES, workspaces);
super.onWillQuit(event);
}
get windows(): BrowserWindow[] {
return this._windows.slice();
}

View File

@@ -42,6 +42,7 @@ export class ArduinoDaemonImpl
protected _running = false;
protected _ready = new Deferred<void>();
protected _execPath: string | undefined;
protected _port: string;
// Backend application lifecycle.
@@ -55,12 +56,17 @@ export class ArduinoDaemonImpl
return Promise.resolve(this._running);
}
async getPort(): Promise<string> {
return Promise.resolve(this._port);
}
async startDaemon(): Promise<void> {
try {
this.toDispose.dispose(); // This will `kill` the previously started daemon process, if any.
const cliPath = await this.getExecPath();
this.onData(`Starting daemon from ${cliPath}...`);
const daemon = await this.spawnDaemonProcess();
const { daemon, port } = await this.spawnDaemonProcess();
this._port = port;
// Watchdog process for terminating the daemon process when the backend app terminates.
spawn(
process.execPath,
@@ -148,6 +154,10 @@ export class ArduinoDaemonImpl
const cliConfigPath = join(FileUri.fsPath(configDirUri), CLI_CONFIG);
return [
'daemon',
'--format',
'jsonmini',
'--port',
'0',
'--config-file',
`"${cliConfigPath}"`,
'-v',
@@ -156,12 +166,15 @@ export class ArduinoDaemonImpl
];
}
protected async spawnDaemonProcess(): Promise<ChildProcess> {
protected async spawnDaemonProcess(): Promise<{
daemon: ChildProcess;
port: string;
}> {
const [cliPath, args] = await Promise.all([
this.getExecPath(),
this.getSpawnArgs(),
]);
const ready = new Deferred<ChildProcess>();
const ready = new Deferred<{ daemon: ChildProcess; port: string }>();
const options = { shell: true };
const daemon = spawn(`"${cliPath}"`, args, options);
@@ -171,20 +184,37 @@ export class ArduinoDaemonImpl
daemon.stdout.on('data', (data) => {
const message = data.toString();
let port = '';
let address = '';
message
.split('\n')
.filter((line: string) => line.length)
.forEach((line: string) => {
try {
const parsedLine = JSON.parse(line);
if ('Port' in parsedLine) {
port = parsedLine.Port;
}
if ('IP' in parsedLine) {
address = parsedLine.IP;
}
} catch (err) {
// ignore
}
});
this.onData(message);
if (!grpcServerIsReady) {
const error = DaemonError.parse(message);
if (error) {
ready.reject(error);
return;
}
for (const expected of [
'Daemon is listening on TCP port',
'Daemon is now listening on 127.0.0.1',
]) {
if (message.includes(expected)) {
grpcServerIsReady = true;
ready.resolve(daemon);
}
if (port.length && address.length) {
grpcServerIsReady = true;
ready.resolve({ daemon, port });
}
}
});

View File

@@ -4,6 +4,7 @@ import {
} from '../common/protocol/arduino-firmware-uploader';
import { injectable, inject, named } from 'inversify';
import { ExecutableService } from '../common/protocol';
import { SerialService } from '../common/protocol/serial-service';
import { getExecPath, spawnCommand } from './exec-util';
import { ILogger } from '@theia/core/lib/common/logger';
@@ -18,6 +19,9 @@ export class ArduinoFirmwareUploaderImpl implements ArduinoFirmwareUploader {
@named('fwuploader')
protected readonly logger: ILogger;
@inject(SerialService)
protected readonly serialService: SerialService;
protected onError(error: any): void {
this.logger.error(error);
}
@@ -66,15 +70,26 @@ export class ArduinoFirmwareUploaderImpl implements ArduinoFirmwareUploader {
}
async flash(firmware: FirmwareInfo, port: string): Promise<string> {
return await this.runCommand([
'firmware',
'flash',
'--fqbn',
firmware.board_fqbn,
'--address',
port,
'--module',
`${firmware.module}@${firmware.firmware_version}`,
]);
let output;
try {
this.serialService.uploadInProgress = true;
await this.serialService.disconnect();
output = await this.runCommand([
'firmware',
'flash',
'--fqbn',
firmware.board_fqbn,
'--address',
port,
'--module',
`${firmware.module}@${firmware.firmware_version}`,
]);
} catch (e) {
throw e;
} finally {
this.serialService.uploadInProgress = false;
this.serialService.connectSerialIfRequired();
return output;
}
}
}

View File

@@ -203,7 +203,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// #endregion Theia customizations
// Monitor client provider per connected frontend.
// Serial client provider per connected frontend.
bind(ConnectionContainerModule).toConstantValue(
ConnectionContainerModule.create(({ bind, bindBackendService }) => {
bind(MonitorClientProvider).toSelf().inSingletonScope();
@@ -260,17 +260,14 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
)
.inSingletonScope();
bind(ArduinoFirmwareUploaderImpl).toSelf().inSingletonScope();
bind(ArduinoFirmwareUploader).toService(ArduinoFirmwareUploaderImpl);
bind(BackendApplicationContribution).toService(ArduinoFirmwareUploaderImpl);
bind(ConnectionHandler)
.toDynamicValue(
(context) =>
new JsonRpcConnectionHandler(ArduinoFirmwareUploaderPath, () =>
context.container.get(ArduinoFirmwareUploader)
)
)
.inSingletonScope();
// Singleton per BE, each FE connection gets its proxy.
bind(ConnectionContainerModule).toConstantValue(
ConnectionContainerModule.create(({ bind, bindBackendService }) => {
bind(ArduinoFirmwareUploaderImpl).toSelf().inSingletonScope();
bind(ArduinoFirmwareUploader).toService(ArduinoFirmwareUploaderImpl);
bindBackendService(ArduinoFirmwareUploaderPath, ArduinoFirmwareUploader);
})
);
// Logger for the Arduino daemon
bind(ILogger)

View File

@@ -60,11 +60,29 @@ export class BoardDiscovery extends CoreClientAware {
this.startBoardListWatch(coreClient);
}
stopBoardListWatch(coreClient: CoreClientProvider.Client): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.boardWatchDuplex) {
return resolve();
}
const { instance } = coreClient;
const req = new BoardListWatchRequest();
req.setInstance(instance);
try {
this.boardWatchDuplex.write(req.setInterrupt(true), resolve);
} catch (e) {
this.discoveryLogger.error(e);
resolve();
}
});
}
startBoardListWatch(coreClient: CoreClientProvider.Client): void {
if (this.watching) {
// We want to avoid starting the board list watch process multiple
// times to meet unforseen consequences
return
return;
}
this.watching = true;
const { client, instance } = coreClient;
@@ -73,9 +91,19 @@ export class BoardDiscovery extends CoreClientAware {
this.boardWatchDuplex = client.boardListWatch();
this.boardWatchDuplex.on('end', () => {
this.watching = false;
console.info('board watch ended')
})
console.info('board watch ended');
});
this.boardWatchDuplex.on('close', () => {
this.watching = false;
console.info('board watch ended');
});
this.boardWatchDuplex.on('data', (resp: BoardListWatchResponse) => {
if (resp.getEventType() === 'quit') {
this.watching = false;
console.info('board watch ended');
return;
}
const detectedPort = resp.getPort();
if (detectedPort) {
let eventType: 'add' | 'remove' | 'unknown' = 'unknown';
@@ -96,8 +124,22 @@ export class BoardDiscovery extends CoreClientAware {
const address = (detectedPort as any).getPort().getAddress();
const protocol = (detectedPort as any).getPort().getProtocol();
const label = (detectedPort as any).getPort().getLabel();;
const port = { address, protocol, label };
// Different discoveries can detect the same port with different
// protocols, so we consider the combination of address and protocol
// to be the id of a certain port to distinguish it from others.
// If we'd use only the address of a port to store it in a map
// we can have conflicts the same port is found with multiple
// protocols.
const portID = `${address}|${protocol}`;
const label = (detectedPort as any).getPort().getLabel();
const protocolLabel = (detectedPort as any).getPort().getProtocolLabel();
const port = {
id: portID,
address,
addressLabel: label,
protocol,
protocolLabel,
};
const boards: Board[] = [];
for (const item of detectedPort.getMatchingBoardsList()) {
boards.push({
@@ -108,21 +150,21 @@ export class BoardDiscovery extends CoreClientAware {
}
if (eventType === 'add') {
if (newState[port.address]) {
const [, knownBoards] = newState[port.address];
if (newState[portID]) {
const [, knownBoards] = newState[portID];
console.warn(
`Port '${port.address}' was already available. Known boards before override: ${JSON.stringify(
`Port '${Port.toString(port)}' was already available. Known boards before override: ${JSON.stringify(
knownBoards
)}`
);
}
newState[port.address] = [port, boards];
newState[portID] = [port, boards];
} else if (eventType === 'remove') {
if (!newState[port.address]) {
console.warn(`Port '${port.address}' was not available. Skipping`);
if (!newState[portID]) {
console.warn(`Port '${Port.toString(port)}' was not available. Skipping`);
return;
}
delete newState[port.address];
delete newState[portID];
}
const oldAvailablePorts = this.getAvailablePorts(oldState);
@@ -149,8 +191,8 @@ export class BoardDiscovery extends CoreClientAware {
getAttachedBoards(state: AvailablePorts = this.state): Board[] {
const attachedBoards: Board[] = [];
for (const address of Object.keys(state)) {
const [, boards] = state[address];
for (const portID of Object.keys(state)) {
const [, boards] = state[portID];
attachedBoards.push(...boards);
}
return attachedBoards;
@@ -158,8 +200,8 @@ export class BoardDiscovery extends CoreClientAware {
getAvailablePorts(state: AvailablePorts = this.state): Port[] {
const availablePorts: Port[] = [];
for (const address of Object.keys(state)) {
const [port] = state[address];
for (const portID of Object.keys(state)) {
const [port] = state[portID];
availablePorts.push(port);
}
return availablePorts;

View File

@@ -45,7 +45,8 @@ import { InstallWithProgress } from './grpc-installable';
@injectable()
export class BoardsServiceImpl
extends CoreClientAware
implements BoardsService {
implements BoardsService
{
@inject(ILogger)
protected logger: ILogger;
@@ -247,7 +248,10 @@ export class BoardsServiceImpl
return boards;
}
async getBoardUserFields(options: { fqbn: string, protocol: string }): Promise<BoardUserField[]> {
async getBoardUserFields(options: {
fqbn: string;
protocol: string;
}): Promise<BoardUserField[]> {
await this.coreClientProvider.initialized;
const coreClient = await this.coreClient();
const { client, instance } = coreClient;
@@ -257,25 +261,23 @@ export class BoardsServiceImpl
supportedUserFieldsReq.setFqbn(options.fqbn);
supportedUserFieldsReq.setProtocol(options.protocol);
const supportedUserFieldsResp = await new Promise<SupportedUserFieldsResponse>(
(resolve, reject) => {
const supportedUserFieldsResp =
await new Promise<SupportedUserFieldsResponse>((resolve, reject) => {
client.supportedUserFields(supportedUserFieldsReq, (err, resp) => {
(!!err ? reject : resolve)(!!err ? err : resp)
})
}
);
return supportedUserFieldsResp.getUserFieldsList().map(e => {
(!!err ? reject : resolve)(!!err ? err : resp);
});
});
return supportedUserFieldsResp.getUserFieldsList().map((e) => {
return {
toolId: e.getToolId(),
name: e.getName(),
label: e.getLabel(),
secret: e.getSecret(),
value: "",
value: '',
};
});
}
async search(options: { query?: string }): Promise<BoardsPackage[]> {
await this.coreClientProvider.initialized;
const coreClient = await this.coreClient();
@@ -408,6 +410,10 @@ export class BoardsServiceImpl
req.setVersion(version);
console.info('>>> Starting boards package installation...', item);
// stop the board discovery
await this.boardDiscovery.stopBoardListWatch(coreClient);
const resp = client.platformInstall(req);
resp.on(
'data',
@@ -418,7 +424,7 @@ export class BoardsServiceImpl
);
await new Promise<void>((resolve, reject) => {
resp.on('end', () => {
this.boardDiscovery.startBoardListWatch(coreClient)
this.boardDiscovery.startBoardListWatch(coreClient);
resolve();
});
resp.on('error', (error) => {
@@ -456,6 +462,10 @@ export class BoardsServiceImpl
req.setPlatformPackage(platform);
console.info('>>> Starting boards package uninstallation...', item);
// stop the board discovery
await this.boardDiscovery.stopBoardListWatch(coreClient);
const resp = client.platformUninstall(req);
resp.on(
'data',
@@ -466,7 +476,7 @@ export class BoardsServiceImpl
);
await new Promise<void>((resolve, reject) => {
resp.on('end', () => {
this.boardDiscovery.startBoardListWatch(coreClient)
this.boardDiscovery.startBoardListWatch(coreClient);
resolve();
});
resp.on('error', reject);

View File

@@ -12,6 +12,7 @@ import * as cc_arduino_cli_commands_v1_common_pb from "../../../../../cc/arduino
import * as cc_arduino_cli_commands_v1_board_pb from "../../../../../cc/arduino/cli/commands/v1/board_pb";
import * as cc_arduino_cli_commands_v1_compile_pb from "../../../../../cc/arduino/cli/commands/v1/compile_pb";
import * as cc_arduino_cli_commands_v1_core_pb from "../../../../../cc/arduino/cli/commands/v1/core_pb";
import * as cc_arduino_cli_commands_v1_monitor_pb from "../../../../../cc/arduino/cli/commands/v1/monitor_pb";
import * as cc_arduino_cli_commands_v1_upload_pb from "../../../../../cc/arduino/cli/commands/v1/upload_pb";
import * as cc_arduino_cli_commands_v1_lib_pb from "../../../../../cc/arduino/cli/commands/v1/lib_pb";
@@ -25,6 +26,7 @@ interface IArduinoCoreServiceService extends grpc.ServiceDefinition<grpc.Untyped
outdated: IArduinoCoreServiceService_IOutdated;
upgrade: IArduinoCoreServiceService_IUpgrade;
version: IArduinoCoreServiceService_IVersion;
newSketch: IArduinoCoreServiceService_INewSketch;
loadSketch: IArduinoCoreServiceService_ILoadSketch;
archiveSketch: IArduinoCoreServiceService_IArchiveSketch;
boardDetails: IArduinoCoreServiceService_IBoardDetails;
@@ -54,6 +56,8 @@ interface IArduinoCoreServiceService extends grpc.ServiceDefinition<grpc.Untyped
libraryResolveDependencies: IArduinoCoreServiceService_ILibraryResolveDependencies;
librarySearch: IArduinoCoreServiceService_ILibrarySearch;
libraryList: IArduinoCoreServiceService_ILibraryList;
monitor: IArduinoCoreServiceService_IMonitor;
enumerateMonitorPortSettings: IArduinoCoreServiceService_IEnumerateMonitorPortSettings;
}
interface IArduinoCoreServiceService_ICreate extends grpc.MethodDefinition<cc_arduino_cli_commands_v1_commands_pb.CreateRequest, cc_arduino_cli_commands_v1_commands_pb.CreateResponse> {
@@ -137,6 +141,15 @@ interface IArduinoCoreServiceService_IVersion extends grpc.MethodDefinition<cc_a
responseSerialize: grpc.serialize<cc_arduino_cli_commands_v1_commands_pb.VersionResponse>;
responseDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_commands_pb.VersionResponse>;
}
interface IArduinoCoreServiceService_INewSketch extends grpc.MethodDefinition<cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest, cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse> {
path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/NewSketch";
requestStream: false;
responseStream: false;
requestSerialize: grpc.serialize<cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest>;
requestDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest>;
responseSerialize: grpc.serialize<cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse>;
responseDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse>;
}
interface IArduinoCoreServiceService_ILoadSketch extends grpc.MethodDefinition<cc_arduino_cli_commands_v1_commands_pb.LoadSketchRequest, cc_arduino_cli_commands_v1_commands_pb.LoadSketchResponse> {
path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/LoadSketch";
requestStream: false;
@@ -398,6 +411,24 @@ interface IArduinoCoreServiceService_ILibraryList extends grpc.MethodDefinition<
responseSerialize: grpc.serialize<cc_arduino_cli_commands_v1_lib_pb.LibraryListResponse>;
responseDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_lib_pb.LibraryListResponse>;
}
interface IArduinoCoreServiceService_IMonitor extends grpc.MethodDefinition<cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest, cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse> {
path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/Monitor";
requestStream: true;
responseStream: true;
requestSerialize: grpc.serialize<cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest>;
requestDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest>;
responseSerialize: grpc.serialize<cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse>;
responseDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse>;
}
interface IArduinoCoreServiceService_IEnumerateMonitorPortSettings extends grpc.MethodDefinition<cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest, cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse> {
path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/EnumerateMonitorPortSettings";
requestStream: false;
responseStream: false;
requestSerialize: grpc.serialize<cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest>;
requestDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest>;
responseSerialize: grpc.serialize<cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse>;
responseDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse>;
}
export const ArduinoCoreServiceService: IArduinoCoreServiceService;
@@ -411,6 +442,7 @@ export interface IArduinoCoreServiceServer {
outdated: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_commands_pb.OutdatedRequest, cc_arduino_cli_commands_v1_commands_pb.OutdatedResponse>;
upgrade: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_commands_pb.UpgradeRequest, cc_arduino_cli_commands_v1_commands_pb.UpgradeResponse>;
version: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_commands_pb.VersionRequest, cc_arduino_cli_commands_v1_commands_pb.VersionResponse>;
newSketch: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest, cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse>;
loadSketch: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_commands_pb.LoadSketchRequest, cc_arduino_cli_commands_v1_commands_pb.LoadSketchResponse>;
archiveSketch: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchRequest, cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchResponse>;
boardDetails: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_board_pb.BoardDetailsRequest, cc_arduino_cli_commands_v1_board_pb.BoardDetailsResponse>;
@@ -440,6 +472,8 @@ export interface IArduinoCoreServiceServer {
libraryResolveDependencies: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_lib_pb.LibraryResolveDependenciesRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryResolveDependenciesResponse>;
librarySearch: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_lib_pb.LibrarySearchRequest, cc_arduino_cli_commands_v1_lib_pb.LibrarySearchResponse>;
libraryList: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_lib_pb.LibraryListRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryListResponse>;
monitor: grpc.handleBidiStreamingCall<cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest, cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse>;
enumerateMonitorPortSettings: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest, cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse>;
}
export interface IArduinoCoreServiceClient {
@@ -465,6 +499,9 @@ export interface IArduinoCoreServiceClient {
version(request: cc_arduino_cli_commands_v1_commands_pb.VersionRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.VersionResponse) => void): grpc.ClientUnaryCall;
version(request: cc_arduino_cli_commands_v1_commands_pb.VersionRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.VersionResponse) => void): grpc.ClientUnaryCall;
version(request: cc_arduino_cli_commands_v1_commands_pb.VersionRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.VersionResponse) => void): grpc.ClientUnaryCall;
newSketch(request: cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse) => void): grpc.ClientUnaryCall;
newSketch(request: cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse) => void): grpc.ClientUnaryCall;
newSketch(request: cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse) => void): grpc.ClientUnaryCall;
loadSketch(request: cc_arduino_cli_commands_v1_commands_pb.LoadSketchRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.LoadSketchResponse) => void): grpc.ClientUnaryCall;
loadSketch(request: cc_arduino_cli_commands_v1_commands_pb.LoadSketchRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.LoadSketchResponse) => void): grpc.ClientUnaryCall;
loadSketch(request: cc_arduino_cli_commands_v1_commands_pb.LoadSketchRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.LoadSketchResponse) => void): grpc.ClientUnaryCall;
@@ -537,6 +574,12 @@ export interface IArduinoCoreServiceClient {
libraryList(request: cc_arduino_cli_commands_v1_lib_pb.LibraryListRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_lib_pb.LibraryListResponse) => void): grpc.ClientUnaryCall;
libraryList(request: cc_arduino_cli_commands_v1_lib_pb.LibraryListRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_lib_pb.LibraryListResponse) => void): grpc.ClientUnaryCall;
libraryList(request: cc_arduino_cli_commands_v1_lib_pb.LibraryListRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_lib_pb.LibraryListResponse) => void): grpc.ClientUnaryCall;
monitor(): grpc.ClientDuplexStream<cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest, cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse>;
monitor(options: Partial<grpc.CallOptions>): grpc.ClientDuplexStream<cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest, cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse>;
monitor(metadata: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientDuplexStream<cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest, cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse>;
enumerateMonitorPortSettings(request: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse) => void): grpc.ClientUnaryCall;
enumerateMonitorPortSettings(request: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse) => void): grpc.ClientUnaryCall;
enumerateMonitorPortSettings(request: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse) => void): grpc.ClientUnaryCall;
}
export class ArduinoCoreServiceClient extends grpc.Client implements IArduinoCoreServiceClient {
@@ -563,6 +606,9 @@ export class ArduinoCoreServiceClient extends grpc.Client implements IArduinoCor
public version(request: cc_arduino_cli_commands_v1_commands_pb.VersionRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.VersionResponse) => void): grpc.ClientUnaryCall;
public version(request: cc_arduino_cli_commands_v1_commands_pb.VersionRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.VersionResponse) => void): grpc.ClientUnaryCall;
public version(request: cc_arduino_cli_commands_v1_commands_pb.VersionRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.VersionResponse) => void): grpc.ClientUnaryCall;
public newSketch(request: cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse) => void): grpc.ClientUnaryCall;
public newSketch(request: cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse) => void): grpc.ClientUnaryCall;
public newSketch(request: cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse) => void): grpc.ClientUnaryCall;
public loadSketch(request: cc_arduino_cli_commands_v1_commands_pb.LoadSketchRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.LoadSketchResponse) => void): grpc.ClientUnaryCall;
public loadSketch(request: cc_arduino_cli_commands_v1_commands_pb.LoadSketchRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.LoadSketchResponse) => void): grpc.ClientUnaryCall;
public loadSketch(request: cc_arduino_cli_commands_v1_commands_pb.LoadSketchRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.LoadSketchResponse) => void): grpc.ClientUnaryCall;
@@ -634,4 +680,9 @@ export class ArduinoCoreServiceClient extends grpc.Client implements IArduinoCor
public libraryList(request: cc_arduino_cli_commands_v1_lib_pb.LibraryListRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_lib_pb.LibraryListResponse) => void): grpc.ClientUnaryCall;
public libraryList(request: cc_arduino_cli_commands_v1_lib_pb.LibraryListRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_lib_pb.LibraryListResponse) => void): grpc.ClientUnaryCall;
public libraryList(request: cc_arduino_cli_commands_v1_lib_pb.LibraryListRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_lib_pb.LibraryListResponse) => void): grpc.ClientUnaryCall;
public monitor(options?: Partial<grpc.CallOptions>): grpc.ClientDuplexStream<cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest, cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse>;
public monitor(metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientDuplexStream<cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest, cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse>;
public enumerateMonitorPortSettings(request: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse) => void): grpc.ClientUnaryCall;
public enumerateMonitorPortSettings(request: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse) => void): grpc.ClientUnaryCall;
public enumerateMonitorPortSettings(request: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse) => void): grpc.ClientUnaryCall;
}

View File

@@ -23,6 +23,7 @@ var cc_arduino_cli_commands_v1_common_pb = require('../../../../../cc/arduino/cl
var cc_arduino_cli_commands_v1_board_pb = require('../../../../../cc/arduino/cli/commands/v1/board_pb.js');
var cc_arduino_cli_commands_v1_compile_pb = require('../../../../../cc/arduino/cli/commands/v1/compile_pb.js');
var cc_arduino_cli_commands_v1_core_pb = require('../../../../../cc/arduino/cli/commands/v1/core_pb.js');
var cc_arduino_cli_commands_v1_monitor_pb = require('../../../../../cc/arduino/cli/commands/v1/monitor_pb.js');
var cc_arduino_cli_commands_v1_upload_pb = require('../../../../../cc/arduino/cli/commands/v1/upload_pb.js');
var cc_arduino_cli_commands_v1_lib_pb = require('../../../../../cc/arduino/cli/commands/v1/lib_pb.js');
@@ -268,6 +269,28 @@ function deserialize_cc_arduino_cli_commands_v1_DestroyResponse(buffer_arg) {
return cc_arduino_cli_commands_v1_commands_pb.DestroyResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_EnumerateMonitorPortSettingsRequest(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.EnumerateMonitorPortSettingsRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_v1_EnumerateMonitorPortSettingsRequest(buffer_arg) {
return cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_EnumerateMonitorPortSettingsResponse(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.EnumerateMonitorPortSettingsResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_v1_EnumerateMonitorPortSettingsResponse(buffer_arg) {
return cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_GitLibraryInstallRequest(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallRequest)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.GitLibraryInstallRequest');
@@ -510,6 +533,50 @@ function deserialize_cc_arduino_cli_commands_v1_LoadSketchResponse(buffer_arg) {
return cc_arduino_cli_commands_v1_commands_pb.LoadSketchResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_MonitorRequest(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.MonitorRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_v1_MonitorRequest(buffer_arg) {
return cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_MonitorResponse(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.MonitorResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_v1_MonitorResponse(buffer_arg) {
return cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_NewSketchRequest(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.NewSketchRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_v1_NewSketchRequest(buffer_arg) {
return cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_NewSketchResponse(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.NewSketchResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_v1_NewSketchResponse(buffer_arg) {
return cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_OutdatedRequest(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_commands_pb.OutdatedRequest)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.OutdatedRequest');
@@ -974,6 +1041,18 @@ version: {
responseSerialize: serialize_cc_arduino_cli_commands_v1_VersionResponse,
responseDeserialize: deserialize_cc_arduino_cli_commands_v1_VersionResponse,
},
// Create a new Sketch
newSketch: {
path: '/cc.arduino.cli.commands.v1.ArduinoCoreService/NewSketch',
requestStream: false,
responseStream: false,
requestType: cc_arduino_cli_commands_v1_commands_pb.NewSketchRequest,
responseType: cc_arduino_cli_commands_v1_commands_pb.NewSketchResponse,
requestSerialize: serialize_cc_arduino_cli_commands_v1_NewSketchRequest,
requestDeserialize: deserialize_cc_arduino_cli_commands_v1_NewSketchRequest,
responseSerialize: serialize_cc_arduino_cli_commands_v1_NewSketchResponse,
responseDeserialize: deserialize_cc_arduino_cli_commands_v1_NewSketchResponse,
},
// Returns all files composing a Sketch
loadSketch: {
path: '/cc.arduino.cli.commands.v1.ArduinoCoreService/LoadSketch',
@@ -1331,6 +1410,30 @@ libraryList: {
responseSerialize: serialize_cc_arduino_cli_commands_v1_LibraryListResponse,
responseDeserialize: deserialize_cc_arduino_cli_commands_v1_LibraryListResponse,
},
// Open a monitor connection to a board port
monitor: {
path: '/cc.arduino.cli.commands.v1.ArduinoCoreService/Monitor',
requestStream: true,
responseStream: true,
requestType: cc_arduino_cli_commands_v1_monitor_pb.MonitorRequest,
responseType: cc_arduino_cli_commands_v1_monitor_pb.MonitorResponse,
requestSerialize: serialize_cc_arduino_cli_commands_v1_MonitorRequest,
requestDeserialize: deserialize_cc_arduino_cli_commands_v1_MonitorRequest,
responseSerialize: serialize_cc_arduino_cli_commands_v1_MonitorResponse,
responseDeserialize: deserialize_cc_arduino_cli_commands_v1_MonitorResponse,
},
// Returns the parameters that can be set in the MonitorRequest calls
enumerateMonitorPortSettings: {
path: '/cc.arduino.cli.commands.v1.ArduinoCoreService/EnumerateMonitorPortSettings',
requestStream: false,
responseStream: false,
requestType: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsRequest,
responseType: cc_arduino_cli_commands_v1_monitor_pb.EnumerateMonitorPortSettingsResponse,
requestSerialize: serialize_cc_arduino_cli_commands_v1_EnumerateMonitorPortSettingsRequest,
requestDeserialize: deserialize_cc_arduino_cli_commands_v1_EnumerateMonitorPortSettingsRequest,
responseSerialize: serialize_cc_arduino_cli_commands_v1_EnumerateMonitorPortSettingsResponse,
responseDeserialize: deserialize_cc_arduino_cli_commands_v1_EnumerateMonitorPortSettingsResponse,
},
};
// BOOTSTRAP COMMANDS

View File

@@ -10,6 +10,7 @@ import * as cc_arduino_cli_commands_v1_common_pb from "../../../../../cc/arduino
import * as cc_arduino_cli_commands_v1_board_pb from "../../../../../cc/arduino/cli/commands/v1/board_pb";
import * as cc_arduino_cli_commands_v1_compile_pb from "../../../../../cc/arduino/cli/commands/v1/compile_pb";
import * as cc_arduino_cli_commands_v1_core_pb from "../../../../../cc/arduino/cli/commands/v1/core_pb";
import * as cc_arduino_cli_commands_v1_monitor_pb from "../../../../../cc/arduino/cli/commands/v1/monitor_pb";
import * as cc_arduino_cli_commands_v1_upload_pb from "../../../../../cc/arduino/cli/commands/v1/upload_pb";
import * as cc_arduino_cli_commands_v1_lib_pb from "../../../../../cc/arduino/cli/commands/v1/lib_pb";
@@ -489,6 +490,59 @@ export namespace VersionResponse {
}
}
export class NewSketchRequest extends jspb.Message {
hasInstance(): boolean;
clearInstance(): void;
getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined;
setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): NewSketchRequest;
getSketchName(): string;
setSketchName(value: string): NewSketchRequest;
getSketchDir(): string;
setSketchDir(value: string): NewSketchRequest;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): NewSketchRequest.AsObject;
static toObject(includeInstance: boolean, msg: NewSketchRequest): NewSketchRequest.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: NewSketchRequest, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): NewSketchRequest;
static deserializeBinaryFromReader(message: NewSketchRequest, reader: jspb.BinaryReader): NewSketchRequest;
}
export namespace NewSketchRequest {
export type AsObject = {
instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject,
sketchName: string,
sketchDir: string,
}
}
export class NewSketchResponse extends jspb.Message {
getMainFile(): string;
setMainFile(value: string): NewSketchResponse;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): NewSketchResponse.AsObject;
static toObject(includeInstance: boolean, msg: NewSketchResponse): NewSketchResponse.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: NewSketchResponse, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): NewSketchResponse;
static deserializeBinaryFromReader(message: NewSketchResponse, reader: jspb.BinaryReader): NewSketchResponse;
}
export namespace NewSketchResponse {
export type AsObject = {
mainFile: string,
}
}
export class LoadSketchRequest extends jspb.Message {
hasInstance(): boolean;

View File

@@ -25,6 +25,8 @@ var cc_arduino_cli_commands_v1_compile_pb = require('../../../../../cc/arduino/c
goog.object.extend(proto, cc_arduino_cli_commands_v1_compile_pb);
var cc_arduino_cli_commands_v1_core_pb = require('../../../../../cc/arduino/cli/commands/v1/core_pb.js');
goog.object.extend(proto, cc_arduino_cli_commands_v1_core_pb);
var cc_arduino_cli_commands_v1_monitor_pb = require('../../../../../cc/arduino/cli/commands/v1/monitor_pb.js');
goog.object.extend(proto, cc_arduino_cli_commands_v1_monitor_pb);
var cc_arduino_cli_commands_v1_upload_pb = require('../../../../../cc/arduino/cli/commands/v1/upload_pb.js');
goog.object.extend(proto, cc_arduino_cli_commands_v1_upload_pb);
var cc_arduino_cli_commands_v1_lib_pb = require('../../../../../cc/arduino/cli/commands/v1/lib_pb.js');
@@ -41,6 +43,8 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.v1.InitResponse.MessageCase', n
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.InitResponse.Progress', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LoadSketchRequest', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LoadSketchResponse', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.NewSketchRequest', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.NewSketchResponse', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.OutdatedRequest', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.OutdatedResponse', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UpdateCoreLibrariesIndexRequest', null, global);
@@ -452,6 +456,48 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.cc.arduino.cli.commands.v1.VersionResponse.displayName = 'proto.cc.arduino.cli.commands.v1.VersionResponse';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.v1.NewSketchRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.displayName = 'proto.cc.arduino.cli.commands.v1.NewSketchRequest';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.v1.NewSketchResponse, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse.displayName = 'proto.cc.arduino.cli.commands.v1.NewSketchResponse';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
@@ -3508,6 +3554,347 @@ proto.cc.arduino.cli.commands.v1.VersionResponse.prototype.setVersion = function
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.v1.NewSketchRequest.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.v1.NewSketchRequest} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.toObject = function(includeInstance, msg) {
var f, obj = {
instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f),
sketchName: jspb.Message.getFieldWithDefault(msg, 2, ""),
sketchDir: jspb.Message.getFieldWithDefault(msg, 3, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.v1.NewSketchRequest}
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.v1.NewSketchRequest;
return proto.cc.arduino.cli.commands.v1.NewSketchRequest.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.v1.NewSketchRequest} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.v1.NewSketchRequest}
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = new cc_arduino_cli_commands_v1_common_pb.Instance;
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader);
msg.setInstance(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setSketchName(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setSketchDir(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.v1.NewSketchRequest.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.v1.NewSketchRequest} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getInstance();
if (f != null) {
writer.writeMessage(
1,
f,
cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter
);
}
f = message.getSketchName();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getSketchDir();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
};
/**
* optional Instance instance = 1;
* @return {?proto.cc.arduino.cli.commands.v1.Instance}
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.getInstance = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.Instance} */ (
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.Instance, 1));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.Instance|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.NewSketchRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.setInstance = function(value) {
return jspb.Message.setWrapperField(this, 1, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.NewSketchRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.clearInstance = function() {
return this.setInstance(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.hasInstance = function() {
return jspb.Message.getField(this, 1) != null;
};
/**
* optional string sketch_name = 2;
* @return {string}
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.getSketchName = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/**
* @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.NewSketchRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.setSketchName = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
};
/**
* optional string sketch_dir = 3;
* @return {string}
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.getSketchDir = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
};
/**
* @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.NewSketchRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.setSketchDir = function(value) {
return jspb.Message.setProto3StringField(this, 3, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.v1.NewSketchResponse.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.v1.NewSketchResponse} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse.toObject = function(includeInstance, msg) {
var f, obj = {
mainFile: jspb.Message.getFieldWithDefault(msg, 1, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.v1.NewSketchResponse}
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.v1.NewSketchResponse;
return proto.cc.arduino.cli.commands.v1.NewSketchResponse.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.v1.NewSketchResponse} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.v1.NewSketchResponse}
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setMainFile(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.v1.NewSketchResponse.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.v1.NewSketchResponse} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getMainFile();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
};
/**
* optional string main_file = 1;
* @return {string}
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse.prototype.getMainFile = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.NewSketchResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.NewSketchResponse.prototype.setMainFile = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.

View File

@@ -74,6 +74,9 @@ export class TaskProgress extends jspb.Message {
getCompleted(): boolean;
setCompleted(value: boolean): TaskProgress;
getPercent(): number;
setPercent(value: number): TaskProgress;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): TaskProgress.AsObject;
@@ -90,6 +93,7 @@ export namespace TaskProgress {
name: string,
message: string,
completed: boolean,
percent: number,
}
}
@@ -181,6 +185,31 @@ export namespace Platform {
}
}
export class PlatformReference extends jspb.Message {
getId(): string;
setId(value: string): PlatformReference;
getVersion(): string;
setVersion(value: string): PlatformReference;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): PlatformReference.AsObject;
static toObject(includeInstance: boolean, msg: PlatformReference): PlatformReference.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: PlatformReference, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): PlatformReference;
static deserializeBinaryFromReader(message: PlatformReference, reader: jspb.BinaryReader): PlatformReference;
}
export namespace PlatformReference {
export type AsObject = {
id: string,
version: string,
}
}
export class Board extends jspb.Message {
getName(): string;
setName(value: string): Board;

View File

@@ -19,6 +19,7 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Board', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgress', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Instance', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Platform', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.PlatformReference', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Programmer', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.TaskProgress', null, global);
/**
@@ -126,6 +127,27 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.cc.arduino.cli.commands.v1.Platform.displayName = 'proto.cc.arduino.cli.commands.v1.Platform';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.v1.PlatformReference = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.v1.PlatformReference, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.displayName = 'proto.cc.arduino.cli.commands.v1.PlatformReference';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
@@ -561,7 +583,8 @@ proto.cc.arduino.cli.commands.v1.TaskProgress.toObject = function(includeInstanc
var f, obj = {
name: jspb.Message.getFieldWithDefault(msg, 1, ""),
message: jspb.Message.getFieldWithDefault(msg, 2, ""),
completed: jspb.Message.getBooleanFieldWithDefault(msg, 3, false)
completed: jspb.Message.getBooleanFieldWithDefault(msg, 3, false),
percent: jspb.Message.getFloatingPointFieldWithDefault(msg, 4, 0.0)
};
if (includeInstance) {
@@ -610,6 +633,10 @@ proto.cc.arduino.cli.commands.v1.TaskProgress.deserializeBinaryFromReader = func
var value = /** @type {boolean} */ (reader.readBool());
msg.setCompleted(value);
break;
case 4:
var value = /** @type {number} */ (reader.readFloat());
msg.setPercent(value);
break;
default:
reader.skipField();
break;
@@ -660,6 +687,13 @@ proto.cc.arduino.cli.commands.v1.TaskProgress.serializeBinaryToWriter = function
f
);
}
f = message.getPercent();
if (f !== 0.0) {
writer.writeFloat(
4,
f
);
}
};
@@ -717,6 +751,24 @@ proto.cc.arduino.cli.commands.v1.TaskProgress.prototype.setCompleted = function(
};
/**
* optional float percent = 4;
* @return {number}
*/
proto.cc.arduino.cli.commands.v1.TaskProgress.prototype.getPercent = function() {
return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 4, 0.0));
};
/**
* @param {number} value
* @return {!proto.cc.arduino.cli.commands.v1.TaskProgress} returns this
*/
proto.cc.arduino.cli.commands.v1.TaskProgress.prototype.setPercent = function(value) {
return jspb.Message.setProto3FloatField(this, 4, value);
};
@@ -1340,6 +1392,166 @@ proto.cc.arduino.cli.commands.v1.Platform.prototype.setDeprecated = function(val
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.v1.PlatformReference.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.toObject = function(includeInstance, msg) {
var f, obj = {
id: jspb.Message.getFieldWithDefault(msg, 1, ""),
version: jspb.Message.getFieldWithDefault(msg, 2, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.v1.PlatformReference}
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.v1.PlatformReference;
return proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.v1.PlatformReference}
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setId(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setVersion(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getId();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
f = message.getVersion();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
};
/**
* optional string id = 1;
* @return {string}
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.getId = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} returns this
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setId = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
/**
* optional string version = 2;
* @return {string}
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.getVersion = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/**
* @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} returns this
*/
proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setVersion = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.

View File

@@ -149,6 +149,24 @@ export class CompileResponse extends jspb.Message {
addExecutableSectionsSize(value?: ExecutableSectionSize, index?: number): ExecutableSectionSize;
hasBoardPlatform(): boolean;
clearBoardPlatform(): void;
getBoardPlatform(): cc_arduino_cli_commands_v1_common_pb.PlatformReference | undefined;
setBoardPlatform(value?: cc_arduino_cli_commands_v1_common_pb.PlatformReference): CompileResponse;
hasBuildPlatform(): boolean;
clearBuildPlatform(): void;
getBuildPlatform(): cc_arduino_cli_commands_v1_common_pb.PlatformReference | undefined;
setBuildPlatform(value?: cc_arduino_cli_commands_v1_common_pb.PlatformReference): CompileResponse;
hasProgress(): boolean;
clearProgress(): void;
getProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined;
setProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): CompileResponse;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): CompileResponse.AsObject;
static toObject(includeInstance: boolean, msg: CompileResponse): CompileResponse.AsObject;
@@ -166,6 +184,9 @@ export namespace CompileResponse {
buildPath: string,
usedLibrariesList: Array<cc_arduino_cli_commands_v1_lib_pb.Library.AsObject>,
executableSectionsSizeList: Array<ExecutableSectionSize.AsObject>,
boardPlatform?: cc_arduino_cli_commands_v1_common_pb.PlatformReference.AsObject,
buildPlatform?: cc_arduino_cli_commands_v1_common_pb.PlatformReference.AsObject,
progress?: cc_arduino_cli_commands_v1_common_pb.TaskProgress.AsObject,
}
}

View File

@@ -971,7 +971,10 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.toObject = function(includeInst
usedLibrariesList: jspb.Message.toObjectList(msg.getUsedLibrariesList(),
cc_arduino_cli_commands_v1_lib_pb.Library.toObject, includeInstance),
executableSectionsSizeList: jspb.Message.toObjectList(msg.getExecutableSectionsSizeList(),
proto.cc.arduino.cli.commands.v1.ExecutableSectionSize.toObject, includeInstance)
proto.cc.arduino.cli.commands.v1.ExecutableSectionSize.toObject, includeInstance),
boardPlatform: (f = msg.getBoardPlatform()) && cc_arduino_cli_commands_v1_common_pb.PlatformReference.toObject(includeInstance, f),
buildPlatform: (f = msg.getBuildPlatform()) && cc_arduino_cli_commands_v1_common_pb.PlatformReference.toObject(includeInstance, f),
progress: (f = msg.getProgress()) && cc_arduino_cli_commands_v1_common_pb.TaskProgress.toObject(includeInstance, f)
};
if (includeInstance) {
@@ -1030,6 +1033,21 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.deserializeBinaryFromReader = f
reader.readMessage(value,proto.cc.arduino.cli.commands.v1.ExecutableSectionSize.deserializeBinaryFromReader);
msg.addExecutableSectionsSize(value);
break;
case 6:
var value = new cc_arduino_cli_commands_v1_common_pb.PlatformReference;
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.PlatformReference.deserializeBinaryFromReader);
msg.setBoardPlatform(value);
break;
case 7:
var value = new cc_arduino_cli_commands_v1_common_pb.PlatformReference;
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.PlatformReference.deserializeBinaryFromReader);
msg.setBuildPlatform(value);
break;
case 8:
var value = new cc_arduino_cli_commands_v1_common_pb.TaskProgress;
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.TaskProgress.deserializeBinaryFromReader);
msg.setProgress(value);
break;
default:
reader.skipField();
break;
@@ -1096,6 +1114,30 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.serializeBinaryToWriter = funct
proto.cc.arduino.cli.commands.v1.ExecutableSectionSize.serializeBinaryToWriter
);
}
f = message.getBoardPlatform();
if (f != null) {
writer.writeMessage(
6,
f,
cc_arduino_cli_commands_v1_common_pb.PlatformReference.serializeBinaryToWriter
);
}
f = message.getBuildPlatform();
if (f != null) {
writer.writeMessage(
7,
f,
cc_arduino_cli_commands_v1_common_pb.PlatformReference.serializeBinaryToWriter
);
}
f = message.getProgress();
if (f != null) {
writer.writeMessage(
8,
f,
cc_arduino_cli_commands_v1_common_pb.TaskProgress.serializeBinaryToWriter
);
}
};
@@ -1277,6 +1319,117 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.clearExecutableSectio
};
/**
* optional PlatformReference board_platform = 6;
* @return {?proto.cc.arduino.cli.commands.v1.PlatformReference}
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.getBoardPlatform = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.PlatformReference} */ (
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.PlatformReference, 6));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.PlatformReference|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.setBoardPlatform = function(value) {
return jspb.Message.setWrapperField(this, 6, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.clearBoardPlatform = function() {
return this.setBoardPlatform(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.hasBoardPlatform = function() {
return jspb.Message.getField(this, 6) != null;
};
/**
* optional PlatformReference build_platform = 7;
* @return {?proto.cc.arduino.cli.commands.v1.PlatformReference}
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.getBuildPlatform = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.PlatformReference} */ (
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.PlatformReference, 7));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.PlatformReference|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.setBuildPlatform = function(value) {
return jspb.Message.setWrapperField(this, 7, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.clearBuildPlatform = function() {
return this.setBuildPlatform(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.hasBuildPlatform = function() {
return jspb.Message.getField(this, 7) != null;
};
/**
* optional TaskProgress progress = 8;
* @return {?proto.cc.arduino.cli.commands.v1.TaskProgress}
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.getProgress = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.TaskProgress} */ (
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.TaskProgress, 8));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.TaskProgress|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.setProgress = function(value) {
return jspb.Message.setWrapperField(this, 8, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.clearProgress = function() {
return this.setProgress(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.hasProgress = function() {
return jspb.Message.getField(this, 8) != null;
};

View File

@@ -371,6 +371,9 @@ export class SupportedUserFieldsRequest extends jspb.Message {
getProtocol(): string;
setProtocol(value: string): SupportedUserFieldsRequest;
getAddress(): string;
setAddress(value: string): SupportedUserFieldsRequest;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): SupportedUserFieldsRequest.AsObject;
@@ -387,6 +390,7 @@ export namespace SupportedUserFieldsRequest {
instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject,
fqbn: string,
protocol: string,
address: string,
}
}

View File

@@ -2718,7 +2718,8 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.toObject = function(
var f, obj = {
instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f),
fqbn: jspb.Message.getFieldWithDefault(msg, 2, ""),
protocol: jspb.Message.getFieldWithDefault(msg, 3, "")
protocol: jspb.Message.getFieldWithDefault(msg, 3, ""),
address: jspb.Message.getFieldWithDefault(msg, 4, "")
};
if (includeInstance) {
@@ -2768,6 +2769,10 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.deserializeBinaryFro
var value = /** @type {string} */ (reader.readString());
msg.setProtocol(value);
break;
case 4:
var value = /** @type {string} */ (reader.readString());
msg.setAddress(value);
break;
default:
reader.skipField();
break;
@@ -2819,6 +2824,13 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.serializeBinaryToWri
f
);
}
f = message.getAddress();
if (f.length > 0) {
writer.writeString(
4,
f
);
}
};
@@ -2895,6 +2907,24 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setProtoco
};
/**
* optional string address = 4;
* @return {string}
*/
proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.getAddress = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, ""));
};
/**
* @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setAddress = function(value) {
return jspb.Message.setProto3StringField(this, 4, value);
};

View File

@@ -43,7 +43,10 @@ function deserialize_cc_arduino_cli_monitor_v1_StreamingOpenResponse(buffer_arg)
}
// MonitorService provides services for boards monitor
// MonitorService provides services for boards monitor.
// DEPRECATION WARNING: MonitorService is deprecated and will be removed in a
// future release. Use ArduinoCoreService.Monitor and
// ArduinoCoreService.EnumerateMonitorPortSettings instead.
var MonitorServiceService = exports['cc.arduino.cli.monitor.v1.MonitorService'] = {
// Open a bidirectional monitor stream. This can be used to implement
// something similar to the Arduino IDE's Serial Monitor.

View File

@@ -75,9 +75,11 @@ export class ConfigServiceImpl
async getConfiguration(): Promise<Config> {
await this.ready.promise;
return this.config;
await this.daemon.ready;
return { ...this.config, daemon: { port: await this.daemon.getPort() } };
}
// Used by frontend to update the config.
async setConfiguration(config: Config): Promise<void> {
await this.ready.promise;
if (Config.sameAs(this.config, config)) {
@@ -108,7 +110,9 @@ export class ConfigServiceImpl
copyDefaultCliConfig.locale = locale || 'en';
const proxy = Network.stringify(network);
copyDefaultCliConfig.network = { proxy };
const { port } = copyDefaultCliConfig.daemon;
// always use the port of the daemon
const port = await this.daemon.getPort();
await this.updateDaemon(port, copyDefaultCliConfig);
await this.writeDaemonState(port);

View File

@@ -48,9 +48,9 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
this._initialized = new Deferred<void>();
}
protected async reconcileClient(
port: string | number | undefined
): Promise<void> {
protected async reconcileClient(): Promise<void> {
const port = await this.daemon.getPort();
if (port && port === this._port) {
// No need to create a new gRPC client, but we have to update the indexes.
if (this._client && !(this._client instanceof Error)) {
@@ -58,7 +58,7 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
this.onClientReadyEmitter.fire();
}
} else {
await super.reconcileClient(port);
await super.reconcileClient();
this.onClientReadyEmitter.fire();
}
}
@@ -66,13 +66,10 @@ export class CoreClientProvider extends GrpcClientProvider<CoreClientProvider.Cl
@postConstruct()
protected async init(): Promise<void> {
this.daemon.ready.then(async () => {
const cliConfig = this.configService.cliConfiguration;
// First create the client and the instance synchronously
// and notify client is ready.
// TODO: Creation failure should probably be handled here
await this.reconcileClient(
cliConfig ? cliConfig.daemon.port : undefined
).then(() => {
await this.reconcileClient().then(() => {
this._created.resolve();
});

View File

@@ -23,6 +23,8 @@ import { NotificationServiceServer } from '../common/protocol';
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
import { firstToUpperCase, firstToLowerCase } from '../common/utils';
import { Port } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb';
import { nls } from '@theia/core';
import { SerialService } from './../common/protocol/serial-service';
@injectable()
export class CoreServiceImpl extends CoreClientAware implements CoreService {
@@ -32,6 +34,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
@inject(NotificationServiceServer)
protected readonly notificationService: NotificationServiceServer;
@inject(SerialService)
protected readonly serialService: SerialService;
protected uploading = false;
async compile(
@@ -85,11 +90,16 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
chunk: '\n--------------------------\nCompilation complete.\n',
});
} catch (e) {
const errorMessage = nls.localize(
'arduino/compile/error',
'Compilation error: {0}',
e.details
);
this.responseService.appendToOutput({
chunk: `Compilation error: ${e.details}\n`,
chunk: `${errorMessage}\n`,
severity: 'error',
});
throw e;
throw new Error(errorMessage);
}
}
@@ -126,8 +136,13 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
) => ClientReadableStream<UploadResponse | UploadUsingProgrammerResponse>,
task = 'upload'
): Promise<void> {
this.uploading = true;
await this.compile(Object.assign(options, { exportBinaries: false }));
this.uploading = true;
this.serialService.uploadInProgress = true;
await this.serialService.disconnect();
const { sketchUri, fqbn, port, programmer } = options;
const sketchPath = FileUri.fsPath(sketchUri);
@@ -144,8 +159,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
const p = new Port();
if (port) {
p.setAddress(port.address);
p.setLabel(port.label || '');
p.setLabel(port.addressLabel);
p.setProtocol(port.protocol);
p.setProtocolLabel(port.protocolLabel);
}
req.setPort(p);
if (programmer) {
@@ -154,7 +170,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
req.setVerbose(options.verbose);
req.setVerify(options.verify);
options.userFields.forEach(e => {
options.userFields.forEach((e) => {
req.getUserFieldsMap().set(e.name, e.value);
});
@@ -180,17 +196,28 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
' complete.\n',
});
} catch (e) {
const errorMessage = nls.localize(
'arduino/upload/error',
'{0} error: {1}',
firstToUpperCase(task),
e.details
);
this.responseService.appendToOutput({
chunk: `${firstToUpperCase(task)} error: ${e.details}\n`,
chunk: `${errorMessage}\n`,
severity: 'error',
});
throw e;
throw new Error(errorMessage);
} finally {
this.uploading = false;
this.serialService.uploadInProgress = false;
}
}
async burnBootloader(options: CoreService.Bootloader.Options): Promise<void> {
this.uploading = true;
this.serialService.uploadInProgress = true;
await this.serialService.disconnect();
await this.coreClientProvider.initialized;
const coreClient = await this.coreClient();
const { client, instance } = coreClient;
@@ -203,8 +230,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
const p = new Port();
if (port) {
p.setAddress(port.address);
p.setLabel(port.label || '');
p.setLabel(port.addressLabel);
p.setProtocol(port.protocol);
p.setProtocolLabel(port.protocolLabel);
}
burnReq.setPort(p);
if (programmer) {
@@ -227,11 +255,19 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
result.on('end', () => resolve());
});
} catch (e) {
const errorMessage = nls.localize(
'arduino/burnBootloader/error',
'Error while burning the bootloader: {0}',
e.details
);
this.responseService.appendToOutput({
chunk: `Error while burning the bootloader: ${e.details}\n`,
chunk: `${errorMessage}\n`,
severity: 'error',
});
throw e;
throw new Error(errorMessage);
} finally {
this.uploading = false;
this.serialService.uploadInProgress = false;
}
}

View File

@@ -17,7 +17,7 @@ export class ExecutableServiceImpl implements ExecutableService {
}> {
const [ls, clangd, cli, fwuploader] = await Promise.all([
getExecPath('arduino-language-server', this.onError.bind(this)),
getExecPath('clangd', this.onError.bind(this), undefined, true),
getExecPath('clangd', this.onError.bind(this), undefined),
getExecPath('arduino-cli', this.onError.bind(this)),
getExecPath('arduino-fwuploader', this.onError.bind(this)),
]);

View File

@@ -21,8 +21,7 @@ export abstract class GrpcClientProvider<C> {
@postConstruct()
protected init(): void {
const updateClient = () => {
const cliConfig = this.configService.cliConfiguration;
this.reconcileClient(cliConfig ? cliConfig.daemon.port : undefined);
this.reconcileClient();
};
this.configService.onConfigChange(updateClient);
this.daemon.ready.then(updateClient);
@@ -44,9 +43,9 @@ export abstract class GrpcClientProvider<C> {
}
}
protected async reconcileClient(
port: string | number | undefined
): Promise<void> {
protected async reconcileClient(): Promise<void> {
const port = await this.daemon.getPort();
if (this._port === port) {
return; // Nothing to do.
}
@@ -71,9 +70,11 @@ export abstract class GrpcClientProvider<C> {
protected abstract close(client: C): void;
protected get channelOptions(): Record<string, unknown> {
const pjson = require('../../package.json') || { version: '0.0.0' };
return {
'grpc.max_send_message_length': 512 * 1024 * 1024,
'grpc.max_receive_message_length': 512 * 1024 * 1024,
'grpc.primary_user_agent': `arduino-ide/${pjson.version}`,
};
}
}

View File

@@ -30,7 +30,8 @@ import { InstallWithProgress } from './grpc-installable';
@injectable()
export class LibraryServiceImpl
extends CoreClientAware
implements LibraryService {
implements LibraryService
{
@inject(ILogger)
protected logger: ILogger;
@@ -267,11 +268,13 @@ export class LibraryServiceImpl
req.setInstance(instance);
req.setName(item.name);
req.setVersion(version);
if (options.installDependencies === false) {
req.setNoDeps(true);
}
req.setNoDeps(!options.installDependencies);
console.info('>>> Starting library package installation...', item);
// stop the board discovery
await this.boardDiscovery.stopBoardListWatch(coreClient);
const resp = client.libraryInstall(req);
resp.on(
'data',
@@ -282,13 +285,14 @@ export class LibraryServiceImpl
);
await new Promise<void>((resolve, reject) => {
resp.on('end', () => {
this.boardDiscovery.startBoardListWatch(coreClient)
this.boardDiscovery.startBoardListWatch(coreClient);
resolve();
});
resp.on('error', (error) => {
this.responseService.appendToOutput({
chunk: `Failed to install library: ${item.name}${version ? `:${version}` : ''
}.\n`,
chunk: `Failed to install library: ${item.name}${
version ? `:${version}` : ''
}.\n`,
});
this.responseService.appendToOutput({
chunk: error.toString(),
@@ -322,6 +326,10 @@ export class LibraryServiceImpl
if (typeof overwrite === 'boolean') {
req.setOverwrite(overwrite);
}
// stop the board discovery
await this.boardDiscovery.stopBoardListWatch(coreClient);
const resp = client.zipLibraryInstall(req);
resp.on(
'data',
@@ -332,7 +340,7 @@ export class LibraryServiceImpl
);
await new Promise<void>((resolve, reject) => {
resp.on('end', () => {
this.boardDiscovery.startBoardListWatch(coreClient)
this.boardDiscovery.startBoardListWatch(coreClient);
resolve();
});
resp.on('error', reject);
@@ -354,6 +362,10 @@ export class LibraryServiceImpl
req.setVersion(item.installedVersion!);
console.info('>>> Starting library package uninstallation...', item);
// stop the board discovery
await this.boardDiscovery.stopBoardListWatch(coreClient);
const resp = client.libraryUninstall(req);
resp.on(
'data',
@@ -364,7 +376,7 @@ export class LibraryServiceImpl
);
await new Promise<void>((resolve, reject) => {
resp.on('end', () => {
this.boardDiscovery.startBoardListWatch(coreClient)
this.boardDiscovery.startBoardListWatch(coreClient);
resolve();
});
resp.on('error', reject);

View File

@@ -16,7 +16,7 @@ import {
MonitorConfig as GrpcMonitorConfig,
} from '../cli-protocol/cc/arduino/cli/monitor/v1/monitor_pb';
import { MonitorClientProvider } from './monitor-client-provider';
import { Board, Port } from '../../common/protocol/boards-service';
import { Board } from '../../common/protocol/boards-service';
import { WebSocketService } from '../web-socket/web-socket-service';
import { SerialPlotter } from '../../browser/serial/plotter/protocol';
import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
@@ -63,27 +63,61 @@ namespace ErrorWithCode {
@injectable()
export class SerialServiceImpl implements SerialService {
@named(SerialServiceName)
@inject(ILogger)
protected readonly logger: ILogger;
protected theiaFEClient?: SerialServiceClient;
protected serialConfig?: SerialConfig;
@inject(MonitorClientProvider)
protected readonly serialClientProvider: MonitorClientProvider;
@inject(WebSocketService)
protected readonly webSocketService: WebSocketService;
protected client?: SerialServiceClient;
protected serialConnection?: {
duplex: ClientDuplexStream<StreamingOpenRequest, StreamingOpenResponse>;
config: SerialConfig;
};
protected messages: string[] = [];
protected onMessageReceived: Disposable | null;
protected onWSClientsNumberChanged: Disposable | null;
protected flushMessagesInterval: NodeJS.Timeout | null;
uploadInProgress = false;
constructor(
@inject(ILogger)
@named(SerialServiceName)
protected readonly logger: ILogger,
@inject(MonitorClientProvider)
protected readonly serialClientProvider: MonitorClientProvider,
@inject(WebSocketService)
protected readonly webSocketService: WebSocketService
) { }
async isSerialPortOpen(): Promise<boolean> {
return !!this.serialConnection;
}
setClient(client: SerialServiceClient | undefined): void {
this.client = client;
this.theiaFEClient = client;
this.theiaFEClient?.notifyWebSocketChanged(
this.webSocketService.getAddress().port
);
// listen for the number of websocket clients and create or dispose the serial connection
this.onWSClientsNumberChanged =
this.webSocketService.onClientsNumberChanged(async () => {
await this.connectSerialIfRequired();
});
}
public async clientsAttached(): Promise<number> {
return this.webSocketService.getConnectedClientsNumber.bind(
this.webSocketService
)();
}
public async connectSerialIfRequired(): Promise<void> {
if (this.uploadInProgress) return;
const clients = await this.clientsAttached();
clients > 0 ? await this.connect() : await this.disconnect();
}
dispose(): void {
@@ -92,7 +126,13 @@ export class SerialServiceImpl implements SerialService {
this.disconnect();
}
this.logger.info('<<< Disposed serial service.');
this.client = undefined;
this.theiaFEClient = undefined;
}
async setSerialConfig(config: SerialConfig): Promise<void> {
this.serialConfig = config;
await this.disconnect();
await this.connectSerialIfRequired();
}
async updateWsConfigParam(
@@ -105,12 +145,17 @@ export class SerialServiceImpl implements SerialService {
this.webSocketService.sendMessage(JSON.stringify(msg));
}
async connect(config: SerialConfig): Promise<Status> {
private async connect(): Promise<Status> {
if (!this.serialConfig) {
return Status.CONFIG_MISSING;
}
this.logger.info(
`>>> Creating serial connection for ${Board.toString(
config.board
)} on port ${Port.toString(config.port)}...`
this.serialConfig.board
)} on port ${this.serialConfig.port.address}...`
);
if (this.serialConnection) {
return Status.ALREADY_CONNECTED;
}
@@ -122,27 +167,29 @@ export class SerialServiceImpl implements SerialService {
return { message: client.message };
}
const duplex = client.streamingOpen();
this.serialConnection = { duplex, config };
this.serialConnection = { duplex, config: this.serialConfig };
const serialConfig = this.serialConfig;
duplex.on(
'error',
((error: Error) => {
const serialError = ErrorWithCode.toSerialError(error, config);
this.disconnect(serialError).then(() => {
if (this.client) {
this.client.notifyError(serialError);
}
if (serialError.code === undefined) {
// Log the original, unexpected error.
this.logger.error(error);
}
});
const serialError = ErrorWithCode.toSerialError(error, serialConfig);
if (serialError.code !== SerialError.ErrorCodes.CLIENT_CANCEL) {
this.disconnect(serialError).then(() => {
if (this.theiaFEClient) {
this.theiaFEClient.notifyError(serialError);
}
});
}
if (serialError.code === undefined) {
// Log the original, unexpected error.
this.logger.error(error);
}
}).bind(this)
);
this.client?.notifyWebSocketChanged(
this.webSocketService.getAddress().port
);
this.updateWsConfigParam({ connected: !!this.serialConnection });
const flushMessagesToFrontend = () => {
if (this.messages.length) {
@@ -162,50 +209,29 @@ export class SerialServiceImpl implements SerialService {
break;
case SerialPlotter.Protocol.Command.PLOTTER_SET_BAUDRATE:
this.client?.notifyBaudRateChanged(
this.theiaFEClient?.notifyBaudRateChanged(
parseInt(message.data, 10) as SerialConfig.BaudRate
);
break;
case SerialPlotter.Protocol.Command.PLOTTER_SET_LINE_ENDING:
this.client?.notifyLineEndingChanged(message.data);
this.theiaFEClient?.notifyLineEndingChanged(message.data);
break;
case SerialPlotter.Protocol.Command.PLOTTER_SET_INTERPOLATE:
this.client?.notifyInterpolateChanged(message.data);
this.theiaFEClient?.notifyInterpolateChanged(message.data);
break;
default:
break;
}
} catch (error) {}
} catch (error) { }
}
);
// empty the queue every 32ms (~30fps)
this.flushMessagesInterval = setInterval(flushMessagesToFrontend, 32);
// converts 'ab\nc\nd' => [ab\n,c\n,d]
const stringToArray = (string: string, separator = '\n') => {
const retArray: string[] = [];
let prevChar = separator;
for (let i = 0; i < string.length; i++) {
const currChar = string[i];
if (prevChar === separator) {
retArray.push(currChar);
} else {
const lastWord = retArray[retArray.length - 1];
retArray[retArray.length - 1] = lastWord + currChar;
}
prevChar = currChar;
}
return retArray;
};
duplex.on(
'data',
((resp: StreamingOpenResponse) => {
@@ -219,69 +245,105 @@ export class SerialServiceImpl implements SerialService {
}).bind(this)
);
const { type, port } = config;
const { type, port } = this.serialConfig;
const req = new StreamingOpenRequest();
const monitorConfig = new GrpcMonitorConfig();
monitorConfig.setType(this.mapType(type));
monitorConfig.setTarget(port.address);
if (config.baudRate !== undefined) {
if (this.serialConfig.baudRate !== undefined) {
monitorConfig.setAdditionalConfig(
Struct.fromJavaScript({ BaudRate: config.baudRate })
Struct.fromJavaScript({ BaudRate: this.serialConfig.baudRate })
);
}
req.setConfig(monitorConfig);
return new Promise<Status>((resolve) => {
if (this.serialConnection) {
this.serialConnection.duplex.write(req, () => {
this.logger.info(
`<<< Serial connection created for ${Board.toString(config.board, {
if (!this.serialConnection) {
return await this.disconnect();
}
const writeTimeout = new Promise<Status>((resolve) => {
setTimeout(async () => {
resolve(Status.NOT_CONNECTED);
}, 1000);
});
const writePromise = (serialConnection: any) => {
return new Promise<Status>((resolve) => {
serialConnection.duplex.write(req, () => {
const boardName = this.serialConfig?.board
? Board.toString(this.serialConfig.board, {
useFqbn: false,
})} on port ${Port.toString(config.port)}.`
})
: 'unknown board';
const portName = this.serialConfig?.port
? this.serialConfig.port.address
: 'unknown port';
this.logger.info(
`<<< Serial connection created for ${boardName} on port ${portName}.`
);
resolve(Status.OK);
});
return;
}
this.disconnect().then(() => resolve(Status.NOT_CONNECTED));
});
});
};
const status = await Promise.race([
writeTimeout,
writePromise(this.serialConnection),
]);
if (status === Status.NOT_CONNECTED) {
this.disconnect();
}
return status;
}
async disconnect(reason?: SerialError): Promise<Status> {
try {
if (this.onMessageReceived) {
this.onMessageReceived.dispose();
this.onMessageReceived = null;
}
if (this.flushMessagesInterval) {
clearInterval(this.flushMessagesInterval);
this.flushMessagesInterval = null;
}
public async disconnect(reason?: SerialError): Promise<Status> {
return new Promise<Status>((resolve) => {
try {
if (this.onMessageReceived) {
this.onMessageReceived.dispose();
this.onMessageReceived = null;
}
if (this.flushMessagesInterval) {
clearInterval(this.flushMessagesInterval);
this.flushMessagesInterval = null;
}
if (
!this.serialConnection &&
reason &&
reason.code === SerialError.ErrorCodes.CLIENT_CANCEL
) {
return Status.OK;
if (
!this.serialConnection &&
reason &&
reason.code === SerialError.ErrorCodes.CLIENT_CANCEL
) {
resolve(Status.OK);
return;
}
this.logger.info('>>> Disposing serial connection...');
if (!this.serialConnection) {
this.logger.warn('<<< Not connected. Nothing to dispose.');
resolve(Status.NOT_CONNECTED);
return;
}
const { duplex, config } = this.serialConnection;
this.logger.info(
`<<< Disposed serial connection for ${Board.toString(config.board, {
useFqbn: false,
})} on port ${config.port.address}.`
);
duplex.cancel();
} finally {
this.serialConnection = undefined;
this.updateWsConfigParam({ connected: !!this.serialConnection });
this.messages.length = 0;
setTimeout(() => {
resolve(Status.OK);
}, 200);
}
this.logger.info('>>> Disposing serial connection...');
if (!this.serialConnection) {
this.logger.warn('<<< Not connected. Nothing to dispose.');
return Status.NOT_CONNECTED;
}
const { duplex, config } = this.serialConnection;
duplex.cancel();
this.logger.info(
`<<< Disposed serial connection for ${Board.toString(config.board, {
useFqbn: false,
})} on port ${Port.toString(config.port)}.`
);
this.serialConnection = undefined;
return Status.OK;
} finally {
this.messages.length = 0;
}
});
}
async sendMessageToSerial(message: string): Promise<Status> {
@@ -312,3 +374,24 @@ export class SerialServiceImpl implements SerialService {
}
}
}
// converts 'ab\nc\nd' => [ab\n,c\n,d]
function stringToArray(string: string, separator = '\n') {
const retArray: string[] = [];
let prevChar = separator;
for (let i = 0; i < string.length; i++) {
const currChar = string[i];
if (prevChar === separator) {
retArray.push(currChar);
} else {
const lastWord = retArray[retArray.length - 1];
retArray[retArray.length - 1] = lastWord + currChar;
}
prevChar = currChar;
}
return retArray;
}

View File

@@ -11,6 +11,9 @@ export default class WebSocketServiceImpl implements WebSocketService {
protected readonly onMessage = new Emitter<string>();
public readonly onMessageReceived = this.onMessage.event;
protected readonly onConnectedClients = new Emitter<number>();
public readonly onClientsNumberChanged = this.onConnectedClients.event;
constructor() {
this.wsClients = [];
this.server = new WebSocket.Server({ port: 0 });
@@ -21,8 +24,11 @@ export default class WebSocketServiceImpl implements WebSocketService {
private addClient(ws: WebSocket): void {
this.wsClients.push(ws);
this.onConnectedClients.fire(this.wsClients.length);
ws.onclose = () => {
this.wsClients.splice(this.wsClients.indexOf(ws), 1);
this.onConnectedClients.fire(this.wsClients.length);
};
ws.onmessage = (res) => {
@@ -30,6 +36,10 @@ export default class WebSocketServiceImpl implements WebSocketService {
};
}
getConnectedClientsNumber(): number {
return this.wsClients.length;
}
getAddress(): WebSocket.AddressInfo {
return this.server.address() as WebSocket.AddressInfo;
}

View File

@@ -6,4 +6,6 @@ export interface WebSocketService {
getAddress(): WebSocket.AddressInfo;
sendMessage(message: string): void;
onMessageReceived: Event<string>;
onClientsNumberChanged: Event<number>;
getConnectedClientsNumber(): number;
}

View File

@@ -4,11 +4,20 @@ import { Board, BoardsPackage, Port } from '../../../common/protocol';
export const aBoard: Board = {
fqbn: 'some:board:fqbn',
name: 'Some Arduino Board',
port: { address: '/lol/port1234', protocol: 'serial' },
port: {
id: '/lol/port1234|serial',
address: '/lol/port1234',
addressLabel: '/lol/port1234',
protocol: 'serial',
protocolLabel: 'Serial Port (USB)',
},
};
export const aPort: Port = {
id: aBoard.port!.id,
address: aBoard.port!.address,
addressLabel: aBoard.port!.addressLabel,
protocol: aBoard.port!.protocol,
protocolLabel: aBoard.port!.protocolLabel,
};
export const aBoardConfig: BoardsConfig.Config = {
selectedBoard: aBoard,
@@ -17,11 +26,20 @@ export const aBoardConfig: BoardsConfig.Config = {
export const anotherBoard: Board = {
fqbn: 'another:board:fqbn',
name: 'Another Arduino Board',
port: { address: '/kek/port5678', protocol: 'serial' },
port: {
id: '/kek/port5678|serial',
address: '/kek/port5678',
addressLabel: '/kek/port5678',
protocol: 'serial',
protocolLabel: 'Serial Port (USB)',
},
};
export const anotherPort: Port = {
id: anotherBoard.port!.id,
address: anotherBoard.port!.address,
addressLabel: anotherBoard.port!.addressLabel,
protocol: anotherBoard.port!.protocol,
protocolLabel: anotherBoard.port!.protocolLabel,
};
export const anotherBoardConfig: BoardsConfig.Config = {
selectedBoard: anotherBoard,

View File

@@ -1,375 +0,0 @@
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
const disableJSDOM = enableJSDOM();
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
import { ApplicationProps } from '@theia/application-package/lib/application-props';
FrontendApplicationConfigProvider.set({
...ApplicationProps.DEFAULT.frontend.config,
});
import { MessageService } from '@theia/core';
import { BoardsServiceProvider } from '../../browser/boards/boards-service-provider';
import {
BoardsService,
CoreService,
SerialService,
SerialServiceClient,
Status,
} from '../../common/protocol';
import { IMock, It, Mock, Times } from 'typemoq';
import {
Serial,
SerialConnectionManager,
} from '../../browser/serial/serial-connection-manager';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { SerialModel } from '../../browser/serial/serial-model';
import {
aBoardConfig,
anotherBoardConfig,
anotherPort,
aPort,
} from './fixtures/boards';
import { BoardsConfig } from '../../browser/boards/boards-config';
import {
anotherSerialConfig,
aSerialConfig,
WebSocketMock,
} from './fixtures/serial';
import { expect } from 'chai';
import { tick } from '../utils';
disableJSDOM();
global.WebSocket = WebSocketMock as any;
describe.only('SerialConnectionManager', () => {
let subject: SerialConnectionManager;
let serialModel: IMock<SerialModel>;
let serialService: IMock<SerialService>;
let serialServiceClient: IMock<SerialServiceClient>;
let boardsService: IMock<BoardsService>;
let boardsServiceProvider: IMock<BoardsServiceProvider>;
let messageService: IMock<MessageService>;
let themeService: IMock<ThemeService>;
let core: IMock<CoreService>;
let handleBoardConfigChange: (
boardsConfig: BoardsConfig.Config
) => Promise<void>;
let handleWebSocketChanged: (wsPort: number) => void;
const wsPort = 1234;
beforeEach(() => {
serialModel = Mock.ofType<SerialModel>();
serialService = Mock.ofType<SerialService>();
serialServiceClient = Mock.ofType<SerialServiceClient>();
boardsService = Mock.ofType<BoardsService>();
boardsServiceProvider = Mock.ofType<BoardsServiceProvider>();
messageService = Mock.ofType<MessageService>();
themeService = Mock.ofType<ThemeService>();
core = Mock.ofType<CoreService>();
boardsServiceProvider
.setup((b) => b.boardsConfig)
.returns(() => aBoardConfig);
boardsServiceProvider
.setup((b) => b.onBoardsConfigChanged(It.isAny()))
.returns((h) => {
handleBoardConfigChange = h;
return { dispose: () => {} };
});
boardsServiceProvider
.setup((b) => b.canUploadTo(It.isAny(), It.isValue({ silent: false })))
.returns(() => true);
boardsService
.setup((b) => b.getAvailablePorts())
.returns(() => Promise.resolve([aPort, anotherPort]));
serialModel
.setup((m) => m.baudRate)
.returns(() => aSerialConfig.baudRate || 9600);
serialServiceClient
.setup((m) => m.onWebSocketChanged(It.isAny()))
.returns((h) => {
handleWebSocketChanged = h;
return { dispose: () => {} };
});
serialService
.setup((m) => m.disconnect())
.returns(() => Promise.resolve(Status.OK));
core.setup((u) => u.isUploading()).returns(() => Promise.resolve(false));
subject = new SerialConnectionManager(
serialModel.object,
serialService.object,
serialServiceClient.object,
boardsService.object,
boardsServiceProvider.object,
messageService.object,
themeService.object,
core.object
);
});
context('when no serial config is set', () => {
context('and the serial is NOT open', () => {
context('and it tries to open the serial plotter', () => {
it('should not try to connect and show an error', async () => {
await subject.openSerial(Serial.Type.Plotter);
messageService.verify((m) => m.error(It.isAnyString()), Times.once());
serialService.verify((m) => m.disconnect(), Times.never());
serialService.verify((m) => m.connect(It.isAny()), Times.never());
});
});
context('and a serial config is set', () => {
it('should not try to reconnect', async () => {
await handleBoardConfigChange(aBoardConfig);
serialService.verify((m) => m.disconnect(), Times.never());
serialService.verify((m) => m.connect(It.isAny()), Times.never());
expect(subject.getConfig()).to.deep.equal(aSerialConfig);
});
});
});
});
context('when a serial config is set', () => {
beforeEach(() => {
subject.setConfig(aSerialConfig);
});
context('and the serial is NOT open', () => {
context('and it tries to disconnect', () => {
it('should do nothing', async () => {
const status = await subject.disconnect();
expect(status).to.be.ok;
expect(subject.connected).to.be.false;
});
});
context('and the config changes', () => {
beforeEach(() => {
subject.setConfig(anotherSerialConfig);
});
it('should not try to reconnect', async () => {
await tick();
messageService.verify(
(m) => m.error(It.isAnyString()),
Times.never()
);
serialService.verify((m) => m.disconnect(), Times.never());
serialService.verify((m) => m.connect(It.isAny()), Times.never());
});
});
context(
'and the connection to the serial succeeds with the config',
() => {
beforeEach(() => {
serialService
.setup((m) => m.connect(It.isValue(aSerialConfig)))
.returns(() => {
handleWebSocketChanged(wsPort);
return Promise.resolve(Status.OK);
});
});
context('and it tries to open the serial plotter', () => {
let status: Status;
beforeEach(async () => {
status = await subject.openSerial(Serial.Type.Plotter);
});
it('should successfully connect to the serial', async () => {
messageService.verify(
(m) => m.error(It.isAnyString()),
Times.never()
);
serialService.verify((m) => m.disconnect(), Times.never());
serialService.verify((m) => m.connect(It.isAny()), Times.once());
expect(status).to.be.ok;
expect(subject.connected).to.be.true;
expect(subject.getWsPort()).to.equal(wsPort);
expect(subject.isSerialOpen()).to.be.true;
expect(subject.isWebSocketConnected()).to.be.false;
});
context('and it tries to open the serial monitor', () => {
let status: Status;
beforeEach(async () => {
status = await subject.openSerial(Serial.Type.Monitor);
});
it('should open it using the same serial connection', () => {
messageService.verify(
(m) => m.error(It.isAnyString()),
Times.never()
);
serialService.verify((m) => m.disconnect(), Times.never());
serialService.verify(
(m) => m.connect(It.isAny()),
Times.once()
);
expect(status).to.be.ok;
expect(subject.connected).to.be.true;
expect(subject.isSerialOpen()).to.be.true;
});
it('should create a websocket connection', () => {
expect(subject.getWsPort()).to.equal(wsPort);
expect(subject.isWebSocketConnected()).to.be.true;
});
context('and then it closes the serial plotter', () => {
beforeEach(async () => {
status = await subject.closeSerial(Serial.Type.Plotter);
});
it('should close the plotter without disconnecting from the serial', () => {
messageService.verify(
(m) => m.error(It.isAnyString()),
Times.never()
);
serialService.verify((m) => m.disconnect(), Times.never());
serialService.verify(
(m) => m.connect(It.isAny()),
Times.once()
);
expect(status).to.be.ok;
expect(subject.connected).to.be.true;
expect(subject.isSerialOpen()).to.be.true;
expect(subject.getWsPort()).to.equal(wsPort);
});
it('should not close the websocket connection', () => {
expect(subject.isWebSocketConnected()).to.be.true;
});
});
context('and then it closes the serial monitor', () => {
beforeEach(async () => {
status = await subject.closeSerial(Serial.Type.Monitor);
});
it('should close the monitor without disconnecting from the serial', () => {
messageService.verify(
(m) => m.error(It.isAnyString()),
Times.never()
);
serialService.verify((m) => m.disconnect(), Times.never());
serialService.verify(
(m) => m.connect(It.isAny()),
Times.once()
);
expect(status).to.be.ok;
expect(subject.connected).to.be.true;
expect(subject.getWsPort()).to.equal(wsPort);
expect(subject.isSerialOpen()).to.be.true;
});
it('should close the websocket connection', () => {
expect(subject.isWebSocketConnected()).to.be.false;
});
});
});
context('and then it closes the serial plotter', () => {
beforeEach(async () => {
status = await subject.closeSerial(Serial.Type.Plotter);
});
it('should successfully disconnect from the serial', () => {
messageService.verify(
(m) => m.error(It.isAnyString()),
Times.never()
);
serialService.verify((m) => m.disconnect(), Times.once());
serialService.verify(
(m) => m.connect(It.isAny()),
Times.once()
);
expect(status).to.be.ok;
expect(subject.connected).to.be.false;
expect(subject.getWsPort()).to.be.undefined;
expect(subject.isSerialOpen()).to.be.false;
expect(subject.isWebSocketConnected()).to.be.false;
});
});
context('and the config changes', () => {
beforeEach(() => {
subject.setConfig(anotherSerialConfig);
});
it('should try to reconnect', async () => {
await tick();
messageService.verify(
(m) => m.error(It.isAnyString()),
Times.never()
);
serialService.verify((m) => m.disconnect(), Times.once());
serialService.verify(
(m) => m.connect(It.isAny()),
Times.exactly(2)
);
});
});
});
}
);
context(
'and the connection to the serial does NOT succeed with the config',
() => {
beforeEach(() => {
serialService
.setup((m) => m.connect(It.isValue(aSerialConfig)))
.returns(() => {
return Promise.resolve(Status.NOT_CONNECTED);
});
serialService
.setup((m) => m.connect(It.isValue(anotherSerialConfig)))
.returns(() => {
handleWebSocketChanged(wsPort);
return Promise.resolve(Status.OK);
});
});
context('and it tries to open the serial plotter', () => {
let status: Status;
beforeEach(async () => {
status = await subject.openSerial(Serial.Type.Plotter);
});
it('should fail to connect to the serial', async () => {
messageService.verify(
(m) => m.error(It.isAnyString()),
Times.never()
);
serialService.verify((m) => m.disconnect(), Times.never());
serialService.verify(
(m) => m.connect(It.isValue(aSerialConfig)),
Times.once()
);
expect(status).to.be.false;
expect(subject.connected).to.be.false;
expect(subject.getWsPort()).to.be.undefined;
expect(subject.isSerialOpen()).to.be.true;
});
context(
'and the board config changes with an acceptable one',
() => {
beforeEach(async () => {
await handleBoardConfigChange(anotherBoardConfig);
});
it('should successfully connect to the serial', async () => {
await tick();
messageService.verify(
(m) => m.error(It.isAnyString()),
Times.never()
);
serialService.verify((m) => m.disconnect(), Times.never());
serialService.verify(
(m) => m.connect(It.isValue(anotherSerialConfig)),
Times.once()
);
expect(subject.connected).to.be.true;
expect(subject.getWsPort()).to.equal(wsPort);
expect(subject.isSerialOpen()).to.be.true;
expect(subject.isWebSocketConnected()).to.be.false;
});
}
);
});
}
);
});
});
});

View File

@@ -2,21 +2,17 @@ import * as fs from 'fs';
// import * as net from 'net';
import * as path from 'path';
import * as temp from 'temp';
import { fail } from 'assert';
import { expect } from 'chai';
import { ChildProcess } from 'child_process';
import { safeLoad, safeDump } from 'js-yaml';
import { DaemonError, ArduinoDaemonImpl } from '../../node/arduino-daemon-impl';
import { ArduinoDaemonImpl } from '../../node/arduino-daemon-impl';
import { spawnCommand } from '../../node/exec-util';
import { CLI_CONFIG } from '../../node/cli-config';
const track = temp.track();
class SilentArduinoDaemonImpl extends ArduinoDaemonImpl {
constructor(
private port: string | number,
private logFormat: 'text' | 'json'
) {
constructor(private logFormat: 'text' | 'json') {
super();
}
@@ -24,7 +20,7 @@ class SilentArduinoDaemonImpl extends ArduinoDaemonImpl {
// NOOP
}
async spawnDaemonProcess(): Promise<ChildProcess> {
async spawnDaemonProcess(): Promise<{ daemon: ChildProcess; port: string }> {
return super.spawnDaemonProcess();
}
@@ -32,6 +28,10 @@ class SilentArduinoDaemonImpl extends ArduinoDaemonImpl {
const cliConfigPath = await this.initCliConfig();
return [
'daemon',
'--format',
'jsonmini',
'--port',
'0',
'--config-file',
cliConfigPath,
'-v',
@@ -53,7 +53,7 @@ class SilentArduinoDaemonImpl extends ArduinoDaemonImpl {
encoding: 'utf8',
});
const cliConfig = safeLoad(content) as any;
cliConfig.daemon.port = String(this.port);
// cliConfig.daemon.port = String(this.port);
const modifiedContent = safeDump(cliConfig);
fs.writeFileSync(path.join(destDir, CLI_CONFIG), modifiedContent, {
encoding: 'utf8',
@@ -113,43 +113,23 @@ describe('arduino-daemon-impl', () => {
// }
// });
it('should parse an error - unknown address [json]', async () => {
try {
await new SilentArduinoDaemonImpl('foo', 'json').spawnDaemonProcess();
fail('Expected a failure.');
} catch (e) {
expect(e).to.be.instanceOf(DaemonError);
expect(e.code).to.be.equal(DaemonError.UNKNOWN_ADDRESS);
}
it('should parse the port address when the log format is json', async () => {
const { daemon, port } = await new SilentArduinoDaemonImpl(
'json'
).spawnDaemonProcess();
expect(port).not.to.be.undefined;
expect(port).not.to.be.equal('0');
daemon.kill();
});
it('should parse an error - unknown address [text]', async () => {
try {
await new SilentArduinoDaemonImpl('foo', 'text').spawnDaemonProcess();
fail('Expected a failure.');
} catch (e) {
expect(e).to.be.instanceOf(DaemonError);
expect(e.code).to.be.equal(DaemonError.UNKNOWN_ADDRESS);
}
});
it('should parse the port address when the log format is text', async () => {
const { daemon, port } = await new SilentArduinoDaemonImpl(
'text'
).spawnDaemonProcess();
it('should parse an error - invalid port [json]', async () => {
try {
await new SilentArduinoDaemonImpl(-1, 'json').spawnDaemonProcess();
fail('Expected a failure.');
} catch (e) {
expect(e).to.be.instanceOf(DaemonError);
expect(e.code).to.be.equal(DaemonError.INVALID_PORT);
}
});
it('should parse an error - invalid port [text]', async () => {
try {
await new SilentArduinoDaemonImpl(-1, 'text').spawnDaemonProcess();
fail('Expected a failure.');
} catch (e) {
expect(e).to.be.instanceOf(DaemonError);
expect(e.code).to.be.equal(DaemonError.INVALID_PORT);
}
expect(port).not.to.be.undefined;
expect(port).not.to.be.equal('0');
daemon.kill();
});
});

View File

@@ -22,7 +22,7 @@ describe('getExecPath', () => {
});
it('should resolve clangd', async () => {
const actual = await getExecPath('clangd', onError, '--version', true);
const actual = await getExecPath('clangd', onError, '--version');
const expected = os.platform() === 'win32' ? '\\clangd.exe' : '/clangd';
expect(actual).to.endsWith(expected);
});

View File

@@ -0,0 +1,167 @@
import { SerialServiceImpl } from './../../node/serial/serial-service-impl';
import { IMock, It, Mock } from 'typemoq';
import { createSandbox } from 'sinon';
import * as sinonChai from 'sinon-chai';
import { expect, use } from 'chai';
use(sinonChai);
import { ILogger } from '@theia/core/lib/common/logger';
import { MonitorClientProvider } from '../../node/serial/monitor-client-provider';
import { WebSocketService } from '../../node/web-socket/web-socket-service';
import { MonitorServiceClient } from '../../node/cli-protocol/cc/arduino/cli/monitor/v1/monitor_grpc_pb';
import { Status } from '../../common/protocol';
describe('SerialServiceImpl', () => {
let subject: SerialServiceImpl;
let logger: IMock<ILogger>;
let serialClientProvider: IMock<MonitorClientProvider>;
let webSocketService: IMock<WebSocketService>;
beforeEach(() => {
logger = Mock.ofType<ILogger>();
logger.setup((b) => b.info(It.isAnyString()));
logger.setup((b) => b.warn(It.isAnyString()));
logger.setup((b) => b.error(It.isAnyString()));
serialClientProvider = Mock.ofType<MonitorClientProvider>();
webSocketService = Mock.ofType<WebSocketService>();
subject = new SerialServiceImpl(
logger.object,
serialClientProvider.object,
webSocketService.object
);
});
context('when a serial connection is requested', () => {
const sandbox = createSandbox();
beforeEach(() => {
subject.uploadInProgress = false;
sandbox.spy(subject, 'disconnect');
sandbox.spy(subject, 'updateWsConfigParam');
});
afterEach(function () {
sandbox.restore();
});
context('and an upload is in progress', () => {
beforeEach(async () => {
subject.uploadInProgress = true;
});
it('should not change the connection status', async () => {
await subject.connectSerialIfRequired();
expect(subject.disconnect).to.have.callCount(0);
});
});
context('and there is no upload in progress', () => {
beforeEach(async () => {
subject.uploadInProgress = false;
});
context('and there are 0 attached ws clients', () => {
it('should disconnect', async () => {
await subject.connectSerialIfRequired();
expect(subject.disconnect).to.have.been.calledOnce;
});
});
context('and there are > 0 attached ws clients', () => {
beforeEach(() => {
webSocketService
.setup((b) => b.getConnectedClientsNumber())
.returns(() => 1);
});
it('should not call the disconenct', async () => {
await subject.connectSerialIfRequired();
expect(subject.disconnect).to.have.callCount(0);
});
});
});
});
context('when a disconnection is requested', () => {
const sandbox = createSandbox();
beforeEach(() => { });
afterEach(function () {
sandbox.restore();
});
context('and a serialConnection is not set', () => {
it('should return a NOT_CONNECTED status', async () => {
const status = await subject.disconnect();
expect(status).to.be.equal(Status.NOT_CONNECTED);
});
});
context('and a serialConnection is set', async () => {
beforeEach(async () => {
sandbox.spy(subject, 'updateWsConfigParam');
await subject.disconnect();
});
it('should dispose the serialConnection', async () => {
const serialConnectionOpen = await subject.isSerialPortOpen();
expect(serialConnectionOpen).to.be.false;
});
it('should call updateWsConfigParam with disconnected status', async () => {
expect(subject.updateWsConfigParam).to.be.calledWith({
connected: false,
});
});
});
});
context('when a new config is passed in', () => {
const sandbox = createSandbox();
beforeEach(async () => {
subject.uploadInProgress = false;
webSocketService
.setup((b) => b.getConnectedClientsNumber())
.returns(() => 1);
serialClientProvider
.setup((b) => b.client())
.returns(async () => {
return {
streamingOpen: () => {
return {
on: (str: string, cb: any) => { },
write: (chunk: any, cb: any) => {
cb();
},
cancel: () => { },
};
},
} as MonitorServiceClient;
});
sandbox.spy(subject, 'disconnect');
await subject.setSerialConfig({
board: { name: 'test' },
port: { id: 'test|test', address: 'test', addressLabel: 'test', protocol: 'test', protocolLabel: 'test' },
});
});
afterEach(function () {
sandbox.restore();
subject.dispose();
});
it('should disconnect from previous connection', async () => {
expect(subject.disconnect).to.be.called;
});
it('should create the serialConnection', async () => {
const serialConnectionOpen = await subject.isSerialPortOpen();
expect(serialConnectionOpen).to.be.true;
});
});
});

Some files were not shown because too many files have changed in this diff Show More