mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-10-06 10:08:31 +00:00
Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
96b5edf427 | ||
![]() |
a5a6a0b611 | ||
![]() |
2a27a14a68 | ||
![]() |
f2d492b5dc | ||
![]() |
5979e5aad2 | ||
![]() |
baa9b5f7ab | ||
![]() |
481497e384 | ||
![]() |
0207778373 | ||
![]() |
d79f32efd7 | ||
![]() |
3ab03dd62f | ||
![]() |
bc3cb0c230 | ||
![]() |
473cb11053 | ||
![]() |
0a87fd00f3 | ||
![]() |
9b1f15def8 | ||
![]() |
77b430675d | ||
![]() |
f660058c75 | ||
![]() |
9ecff86bbe | ||
![]() |
5ab3a747a6 | ||
![]() |
877c1a1559 | ||
![]() |
2f9bf86d75 | ||
![]() |
112153fb96 | ||
![]() |
69ac1f4779 | ||
![]() |
a20899ff43 | ||
![]() |
ef2be1c086 | ||
![]() |
af33dce0f6 | ||
![]() |
b3b22795f8 | ||
![]() |
8a0454db51 | ||
![]() |
f1a5d87ab2 | ||
![]() |
cf0a2161af | ||
![]() |
dcebd863cc | ||
![]() |
e8477b14f3 | ||
![]() |
0230071b5f | ||
![]() |
1d88263c85 | ||
![]() |
a71ac4c44d | ||
![]() |
66fc27e58c | ||
![]() |
bc365f4a8d | ||
![]() |
a5891f9884 | ||
![]() |
fcdf16a937 | ||
![]() |
e0b6dbbf2a | ||
![]() |
9529e78647 | ||
![]() |
51da3c0668 | ||
![]() |
c00d3d33dd | ||
![]() |
cfa9b8aea6 | ||
![]() |
6106e9ff1a | ||
![]() |
b1d9f65a0d | ||
![]() |
f4008100e1 | ||
![]() |
11a6959a24 | ||
![]() |
3c6e11832b | ||
![]() |
c064673ce1 | ||
![]() |
cc5764e536 | ||
![]() |
9131f2d09e | ||
![]() |
0b6fc0b973 | ||
![]() |
c91fe2d775 | ||
![]() |
bbded57ae4 | ||
![]() |
a8ae0bb4e0 | ||
![]() |
49d12d99ff | ||
![]() |
767b09d2f1 | ||
![]() |
88397931c5 | ||
![]() |
5ddab1ded7 | ||
![]() |
f0d9894a16 |
115
.github/workflows/build.yml
vendored
115
.github/workflows/build.yml
vendored
@@ -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 }}
|
||||
|
4
.github/workflows/check-i18n-task.yml
vendored
4
.github/workflows/check-i18n-task.yml
vendored
@@ -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
|
||||
|
45
.github/workflows/compose-full-changelog.yaml
vendored
Normal file
45
.github/workflows/compose-full-changelog.yaml
vendored
Normal 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 }}
|
4
.github/workflows/i18n-nightly-push.yml
vendored
4
.github/workflows/i18n-nightly-push.yml
vendored
@@ -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
|
||||
|
4
.github/workflows/i18n-weekly-pull.yml
vendored
4
.github/workflows/i18n-weekly-pull.yml
vendored
@@ -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
|
||||
|
4
.github/workflows/sync-labels.yml
vendored
4
.github/workflows/sync-labels.yml
vendored
@@ -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
20
.vscode/launch.json
vendored
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
37
BUILDING.md
37
BUILDING.md
@@ -40,22 +40,31 @@ The _frontend_ is running as an Electron renderer process and can invoke service
|
||||
## Build from source
|
||||
|
||||
If you’re 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.
|
||||
|
||||
|
42
README.md
42
README.md
@@ -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.
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
116
arduino-ide-extension/scripts/compose-changelog.js
Executable file
116
arduino-ide-extension/scripts/compose-changelog.js
Executable 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;
|
||||
};
|
@@ -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.
|
||||
|
@@ -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}`);
|
||||
};
|
||||
|
@@ -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',
|
||||
|
@@ -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();
|
||||
});
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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');
|
||||
|
@@ -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(
|
||||
|
@@ -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 {
|
||||
|
@@ -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> {
|
||||
|
28
arduino-ide-extension/src/browser/components/ProgressBar.tsx
Normal file
28
arduino-ide-extension/src/browser/components/ProgressBar.tsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -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';
|
||||
|
@@ -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,
|
||||
|
@@ -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';
|
||||
|
@@ -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';
|
||||
|
@@ -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
|
||||
),
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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';
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
),
|
||||
|
@@ -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,
|
||||
|
@@ -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 {
|
||||
|
@@ -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',
|
||||
};
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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) };
|
||||
|
@@ -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>
|
||||
)}
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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',
|
||||
};
|
||||
}
|
@@ -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'
|
||||
);
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
@@ -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'
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -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}
|
||||
|
@@ -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(
|
||||
|
@@ -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}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
arduino-ide-extension/src/browser/style/ide-logo.png
Normal file
BIN
arduino-ide-extension/src/browser/style/ide-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@@ -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;
|
||||
}
|
@@ -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;
|
||||
|
32
arduino-ide-extension/src/browser/style/progress-bar.css
Normal file
32
arduino-ide-extension/src/browser/style/progress-bar.css
Normal 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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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'
|
||||
|
@@ -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}
|
||||
|
@@ -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';
|
||||
|
@@ -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 {
|
||||
|
@@ -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()
|
||||
);
|
||||
|
||||
|
@@ -2,4 +2,5 @@ export const ArduinoDaemonPath = '/services/arduino-daemon';
|
||||
export const ArduinoDaemon = Symbol('ArduinoDaemon');
|
||||
export interface ArduinoDaemon {
|
||||
isRunning(): Promise<boolean>;
|
||||
getPort(): Promise<string>;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
71
arduino-ide-extension/src/common/protocol/ide-updater.ts
Normal file
71
arduino-ide-extension/src/common/protocol/ide-updater.ts
Normal 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;
|
||||
}
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
|
@@ -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(
|
||||
[],
|
||||
|
@@ -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();
|
||||
});
|
||||
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
|
@@ -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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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.
|
||||
|
@@ -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;
|
||||
|
@@ -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.
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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();
|
||||
});
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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)),
|
||||
]);
|
||||
|
@@ -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}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -6,4 +6,6 @@ export interface WebSocketService {
|
||||
getAddress(): WebSocket.AddressInfo;
|
||||
sendMessage(message: string): void;
|
||||
onMessageReceived: Event<string>;
|
||||
onClientsNumberChanged: Event<number>;
|
||||
getConnectedClientsNumber(): number;
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
|
167
arduino-ide-extension/src/test/node/serial-service-impl.test.ts
Normal file
167
arduino-ide-extension/src/test/node/serial-service-impl.test.ts
Normal 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
Reference in New Issue
Block a user