mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-09-30 07:08:33 +00:00
Compare commits
54 Commits
0.1.3
...
2.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f34f594653 | ||
![]() |
1dc7a89dd9 | ||
![]() |
1280a344a7 | ||
![]() |
f1c80041fe | ||
![]() |
ec1abcc989 | ||
![]() |
1c03d12165 | ||
![]() |
9180f4e378 | ||
![]() |
516c79276c | ||
![]() |
e639d7da06 | ||
![]() |
754097877b | ||
![]() |
b847cff615 | ||
![]() |
b3deb2fd34 | ||
![]() |
b2641f56be | ||
![]() |
ba8885c8c8 | ||
![]() |
3e92567d52 | ||
![]() |
19613de1b4 | ||
![]() |
01ef138d9a | ||
![]() |
39b8a602c7 | ||
![]() |
57c50fefe3 | ||
![]() |
23877f162c | ||
![]() |
96f0722d56 | ||
![]() |
48c6c53b9b | ||
![]() |
b8647f16ad | ||
![]() |
cfe9e8ec95 | ||
![]() |
291179489f | ||
![]() |
71cfa06fc2 | ||
![]() |
68b1f8d4f2 | ||
![]() |
e957ac4331 | ||
![]() |
b65867d2f4 | ||
![]() |
a8e60698a8 | ||
![]() |
52b0fd35a3 | ||
![]() |
b1ab6df8b7 | ||
![]() |
911875665d | ||
![]() |
113fe38850 | ||
![]() |
770e0b592a | ||
![]() |
79bf0a123f | ||
![]() |
8eaf03a299 | ||
![]() |
f36d261dcd | ||
![]() |
a99093624f | ||
![]() |
1f544b2656 | ||
![]() |
1742c53015 | ||
![]() |
e33af0d78a | ||
![]() |
24dfffa976 | ||
![]() |
6626701bc9 | ||
![]() |
66b711f43c | ||
![]() |
c6b125011e | ||
![]() |
f6b5dd24e2 | ||
![]() |
14919bba1b | ||
![]() |
0b89cc4a3b | ||
![]() |
284181b874 | ||
![]() |
db2967084f | ||
![]() |
1b6d9eccdc | ||
![]() |
258b1e903e | ||
![]() |
00a3ee34c8 |
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows]
|
||||
- Version: [e.g. 2.0.0]
|
||||
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
23
.github/workflows/build.yml
vendored
23
.github/workflows/build.yml
vendored
@@ -1,15 +1,15 @@
|
||||
name: Arduino Pro IDE
|
||||
name: Arduino IDE
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+*'
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
schedule:
|
||||
- cron: '0 3 * * *' # run every day at 3AM (https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule)
|
||||
|
||||
@@ -22,7 +22,6 @@ jobs:
|
||||
- os: windows-latest
|
||||
- os: ubuntu-latest
|
||||
- os: macos-latest
|
||||
# - os: rsora-rpi-arm # self-hosted armhf
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
timeout-minutes: 90
|
||||
|
||||
@@ -44,13 +43,12 @@ jobs:
|
||||
- name: Package
|
||||
shell: bash
|
||||
env:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
AC_USERNAME: ${{ secrets.AC_USERNAME }}
|
||||
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
|
||||
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/master') }}
|
||||
IS_NIGHTLY: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }}
|
||||
IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
run: |
|
||||
# See: https://www.electron.build/code-signing
|
||||
@@ -99,7 +97,7 @@ jobs:
|
||||
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-pro-ide/releases/tag/$LATEST_TAG)")
|
||||
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
|
||||
@@ -114,7 +112,7 @@ jobs:
|
||||
echo "$BODY" > CHANGELOG.txt
|
||||
|
||||
- name: Upload Changelog [GitHub Actions]
|
||||
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master')
|
||||
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: build-artifacts
|
||||
@@ -122,7 +120,7 @@ jobs:
|
||||
|
||||
publish:
|
||||
needs: changelog
|
||||
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master')
|
||||
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download [GitHub Actions]
|
||||
@@ -136,7 +134,7 @@ jobs:
|
||||
env:
|
||||
PLUGIN_SOURCE: "build-artifacts/*"
|
||||
PLUGIN_STRIP_PREFIX: "build-artifacts/"
|
||||
PLUGIN_TARGET: "/arduino-pro-ide/nightly"
|
||||
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 }}
|
||||
@@ -160,8 +158,7 @@ jobs:
|
||||
- name: Publish Release [GitHub]
|
||||
uses: svenstaro/upload-release-action@2.2.0
|
||||
with:
|
||||
repo_token: ${{ secrets.RELEASE_TOKEN }}
|
||||
repo_name: arduino/arduino-pro-ide
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release_name: ${{ steps.tag_name.outputs.TAG_NAME }}
|
||||
file: build-artifacts/*
|
||||
tag: ${{ github.ref }}
|
||||
@@ -173,7 +170,7 @@ jobs:
|
||||
env:
|
||||
PLUGIN_SOURCE: "build-artifacts/*"
|
||||
PLUGIN_STRIP_PREFIX: "build-artifacts/"
|
||||
PLUGIN_TARGET: "/arduino-pro-ide"
|
||||
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 }}
|
||||
|
24
.gitpod.yml
24
.gitpod.yml
@@ -1,24 +0,0 @@
|
||||
image:
|
||||
file: Dockerfile
|
||||
|
||||
ports:
|
||||
- port: 3000
|
||||
onOpen: open-preview
|
||||
- port: 5900
|
||||
onOpen: ignore
|
||||
- port: 6080
|
||||
onOpen: ignore
|
||||
|
||||
tasks:
|
||||
- init: >
|
||||
yarn &&
|
||||
yarn --cwd ./browser-app start
|
||||
|
||||
github:
|
||||
prebuilds:
|
||||
master: true
|
||||
branches: true
|
||||
pullRequests: true
|
||||
pullRequestsFromForks: true
|
||||
addComment: false
|
||||
addBadge: false
|
22
.vscode/tasks.json
vendored
22
.vscode/tasks.json
vendored
@@ -4,7 +4,7 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Arduino Pro IDE - Rebuild Electron App",
|
||||
"label": "Arduino IDE - Rebuild Electron App",
|
||||
"type": "shell",
|
||||
"command": "yarn rebuild:browser && yarn rebuild:electron",
|
||||
"group": "build",
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Arduino Pro IDE - Start Browser App",
|
||||
"label": "Arduino IDE - Start Browser App",
|
||||
"type": "shell",
|
||||
"command": "yarn --cwd ./browser-app start",
|
||||
"group": "build",
|
||||
@@ -26,7 +26,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Arduino Pro IDE - Watch IDE Extension",
|
||||
"label": "Arduino IDE - Watch IDE Extension",
|
||||
"type": "shell",
|
||||
"command": "yarn --cwd ./arduino-ide-extension watch",
|
||||
"group": "build",
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
}
|
||||
{
|
||||
"label": "Arduino Pro IDE - Watch Browser App",
|
||||
"label": "Arduino IDE - Watch Browser App",
|
||||
"type": "shell",
|
||||
"command": "yarn --cwd ./browser-app watch",
|
||||
"group": "build",
|
||||
@@ -48,7 +48,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Arduino Pro IDE - Watch Electron App",
|
||||
"label": "Arduino IDE - Watch Electron App",
|
||||
"type": "shell",
|
||||
"command": "yarn --cwd ./electron-app watch",
|
||||
"group": "build",
|
||||
@@ -59,19 +59,19 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Arduino Pro IDE - Watch All [Browser]",
|
||||
"label": "Arduino IDE - Watch All [Browser]",
|
||||
"type": "shell",
|
||||
"dependsOn": [
|
||||
"Arduino Pro IDE - Watch IDE Extension",
|
||||
"Arduino Pro IDE - Watch Browser App"
|
||||
"Arduino IDE - Watch IDE Extension",
|
||||
"Arduino IDE - Watch Browser App"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Arduino Pro IDE - Watch All [Electron]",
|
||||
"label": "Arduino IDE - Watch All [Electron]",
|
||||
"type": "shell",
|
||||
"dependsOn": [
|
||||
"Arduino Pro IDE - Watch IDE Extension",
|
||||
"Arduino Pro IDE - Watch Electron App"
|
||||
"Arduino IDE - Watch IDE Extension",
|
||||
"Arduino IDE - Watch Electron App"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
78
README.md
78
README.md
@@ -1,10 +1,10 @@
|
||||
# Arduino Pro IDE
|
||||
# Arduino IDE
|
||||
|
||||
[](https://github.com/bcmi-labs/arduino-editor/actions?query=workflow%3A%22Arduino+Pro+IDE%22)
|
||||
[](https://github.com/arduino/arduino-ide/actions?query=workflow%3A%22Arduino+IDE%22)
|
||||
|
||||
### Download
|
||||
|
||||
You can download the latest version of the Arduino Pro IDE application for the supported platforms from the [GitHub release page](https://github.com/arduino/arduino-pro-ide/releases) or following the links in the following table.
|
||||
You can download the latest version of the Arduino IDE application for the supported platforms from the [GitHub release page](https://github.com/arduino/arduino-ide/releases) or following the links in the following table.
|
||||
|
||||
#### Latest version
|
||||
|
||||
@@ -15,20 +15,20 @@ Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...]
|
||||
Windows | | [Windows 64 bit installer]<br />[Windows 64 bit MSI]<br />[Windows 64 bit ZIP] |
|
||||
macOS | | [macOS 64 bit] |
|
||||
|
||||
[🚧 Work in progress...]: https://github.com/arduino/arduino-pro-ide/issues/287
|
||||
[Linux 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/arduino-pro-ide_latest_Linux_64bit.zip
|
||||
[Windows 64 bit installer]: https://downloads.arduino.cc/arduino-pro-ide/arduino-pro-ide_latest_Windows_64bit.exe
|
||||
[Windows 64 bit MSI]: https://downloads.arduino.cc/arduino-pro-ide/arduino-pro-ide_latest_Windows_64bit.msi
|
||||
[Windows 64 bit ZIP]: https://downloads.arduino.cc/arduino-pro-ide/arduino-pro-ide_latest_Windows_64bit.zip
|
||||
[macOS 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/arduino-pro-ide_latest_macOS_64bit.dmg
|
||||
[🚧 Work in progress...]: https://github.com/arduino/arduino-ide/issues/287
|
||||
[Linux 64 bit]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_Linux_64bit.zip
|
||||
[Windows 64 bit installer]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_Windows_64bit.exe
|
||||
[Windows 64 bit MSI]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_Windows_64bit.msi
|
||||
[Windows 64 bit ZIP]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_Windows_64bit.zip
|
||||
[macOS 64 bit]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_macOS_64bit.dmg
|
||||
|
||||
#### Previous versions
|
||||
|
||||
These are available from the [GitHub releases page](https://github.com/arduino/arduino-pro-ide/releases).
|
||||
These are available from the [GitHub releases page](https://github.com/arduino/arduino-ide/releases).
|
||||
|
||||
#### Nightly builds
|
||||
|
||||
These builds are generated every day at 03:00 GMT from the `master` branch and
|
||||
These builds are generated every day at 03:00 GMT from the `main` branch and
|
||||
should be considered unstable. In order to get the latest nightly build
|
||||
available for the supported platform, use the following links:
|
||||
|
||||
@@ -39,12 +39,12 @@ 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-pro-ide/issues/287
|
||||
[Nightly Linux 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/nightly/arduino-pro-ide_nightly-latest_Linux_64bit.zip
|
||||
[Nightly Windows 64 bit installer]: https://downloads.arduino.cc/arduino-pro-ide/nightly/arduino-pro-ide_nightly-latest_Windows_64bit.exe
|
||||
[Nightly Windows 64 bit MSI]: https://downloads.arduino.cc/arduino-pro-ide/nightly/arduino-pro-ide_nightly-latest_Windows_64bit.msi
|
||||
[Nightly Windows 64 bit ZIP]: https://downloads.arduino.cc/arduino-pro-ide/nightly/arduino-pro-ide_nightly-latest_Windows_64bit.zip
|
||||
[Nightly macOS 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/nightly/arduino-pro-ide_nightly-latest_macOS_64bit.dmg
|
||||
[🚧 Work in progress...]: https://github.com/arduino/arduino-ide/issues/287
|
||||
[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
|
||||
@@ -54,7 +54,7 @@ macOS | | [Nightly macOS 64 bit]
|
||||
### 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 Pro 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.
|
||||
|
||||
### Build
|
||||
```sh
|
||||
@@ -75,8 +75,8 @@ yarn start
|
||||
|
||||
This project is built on [GitHub Actions](https://github.com/bcmi-labs/arduino-editor/actions?query=workflow%3A%22Arduino+Pro+IDE%22).
|
||||
|
||||
- _Snapshot_ builds run when changes are pushed to the `master` branch, or when a PR is created against the `master` branch. For the sake of the review and verification process, the build artifacts can be downloaded from the GitHub Actions page. Note: [due to a limitation](https://github.com/actions/upload-artifact/issues/80#issuecomment-630030144) with the GH Actions UI, you cannot download a particular build, but you have to get all together inside the `build-artifacts.zip`.
|
||||
- _Nightly_ builds run every day at 03:00 GMT from the `master` branch.
|
||||
- _Snapshot_ builds run when changes are pushed to the `main` branch, or when a PR is created against the `main` branch. For the sake of the review and verification process, the build artifacts can be downloaded from the GitHub Actions page. Note: [due to a limitation](https://github.com/actions/upload-artifact/issues/80#issuecomment-630030144) with the GH Actions UI, you cannot download a particular build, but you have to get all together inside the `build-artifacts.zip`.
|
||||
- _Nightly_ builds run every day at 03:00 GMT from the `main` branch.
|
||||
- _Release_ builds run when a new tag is pushed to the remote. The tag must follow the [semver](https://semver.org/). For instance, `1.2.3` is a correct tag, but `v2.3.4` won't work. Steps to trigger a new release build:
|
||||
- Create a local tag:
|
||||
```sh
|
||||
@@ -87,6 +87,38 @@ This project is built on [GitHub Actions](https://github.com/bcmi-labs/arduino-e
|
||||
git push origin 1.2.3
|
||||
```
|
||||
|
||||
### Creating a GH release
|
||||
This section guides you through how to create a new release. Let's assume the current version is `0.1.3` and you want to release `0.2.0`.
|
||||
|
||||
- Make sure the `main` state represents what you want to release and you're on `main`.
|
||||
- Prepare a release-candidate build on a branch:
|
||||
```bash
|
||||
git branch 0.2.0-rc \
|
||||
&& git checkout 0.2.0-rc
|
||||
```
|
||||
- Bump up the version number. It must be a valid [semver](https://semver.org/) and must be greater than the current one:
|
||||
```bash
|
||||
yarn update:version 0.2.0
|
||||
```
|
||||
- This should generate multiple outgoing changes with the version update.
|
||||
- Commit your changes and push to the remote:
|
||||
```bash
|
||||
git add . \
|
||||
&& git commit -s -m "Updated versions to 0.2.0" \
|
||||
&& git push
|
||||
```
|
||||
- Create the GH PR the workflow starts automatically.
|
||||
- Once you're happy with the RC, merge the changes to the `main`.
|
||||
- Create a tag and push it:
|
||||
```bash
|
||||
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 Pro IDE [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.
|
||||
|
||||
|
||||
### FAQ
|
||||
|
||||
- Q: Can I manually change the version of the [`arduino-cli`](https://github.com/arduino/arduino-cli/) used by the IDE?
|
||||
@@ -94,9 +126,9 @@ This project is built on [GitHub Actions](https://github.com/bcmi-labs/arduino-e
|
||||
|
||||
- Q: I have understood that not all versions of the CLI is compatible with my version of IDE but how can I manually update the `arduino-cli` inside the IDE?
|
||||
- A: [Get](https://arduino.github.io/arduino-cli/installation) the desired version of `arduino-cli` for your platform and manually replace the one inside the IDE. The CLI can be found inside the IDE at:
|
||||
- Windows: `C:\path\to\Arduino Pro IDE\resources\app\node_modules\arduino-ide-extension\build\arduino-cli.exe`,
|
||||
- macOS: `/path/to/Arduino Pro IDE.app/Contents/Resources/app/node_modules/arduino-ide-extension/build/arduino-cli`, and
|
||||
- Linux: `/path/to/Arduino Pro IDE/resources/app/node_modules/arduino-ide-extension/build/arduino-cli`.
|
||||
- Windows: `C:\path\to\Arduino IDE\resources\app\node_modules\arduino-ide-extension\build\arduino-cli.exe`,
|
||||
- macOS: `/path/to/Arduino IDE.app/Contents/Resources/app/node_modules/arduino-ide-extension/build/arduino-cli`, and
|
||||
- Linux: `/path/to/Arduino IDE/resources/app/node_modules/arduino-ide-extension/build/arduino-cli`.
|
||||
|
||||
### Architecture overview
|
||||
|
||||
|
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "arduino-debugger-extension",
|
||||
"version": "0.1.3",
|
||||
"description": "An extension for debugging Arduino programs",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@theia/debug": "next",
|
||||
"arduino-ide-extension": "0.1.3",
|
||||
"cdt-gdb-adapter": "^0.0.14",
|
||||
"vscode-debugadapter": "^1.26.0",
|
||||
"vscode-debugprotocol": "^1.26.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "yarn run clean && yarn run build",
|
||||
"clean": "rimraf lib",
|
||||
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
|
||||
"build": "tsc && yarn lint",
|
||||
"watch": "tsc -w"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"theiaExtensions": [
|
||||
{
|
||||
"backend": "lib/node/arduino-debug-backend-module",
|
||||
"frontend": "lib/browser/arduino-debug-frontend-module"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
import { DebugConfigurationManager } from "@theia/debug/lib/browser/debug-configuration-manager";
|
||||
import { injectable } from "inversify";
|
||||
|
||||
@injectable()
|
||||
export class ArduinoDebugConfigurationManager extends DebugConfigurationManager {
|
||||
|
||||
get defaultDebugger(): Promise<string | undefined> {
|
||||
return this.debug.getDebuggersForLanguage('ino').then(debuggers => {
|
||||
if (debuggers.length === 0)
|
||||
return undefined;
|
||||
return debuggers[0].type;
|
||||
});
|
||||
}
|
||||
|
||||
protected async selectDebugType(): Promise<string | undefined> {
|
||||
const widget = this.editorManager.currentEditor;
|
||||
if (!widget) {
|
||||
return this.defaultDebugger;
|
||||
}
|
||||
const { languageId } = widget.editor.document;
|
||||
const debuggers = await this.debug.getDebuggersForLanguage(languageId);
|
||||
if (debuggers.length === 0) {
|
||||
return this.defaultDebugger;
|
||||
}
|
||||
return this.quickPick.show(debuggers.map(
|
||||
({ label, type }) => ({ label, value: type }),
|
||||
{ placeholder: 'Select Environment' })
|
||||
);
|
||||
}
|
||||
|
||||
async createDefaultConfiguration(): Promise<void> {
|
||||
const { model } = this;
|
||||
if (model) {
|
||||
await this.doCreate(model);
|
||||
await this.updateModels();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,133 +0,0 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { MenuModelRegistry, MessageService, Command, CommandRegistry } from '@theia/core';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser';
|
||||
import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { DebugFrontendApplicationContribution, DebugCommands } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||
import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options";
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { FileSystem } from '@theia/filesystem/lib/common';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode";
|
||||
import { SketchesService } from 'arduino-ide-extension/lib/common/protocol/sketches-service';
|
||||
import { ArduinoToolbar } from 'arduino-ide-extension/lib/browser/toolbar/arduino-toolbar';
|
||||
import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager';
|
||||
|
||||
export namespace ArduinoDebugCommands {
|
||||
export const START_DEBUG: Command = {
|
||||
id: 'arduino-start-debug',
|
||||
label: 'Start Debugging'
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendApplicationContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
|
||||
@inject(FileSystem)
|
||||
protected readonly fileSystem: FileSystem;
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
async start(noDebug?: boolean, debugSessionOptions?: DebugSessionOptions): Promise<void> {
|
||||
const configurations = this.configurations as ArduinoDebugConfigurationManager;
|
||||
let current = debugSessionOptions ? debugSessionOptions : configurations.current;
|
||||
// If no configurations are currently present, create them
|
||||
if (!current) {
|
||||
await configurations.createDefaultConfiguration();
|
||||
current = configurations.current;
|
||||
}
|
||||
if (current) {
|
||||
if (noDebug !== undefined) {
|
||||
current = {
|
||||
...current,
|
||||
configuration: {
|
||||
...current.configuration,
|
||||
noDebug
|
||||
}
|
||||
};
|
||||
}
|
||||
if (current.configuration.type === 'arduino') {
|
||||
const wsStat = this.workspaceService.workspace;
|
||||
let sketchFileURI: URI | undefined;
|
||||
if (wsStat && await this.sketchesService.isSketchFolder(wsStat.resource.toString())) {
|
||||
const wsPath = wsStat.resource.path;
|
||||
const sketchFilePath = wsPath.join(wsPath.name + '.ino').toString();
|
||||
sketchFileURI = new URI(sketchFilePath);
|
||||
} else if (this.editorManager.currentEditor) {
|
||||
const editorURI = this.editorManager.currentEditor.getResourceUri();
|
||||
if (editorURI && editorURI.path && editorURI.path.ext === '.ino') {
|
||||
sketchFileURI = editorURI;
|
||||
}
|
||||
}
|
||||
if (sketchFileURI) {
|
||||
await this.editorManager.open(sketchFileURI);
|
||||
await this.manager.start(current);
|
||||
} else {
|
||||
this.messageService.error('Please open a sketch file to start debugging.')
|
||||
}
|
||||
} else {
|
||||
await this.manager.start(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initializeLayout(): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout();
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
if (this.editorMode.proMode) {
|
||||
super.registerMenus(menus);
|
||||
menus.unregisterMenuAction(DebugCommands.START_NO_DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
if (this.editorMode.proMode) {
|
||||
super.registerKeybindings(keybindings);
|
||||
keybindings.unregisterKeybinding({
|
||||
command: DebugCommands.START_NO_DEBUG.id,
|
||||
keybinding: 'ctrl+f5'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerToolbarItems(toolbar: TabBarToolbarRegistry): void {
|
||||
super.registerToolbarItems(toolbar);
|
||||
toolbar.registerItem({
|
||||
id: ArduinoDebugCommands.START_DEBUG.id,
|
||||
command: ArduinoDebugCommands.START_DEBUG.id,
|
||||
tooltip: 'Start Debugging',
|
||||
priority: 3
|
||||
});
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
super.registerCommands(registry);
|
||||
registry.registerCommand(ArduinoDebugCommands.START_DEBUG, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: () => {
|
||||
registry.executeCommand(DebugCommands.START.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { VariableContribution } from '@theia/variable-resolver/lib/browser';
|
||||
import { ArduinoVariableResolver } from './arduino-variable-resolver';
|
||||
import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||
import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||
import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
|
||||
import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager';
|
||||
import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution';
|
||||
import { ArduinoDebugSessionManager } from './arduino-debug-session-manager';
|
||||
|
||||
import '../../src/browser/style/index.css';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ArduinoVariableResolver).toSelf().inSingletonScope();
|
||||
bind(VariableContribution).toService(ArduinoVariableResolver);
|
||||
rebind(DebugSessionManager).to(ArduinoDebugSessionManager).inSingletonScope();
|
||||
rebind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope();
|
||||
rebind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution);
|
||||
});
|
@@ -1,14 +0,0 @@
|
||||
import { DebugSessionManager } from "@theia/debug/lib/browser/debug-session-manager";
|
||||
import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options";
|
||||
|
||||
export class ArduinoDebugSessionManager extends DebugSessionManager {
|
||||
|
||||
start(options: DebugSessionOptions) {
|
||||
if (options.configuration.type === 'arduino' && this.sessions.find(s => s.configuration.type === 'arduino')) {
|
||||
this.messageService.info('A debug session is already running. You must stop the running session before starting a new one.')
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return super.start(options);
|
||||
}
|
||||
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
|
||||
import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser';
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { BoardsServiceProvider } from 'arduino-ide-extension/lib/browser/boards/boards-service-provider';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoVariableResolver implements VariableContribution {
|
||||
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService
|
||||
|
||||
registerVariables(variables: VariableRegistry): void {
|
||||
variables.registerVariable(<Variable>{
|
||||
name: 'fqbn',
|
||||
description: 'Qualified name of the selected board',
|
||||
resolve: this.resolveFqbn.bind(this),
|
||||
});
|
||||
variables.registerVariable({
|
||||
name: 'port',
|
||||
description: 'Selected upload port',
|
||||
resolve: this.resolvePort.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
protected async resolveFqbn(): Promise<string | undefined> {
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
||||
this.messageService.error('No board selected. Please select a board for debugging.');
|
||||
return undefined;
|
||||
}
|
||||
return boardsConfig.selectedBoard.fqbn;
|
||||
}
|
||||
|
||||
protected async resolvePort(): Promise<string | undefined> {
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
if (!boardsConfig || !boardsConfig.selectedPort) {
|
||||
return undefined;
|
||||
}
|
||||
return boardsConfig.selectedPort.address;
|
||||
}
|
||||
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
.arduino-start-debug-icon {
|
||||
-webkit-mask: url('debug-dark.svg') 50%;
|
||||
mask: url('debug-dark.svg') 50%;
|
||||
-webkit-mask-size: 100%;
|
||||
mask-size: 100%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--theia-ui-button-font-color);
|
||||
}
|
||||
|
||||
.arduino-start-debug {
|
||||
border-radius: 12px;
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
import * as path from 'path';
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { DebugAdapterContribution, DebugAdapterExecutable } from '@theia/debug/lib/common/debug-model';
|
||||
import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration';
|
||||
import { IJSONSchema } from '@theia/core/lib/common/json-schema';
|
||||
import { ArduinoDaemonImpl } from 'arduino-ide-extension/lib/node/arduino-daemon-impl';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoDebugAdapterContribution implements DebugAdapterContribution {
|
||||
|
||||
readonly type = 'arduino';
|
||||
readonly label = 'Arduino';
|
||||
readonly languages = ['c', 'cpp', 'ino'];
|
||||
|
||||
@inject(ArduinoDaemonImpl) daemon: ArduinoDaemonImpl;
|
||||
|
||||
getSchemaAttributes(): IJSONSchema[] {
|
||||
return [
|
||||
{
|
||||
'properties': {
|
||||
'sketch': {
|
||||
'type': 'string',
|
||||
'description': 'path to the sketch root ino file',
|
||||
'default': '${file}',
|
||||
},
|
||||
'pauseAtMain': {
|
||||
'description': 'If enabled the debugger will pause at the start of the main function.',
|
||||
'type': 'boolean',
|
||||
'default': false
|
||||
},
|
||||
'debugDebugAdapter': {
|
||||
'type': 'boolean',
|
||||
'description': 'Start the debug adapter in debug mode (with --inspect-brk)',
|
||||
'default': false
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
provideDebugAdapterExecutable(config: DebugConfiguration): DebugAdapterExecutable {
|
||||
const debugAdapterMain = path.join(__dirname, 'debug-adapter', 'main');
|
||||
if (config.debugDebugAdapter) {
|
||||
return {
|
||||
command: process.execPath,
|
||||
args: ['--inspect-brk', debugAdapterMain],
|
||||
}
|
||||
}
|
||||
return {
|
||||
modulePath: debugAdapterMain,
|
||||
args: [],
|
||||
}
|
||||
}
|
||||
|
||||
provideDebugConfigurations(): DebugConfiguration[] {
|
||||
return [
|
||||
<DebugConfiguration>{
|
||||
name: this.label,
|
||||
type: this.type,
|
||||
request: 'launch'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async resolveDebugConfiguration(config: DebugConfiguration): Promise<DebugConfiguration> {
|
||||
const startFunction = config.pauseAtMain ? 'main' : 'setup';
|
||||
const res: ActualDebugConfig = {
|
||||
...config,
|
||||
arduinoCli: await this.daemon.getExecPath(),
|
||||
fqbn: '${fqbn}',
|
||||
uploadPort: '${port}',
|
||||
initCommands: [
|
||||
`-break-insert -t --function ${startFunction}`
|
||||
]
|
||||
}
|
||||
if (!res.sketch) {
|
||||
res.sketch = '${file}';
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface ActualDebugConfig extends DebugConfiguration {
|
||||
arduinoCli?: string;
|
||||
sketch?: string;
|
||||
fqbn?: string;
|
||||
uploadPort?: string;
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { DebugAdapterContribution } from '@theia/debug/lib/common/debug-model';
|
||||
import { ArduinoDebugAdapterContribution } from './arduino-debug-adapter-contribution';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(DebugAdapterContribution).to(ArduinoDebugAdapterContribution).inSingletonScope();
|
||||
});
|
@@ -1,140 +0,0 @@
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { GDBDebugSession, FrameVariableReference } from 'cdt-gdb-adapter/dist/GDBDebugSession';
|
||||
import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend';
|
||||
import * as mi from 'cdt-gdb-adapter/dist/mi';
|
||||
import { ArduinoGDBBackend } from './arduino-gdb-backend';
|
||||
import { ArduinoVariableHandler } from './arduino-variable-handler';
|
||||
import { Scope, OutputEvent } from 'vscode-debugadapter';
|
||||
|
||||
export interface ArduinoLaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
||||
arduinoCli?: string;
|
||||
sketch?: string;
|
||||
fqbn?: string;
|
||||
uploadPort?: string;
|
||||
}
|
||||
|
||||
const GLOBAL_HANDLE_ID = 0xFE;
|
||||
const STATIC_HANDLES_START = 0x010000;
|
||||
const STATIC_HANDLES_FINISH = 0x01FFFF;
|
||||
|
||||
export class ArduinoDebugSession extends GDBDebugSession {
|
||||
|
||||
private _variableHandler: ArduinoVariableHandler;
|
||||
|
||||
get arduinoBackend(): ArduinoGDBBackend {
|
||||
return this.gdb as ArduinoGDBBackend;
|
||||
}
|
||||
|
||||
protected get variableHandler() {
|
||||
if (this._variableHandler) {
|
||||
return this._variableHandler;
|
||||
}
|
||||
if (!this.gdb) {
|
||||
throw new Error("GDB backend is not ready.");
|
||||
}
|
||||
const handler = new ArduinoVariableHandler(this, this.frameHandles, this.variableHandles);
|
||||
this._variableHandler = handler;
|
||||
return handler;
|
||||
}
|
||||
|
||||
protected createBackend(): GDBBackend {
|
||||
return new ArduinoGDBBackend();
|
||||
}
|
||||
|
||||
protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): Promise<void> {
|
||||
try {
|
||||
await this.gdb.sendCommand('-interpreter-exec console "monitor reset halt"')
|
||||
await mi.sendExecContinue(this.gdb);
|
||||
this.sendResponse(response);
|
||||
} catch (err) {
|
||||
this.sendErrorResponse(response, 100, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
protected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): Promise<void> {
|
||||
if (process.platform === 'win32') {
|
||||
const message = 'Pause is not supported on Windows. Please stop the debug session and set a breakpoint instead.';
|
||||
this.sendEvent(new OutputEvent(message));
|
||||
this.sendErrorResponse(response, 1, message);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return super.pauseRequest(response, args);
|
||||
}
|
||||
|
||||
protected async disconnectRequest(response: DebugProtocol.DisconnectResponse): Promise<void> {
|
||||
try {
|
||||
if (this.isRunning) {
|
||||
if (process.platform === 'win32') {
|
||||
// We cannot pause on Windows
|
||||
this.arduinoBackend.kill();
|
||||
this.sendResponse(response);
|
||||
return;
|
||||
}
|
||||
// Need to pause first
|
||||
const waitPromise = new Promise(resolve => this.waitPaused = resolve);
|
||||
this.gdb.pause();
|
||||
await waitPromise;
|
||||
}
|
||||
await this.gdb.sendGDBExit();
|
||||
this.sendResponse(response);
|
||||
} catch (err) {
|
||||
this.sendErrorResponse(response, 1, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
||||
try {
|
||||
const frame: FrameVariableReference = {
|
||||
type: 'frame',
|
||||
frameHandle: args.frameId,
|
||||
};
|
||||
// const pins: ObjectVariableReference = {
|
||||
// type: "object",
|
||||
// varobjName: "__pins",
|
||||
// frameHandle: 42000,
|
||||
// }
|
||||
|
||||
response.body = {
|
||||
scopes: [
|
||||
// new Scope('Pins', this.variableHandles.create(pins), false),
|
||||
new Scope('Local', this.variableHandles.create(frame), false),
|
||||
new Scope('Global', GLOBAL_HANDLE_ID, false),
|
||||
// new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false)
|
||||
],
|
||||
};
|
||||
|
||||
this.sendResponse(response);
|
||||
} catch (err) {
|
||||
this.sendErrorResponse(response, 1, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise<void> {
|
||||
try {
|
||||
response.body = {
|
||||
variables: [] as DebugProtocol.Variable[]
|
||||
};
|
||||
const ref = this.variableHandles.get(args.variablesReference);
|
||||
if (args.variablesReference === GLOBAL_HANDLE_ID) {
|
||||
// Use hardcoded global handle to load and store global variables
|
||||
response.body.variables = await this.variableHandler.getGlobalVariables();
|
||||
} else if (args.variablesReference >= STATIC_HANDLES_START && args.variablesReference <= STATIC_HANDLES_FINISH) {
|
||||
// Use STATIC_HANDLES_START to shift the framehandles back
|
||||
const frameHandle = args.variablesReference - STATIC_HANDLES_START;
|
||||
response.body.variables = await this.variableHandler.getStaticVariables(frameHandle);
|
||||
} else if (ref && ref.type === 'frame') {
|
||||
// List variables for current frame
|
||||
response.body.variables = await this.handleVariableRequestFrame(ref);
|
||||
} else if (ref && ref.varobjName === '__pins') {
|
||||
response.body.variables = await this.variableHandler.handlePinStatusRequest();
|
||||
} else if (ref && ref.type === 'object') {
|
||||
// List data under any variable
|
||||
response.body.variables = await this.handleVariableRequestObject(ref);
|
||||
}
|
||||
this.sendResponse(response);
|
||||
} catch (err) {
|
||||
this.sendErrorResponse(response, 1, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,73 +0,0 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'arduino-ide-extension/lib/node/fs-extra'
|
||||
import { spawn } from 'child_process';
|
||||
import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend';
|
||||
import { MIFrameInfo } from 'cdt-gdb-adapter/dist/mi';
|
||||
import { ArduinoLaunchRequestArguments } from './arduino-debug-session';
|
||||
import { ArduinoParser } from './arduino-parser';
|
||||
|
||||
export class ArduinoGDBBackend extends GDBBackend {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.parser = new ArduinoParser(this);
|
||||
}
|
||||
|
||||
spawn(requestArgs: ArduinoLaunchRequestArguments): Promise<void> {
|
||||
if (!requestArgs.sketch) {
|
||||
throw new Error('Missing argument: sketch');
|
||||
}
|
||||
if (!requestArgs.fqbn) {
|
||||
throw new Error('Missing argument: fqbn')
|
||||
}
|
||||
const sketchFS = fs.statSync(requestArgs.sketch);
|
||||
const sketchDir = sketchFS.isFile() ? path.dirname(requestArgs.sketch) : requestArgs.sketch;
|
||||
const command = requestArgs.arduinoCli || 'arduino-cli';
|
||||
const args = [
|
||||
'debug',
|
||||
'-p', requestArgs.uploadPort || 'none',
|
||||
'-b', requestArgs.fqbn,
|
||||
'--interpreter', 'mi2',
|
||||
sketchDir
|
||||
];
|
||||
console.log('Starting debugger:', command, JSON.stringify(args));
|
||||
const proc = spawn(command, args);
|
||||
this.proc = proc;
|
||||
this.out = proc.stdin;
|
||||
return (this.parser as ArduinoParser).parseFull(proc);
|
||||
}
|
||||
|
||||
sendFileExecAndSymbols(): Promise<void> {
|
||||
// The program file is already sent by `arduino-cli`
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
sendExecInterrupt(threadId?: number) {
|
||||
let command = '-exec-interrupt';
|
||||
if (threadId) {
|
||||
command += ` --thread ${threadId}`;
|
||||
}
|
||||
return this.sendCommand(command);
|
||||
}
|
||||
|
||||
sendStackInfoFrame(threadId: number, frameId: number): Promise<{ frame: MIFrameInfo }> {
|
||||
const command = `-stack-info-frame --thread ${threadId} --frame ${frameId}`;
|
||||
return this.sendCommand(command);
|
||||
}
|
||||
|
||||
sendTargetDetach(): Promise<void> {
|
||||
return this.sendCommand('-target-detach');
|
||||
}
|
||||
|
||||
kill(): void {
|
||||
if (!this.proc) {
|
||||
return;
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
spawn('taskkill', ['/pid', this.proc.pid.toString(), '/f', '/t']);
|
||||
} else {
|
||||
this.proc.kill('SIGKILL');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
import { ChildProcessWithoutNullStreams } from 'child_process';
|
||||
import { Readable } from 'stream';
|
||||
import { MIParser } from "cdt-gdb-adapter/dist/MIParser";
|
||||
|
||||
const LINE_REGEX = /(.*)(\r?\n)/;
|
||||
|
||||
export class ArduinoParser extends MIParser {
|
||||
|
||||
protected rejectReady?: (error: Error) => void;
|
||||
|
||||
parseFull(proc: ChildProcessWithoutNullStreams): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Detect errors when the child process could not be spawned
|
||||
proc.on('error', reject);
|
||||
|
||||
this.waitReady = () => {
|
||||
this.rejectReady = undefined;
|
||||
resolve();
|
||||
}
|
||||
this.rejectReady = (error: Error) => {
|
||||
this.waitReady = undefined;
|
||||
reject(error);
|
||||
}
|
||||
// Detect unexpected termination
|
||||
proc.on('exit', () => {
|
||||
if (this.rejectReady) {
|
||||
this.rejectReady(new Error('The gdb debugger terminated unexpectedly.'));
|
||||
}
|
||||
});
|
||||
this.readInputStream(proc.stdout);
|
||||
this.readErrorStream(proc.stderr);
|
||||
});
|
||||
}
|
||||
|
||||
private readInputStream(stream: Readable) {
|
||||
let buff = '';
|
||||
stream.on('data', chunk => {
|
||||
buff += chunk.toString();
|
||||
let regexArray = LINE_REGEX.exec(buff);
|
||||
while (regexArray) {
|
||||
const line = regexArray[1];
|
||||
this.line = line;
|
||||
this.pos = 0;
|
||||
this.handleLine();
|
||||
// Detect error emitted as log message
|
||||
if (this.rejectReady && line.toLowerCase().startsWith('&"error')) {
|
||||
this.pos = 1;
|
||||
this.rejectReady(new Error(this.handleCString() || regexArray[1]));
|
||||
this.rejectReady = undefined;
|
||||
}
|
||||
buff = buff.substring(regexArray[1].length + regexArray[2].length);
|
||||
regexArray = LINE_REGEX.exec(buff);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private readErrorStream(stream: Readable) {
|
||||
let buff = '';
|
||||
stream.on('data', chunk => {
|
||||
buff += chunk.toString();
|
||||
let regexArray = LINE_REGEX.exec(buff);
|
||||
while (regexArray) {
|
||||
const line = regexArray[1];
|
||||
this.gdb.emit('consoleStreamOutput', line + '\n', 'stderr');
|
||||
// Detect error emitted on the stderr stream
|
||||
if (this.rejectReady && line.toLowerCase().startsWith('error')) {
|
||||
this.rejectReady(new Error(line));
|
||||
this.rejectReady = undefined;
|
||||
}
|
||||
buff = buff.substring(regexArray[1].length + regexArray[2].length);
|
||||
regexArray = LINE_REGEX.exec(buff);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -1,115 +0,0 @@
|
||||
import * as path from 'path';
|
||||
import { DebugProtocol } from "vscode-debugprotocol";
|
||||
import { Handles } from 'vscode-debugadapter/lib/handles';
|
||||
import { FrameReference, VariableReference } from "cdt-gdb-adapter/dist/GDBDebugSession";
|
||||
import { VarManager } from 'cdt-gdb-adapter/dist/varManager';
|
||||
import * as mi from 'cdt-gdb-adapter/dist/mi';
|
||||
import { ArduinoDebugSession } from "./arduino-debug-session";
|
||||
import { ArduinoGDBBackend } from './arduino-gdb-backend';
|
||||
|
||||
export class ArduinoVariableHandler {
|
||||
|
||||
protected readonly gdb: ArduinoGDBBackend;
|
||||
protected readonly varMgr: VarManager;
|
||||
|
||||
protected globalHandle: number;
|
||||
|
||||
constructor(protected readonly session: ArduinoDebugSession,
|
||||
protected frameHandles: Handles<FrameReference>,
|
||||
protected variableHandles: Handles<VariableReference>) {
|
||||
this.gdb = session.arduinoBackend;
|
||||
this.varMgr = new VarManager(this.gdb);
|
||||
}
|
||||
|
||||
createGlobalHandle() {
|
||||
this.globalHandle = this.frameHandles.create({
|
||||
threadId: -1,
|
||||
frameId: -1
|
||||
});
|
||||
}
|
||||
|
||||
/** TODO */
|
||||
async getGlobalVariables(): Promise<DebugProtocol.Variable[]> {
|
||||
throw new Error('Global variables are not supported yet.');
|
||||
const frame = this.frameHandles.get(this.globalHandle);
|
||||
const symbolInfo: any[] = [] // this.symbolTable.getGlobalVariables();
|
||||
const variables: DebugProtocol.Variable[] = [];
|
||||
|
||||
for (const symbol of symbolInfo) {
|
||||
const name = `global_var_${symbol.name}`;
|
||||
const variable = await this.getVariables(frame, name, symbol.name, -1);
|
||||
variables.push(variable);
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
/** TODO */
|
||||
async getStaticVariables(frameHandle: number): Promise<DebugProtocol.Variable[]> {
|
||||
throw new Error('Static variables are not supported yet.');
|
||||
const frame = this.frameHandles.get(frameHandle);
|
||||
const result = await this.gdb.sendStackInfoFrame(frame.threadId, frame.frameId);
|
||||
const file = path.normalize(result.frame.file || '');
|
||||
const symbolInfo: any[] = [] // this.symbolTable.getStaticVariables(file);
|
||||
const variables: DebugProtocol.Variable[] = [];
|
||||
|
||||
// Fetch stack depth to obtain frameId/threadId/depth tuple
|
||||
const stackDepth = await mi.sendStackInfoDepth(this.gdb, { maxDepth: 100 });
|
||||
const depth = parseInt(stackDepth.depth, 10);
|
||||
|
||||
for (const symbol of symbolInfo) {
|
||||
const name = `${file}_static_var_${symbol.name}`;
|
||||
const variable = await this.getVariables(frame, name, symbol.name, depth);
|
||||
variables.push(variable);
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
private async getVariables(frame: FrameReference, name: string, expression: string, depth: number): Promise<DebugProtocol.Variable> {
|
||||
let global = this.varMgr.getVar(frame.frameId, frame.threadId, depth, name);
|
||||
|
||||
if (global) {
|
||||
// Update value if it is already loaded
|
||||
const vup = await mi.sendVarUpdate(this.gdb, { name });
|
||||
const update = vup.changelist[0];
|
||||
if (update && update.in_scope === 'true' && update.name === global.varname) {
|
||||
global.value = update.value;
|
||||
}
|
||||
} else {
|
||||
// create var in GDB and store it in the varMgr
|
||||
const varCreateResponse = await mi.sendVarCreate(this.gdb, {
|
||||
name,
|
||||
frame: 'current',
|
||||
expression,
|
||||
});
|
||||
|
||||
global = this.varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse);
|
||||
}
|
||||
|
||||
return {
|
||||
name: expression,
|
||||
value: (global.value === void 0) ? '<unknown>' : global.value,
|
||||
type: global.type,
|
||||
variablesReference: parseInt(global.numchild, 10) > 0
|
||||
? this.variableHandles.create({
|
||||
frameHandle: this.globalHandle,
|
||||
type: 'object',
|
||||
varobjName: global.varname,
|
||||
})
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async handlePinStatusRequest(): Promise<DebugProtocol.Variable[]> {
|
||||
const variables: DebugProtocol.Variable[] = [];
|
||||
variables.push({
|
||||
name: "D2",
|
||||
type: "gpio",
|
||||
value: "0x00",
|
||||
variablesReference: 0
|
||||
})
|
||||
return variables;
|
||||
}
|
||||
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
import * as process from 'process';
|
||||
import { logger } from 'vscode-debugadapter/lib/logger';
|
||||
import { ArduinoDebugSession } from './arduino-debug-session';
|
||||
import { DebugSession } from 'vscode-debugadapter';
|
||||
|
||||
process.on('uncaughtException', (err: any) => {
|
||||
logger.error(JSON.stringify(err));
|
||||
});
|
||||
|
||||
DebugSession.run(ArduinoDebugSession);
|
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"noImplicitAny": true,
|
||||
"noEmitOnError": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"experimentalDecorators": true,
|
||||
"downlevelIteration": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es6",
|
||||
"outDir": "lib",
|
||||
"lib": [
|
||||
"es6",
|
||||
"dom"
|
||||
],
|
||||
"jsx": "react",
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"files": [
|
||||
"../node_modules/@theia/monaco/src/typings/monaco/index.d.ts"
|
||||
]
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"class-name": true,
|
||||
"comment-format": [true, "check-space"],
|
||||
"curly": false,
|
||||
"forin": false,
|
||||
"indent": [true, "spaces"],
|
||||
"max-line-length": [true, 180],
|
||||
"no-trailing-whitespace": false,
|
||||
"no-unused-expression": true,
|
||||
"no-var-keyword": true,
|
||||
"one-line": [true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-whitespace"
|
||||
],
|
||||
"radix": true,
|
||||
"trailing-comma": [false],
|
||||
"triple-equals": [true, "allow-null-check"],
|
||||
"typedef-whitespace": [true, {
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}],
|
||||
"variable-name": false,
|
||||
"whitespace": [true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
## Arduino IDE Extension
|
||||
|
||||
Arduino Pro IDE is based on Theia, and most of its IDE features, UIs and customizations are implemented in this Theia extension.
|
||||
Arduino IDE is based on Theia, and most of its IDE features, UIs and customizations are implemented in this Theia extension.
|
||||
|
||||
### IDE Services
|
||||
|
||||
|
@@ -1,137 +0,0 @@
|
||||
{
|
||||
"$id": "http://arduino.cc/arduino-cli.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "Arduino CLI Configuration",
|
||||
"properties": {
|
||||
"board_manager": {
|
||||
"type": "object",
|
||||
"description": "Board Manager Configuration",
|
||||
"properties": {
|
||||
"additional_urls": {
|
||||
"type": "array",
|
||||
"description": "If your board requires 3rd party core packages to work, you can list the URLs to additional package indexes in the Arduino CLI configuration file.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "URL pointing to the 3rd party core package index JSON.",
|
||||
"pattern": "^(.*)$"
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"daemon": {
|
||||
"type": "object",
|
||||
"description": "CLI Daemon Configuration",
|
||||
"properties": {
|
||||
"port": {
|
||||
"type": [
|
||||
"string",
|
||||
"number"
|
||||
],
|
||||
"description": "The CLI daemon port where the gRPC clients can connect to.",
|
||||
"pattern": "^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"directories": {
|
||||
"type": "object",
|
||||
"description": "Directories Configuration",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "Path to the the data folder where core packages will be stored.",
|
||||
"pattern": "^(.*)$"
|
||||
},
|
||||
"downloads": {
|
||||
"type": "string",
|
||||
"description": "Path to the staging folder.",
|
||||
"pattern": "^(.*)$"
|
||||
},
|
||||
"user": {
|
||||
"type": "string",
|
||||
"description": "Path to the sketchbooks.",
|
||||
"pattern": "^(.*)$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"logging": {
|
||||
"type": "object",
|
||||
"description": "Logging Configuration",
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string",
|
||||
"description": "Path to the file where logs will be written.",
|
||||
"pattern": "^(.*)$"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "The output format for the logs, can be 'text' or 'json'",
|
||||
"enum": [
|
||||
"text",
|
||||
"json"
|
||||
]
|
||||
},
|
||||
"level": {
|
||||
"type": "string",
|
||||
"description": "Messages with this level and above will be logged.",
|
||||
"enum": [
|
||||
"trace",
|
||||
"debug",
|
||||
"info",
|
||||
"warning",
|
||||
"error",
|
||||
"fatal",
|
||||
"panic"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sketch": {
|
||||
"type": "object",
|
||||
"description": "Sketch Configuration",
|
||||
"properties": {
|
||||
"always_export_binaries": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the compiled binaries will be exported into the sketch's 'build' folder or not. 'false' if the binaries are not exported."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"telemetry": {
|
||||
"type": "object",
|
||||
"description": "Telemetry Configuration",
|
||||
"properties": {
|
||||
"addr": {
|
||||
"type": "string",
|
||||
"description": "Address to the telemetry endpoint. Must be a full address with host, address, and port. For instance, ':9090' represents 'localhost:9090'",
|
||||
"pattern": "^(.*)$"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the telemetry is enabled or not."
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"library": {
|
||||
"type": "object",
|
||||
"description": "Library Configuration",
|
||||
"properties": {
|
||||
"enable_unsafe_install": {
|
||||
"type": "boolean",
|
||||
"description": "Set to 'true' to enable the use of the '--git-url' and '--zip-file' flags with 'arduino-cli lib install' These are considered 'unsafe' installation methods because they allow installing files that have not passed through the Library Manager submission process."
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "arduino-ide-extension",
|
||||
"version": "0.1.3",
|
||||
"version": "2.0.0-beta.2",
|
||||
"description": "An extension for Theia building the Arduino IDE",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -41,6 +41,7 @@
|
||||
"@types/ncp": "^2.0.4",
|
||||
"@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",
|
||||
@@ -56,7 +57,9 @@
|
||||
"ncp": "^2.0.0",
|
||||
"p-queue": "^5.0.0",
|
||||
"ps-tree": "^1.2.0",
|
||||
"react-disable": "^0.1.0",
|
||||
"react-select": "^3.0.4",
|
||||
"react-tabs": "^3.1.2",
|
||||
"semver": "^7.3.2",
|
||||
"string-natural-compare": "^2.0.3",
|
||||
"temp": "^0.9.1",
|
||||
@@ -120,7 +123,7 @@
|
||||
],
|
||||
"arduino": {
|
||||
"cli": {
|
||||
"version": "0.14.0"
|
||||
"version": "0.16.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
(() => {
|
||||
|
||||
const DEFAULT_ALS_VERSION = 'nightly';
|
||||
const DEFAULT_CLANGD_VERSION = '9.0.0';
|
||||
const DEFAULT_CLANGD_VERSION = 'snapshot_20210124';
|
||||
|
||||
const path = require('path');
|
||||
const shell = require('shelljs');
|
||||
@@ -22,7 +22,7 @@
|
||||
.option('clangd-version', {
|
||||
alias: 'cv',
|
||||
default: DEFAULT_CLANGD_VERSION,
|
||||
choices: ['8.0.1', '9.0.0'],
|
||||
choices: ['snapshot_20210124'],
|
||||
describe: `The version of 'clangd' to download. Defaults to ${DEFAULT_CLANGD_VERSION}.`
|
||||
})
|
||||
.option('force-download', {
|
||||
@@ -38,35 +38,35 @@
|
||||
const { platform, arch } = process;
|
||||
|
||||
const build = path.join(__dirname, '..', 'build');
|
||||
const alsTarget = path.join(build, `arduino-language-server${platform === 'win32' ? '.exe' : ''}`);
|
||||
const lsExecutablePath = path.join(build, `arduino-language-server${platform === 'win32' ? '.exe' : ''}`);
|
||||
|
||||
let clangdTarget, alsSuffix, clangdSuffix;
|
||||
let clangdExecutablePath, lsSuffix, clangdPrefix;
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
clangdTarget = path.join(build, 'bin', 'clangd')
|
||||
alsSuffix = 'Darwin_amd64.zip';
|
||||
clangdSuffix = 'macos.zip';
|
||||
clangdExecutablePath = path.join(build, 'bin', 'clangd')
|
||||
lsSuffix = 'macOS_amd64.zip';
|
||||
clangdPrefix = 'mac';
|
||||
break;
|
||||
case 'linux':
|
||||
clangdTarget = path.join(build, 'bin', 'clangd')
|
||||
alsSuffix = 'Linux_amd64.zip';
|
||||
clangdSuffix = 'linux.zip'
|
||||
clangdExecutablePath = path.join(build, 'bin', 'clangd')
|
||||
lsSuffix = 'Linux_amd64.zip';
|
||||
clangdPrefix = 'linux'
|
||||
break;
|
||||
case 'win32':
|
||||
clangdTarget = path.join(build, 'clangd.exe')
|
||||
alsSuffix = 'Windows_NT_amd64.zip';
|
||||
clangdSuffix = 'windows.zip';
|
||||
clangdExecutablePath = path.join(build, 'bin', 'clangd.exe')
|
||||
lsSuffix = 'Windows_amd64.zip';
|
||||
clangdPrefix = 'windows';
|
||||
break;
|
||||
}
|
||||
if (!alsSuffix) {
|
||||
if (!lsSuffix) {
|
||||
shell.echo(`The arduino-language-server is not available for ${platform} ${arch}.`);
|
||||
shell.exit(1);
|
||||
}
|
||||
|
||||
const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${alsVersion === 'nightly' ? 'nightly/arduino-language-server' : 'arduino-language-server_' + alsVersion}_${alsSuffix}`;
|
||||
downloader.downloadUnzipAll(alsUrl, build, alsTarget, force);
|
||||
const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${alsVersion === 'nightly' ? 'nightly/arduino-language-server' : 'arduino-language-server_' + alsVersion}_${lsSuffix}`;
|
||||
downloader.downloadUnzipAll(alsUrl, build, lsExecutablePath, force);
|
||||
|
||||
const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd_${clangdVersion}_${clangdSuffix}`;
|
||||
downloader.downloadUnzipAll(clangdUrl, build, clangdTarget, force);
|
||||
const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd-${clangdPrefix}-${clangdVersion}.zip`;
|
||||
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.
|
||||
|
||||
})();
|
||||
|
@@ -79,7 +79,7 @@ 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) => {
|
||||
exports.downloadUnzipAll = async (url, targetDir, targetFile, force, decompressOptions = undefined) => {
|
||||
if (fs.existsSync(targetFile) && !force) {
|
||||
shell.echo(`Skipping download because file already exists: ${targetFile}`);
|
||||
return;
|
||||
@@ -96,12 +96,16 @@ exports.downloadUnzipAll = async (url, targetDir, targetFile, force) => {
|
||||
shell.echo(`<<< Download succeeded.`);
|
||||
|
||||
shell.echo('>>> Decompressing...');
|
||||
const files = await decompress(data, targetDir, {
|
||||
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);
|
||||
|
@@ -66,24 +66,48 @@
|
||||
const { platform } = process;
|
||||
const build = path.join(__dirname, '..', 'build');
|
||||
const cli = path.join(build, `arduino-cli${platform === 'win32' ? '.exe' : ''}`);
|
||||
const jsonVersion = shell.exec(`${cli} version --format json`).trim();
|
||||
if (!jsonVersion) {
|
||||
const versionJson = shell.exec(`${cli} version --format json`).trim();
|
||||
if (!versionJson) {
|
||||
shell.echo(`Could not retrieve the CLI version from ${cli}.`);
|
||||
shell.exit(1);
|
||||
}
|
||||
const version = JSON.parse(jsonVersion).VersionString;
|
||||
if (version) {
|
||||
shell.echo(`>>> Checking out version: ${version}...`);
|
||||
if (shell.exec(`git -C ${repository} checkout ${version} -b ${version}`).code !== 0) {
|
||||
// As of today (28.01.2021), the `VersionString` can be one of the followings:
|
||||
// - `nightly-YYYYMMDD` stands for the nightly build, we use the , the `commitish` from the `package.json` to check out the code.
|
||||
// - `0.0.0-git` for local builds, we use the `commitish` from the `package.json` to check out the code and generate the APIs.
|
||||
// - `git-snapshot` for local build executed via `task build`. We do not do this.
|
||||
// - rest, we assume it is a valid semver and has the corresponding tagged code, we use the tag to generate the APIs from the `proto` files.
|
||||
/*
|
||||
{
|
||||
"Application": "arduino-cli",
|
||||
"VersionString": "nightly-20210126",
|
||||
"Commit": "079bb6c6",
|
||||
"Status": "alpha",
|
||||
"Date": "2021-01-26T01:46:31Z"
|
||||
}
|
||||
*/
|
||||
const versionObject = JSON.parse(versionJson);
|
||||
const version = versionObject.VersionString;
|
||||
if (version && !version.startsWith('nightly-') && version !== '0.0.0-git' && version !== 'git-snapshot') {
|
||||
shell.echo(`>>> Checking out tagged version: '${version}'...`);
|
||||
shell.exec(`git -C ${repository} fetch --all --tags`);
|
||||
if (shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`).code !== 0) {
|
||||
shell.exit(1);
|
||||
}
|
||||
shell.echo(`<<< Checked out version: ${commitish}.`);
|
||||
shell.echo(`<<< Checked out tagged version: '${commitish}'.`);
|
||||
} else if (commitish) {
|
||||
shell.echo(`>>> Checking out commitish: ${commitish}...`);
|
||||
shell.echo(`>>> Checking out commitish from 'package.json': '${commitish}'...`);
|
||||
if (shell.exec(`git -C ${repository} checkout ${commitish}`).code !== 0) {
|
||||
shell.exit(1);
|
||||
}
|
||||
shell.echo(`<<< Checked out commitish: ${commitish}.`);
|
||||
shell.echo(`<<< Checked out commitish from 'package.json': '${commitish}'.`);
|
||||
} else if (versionObject.Commit) {
|
||||
shell.echo(`>>> Checking out commitish from the CLI: '${versionObject.Commit}'...`);
|
||||
if (shell.exec(`git -C ${repository} checkout ${versionObject.Commit}`).code !== 0) {
|
||||
shell.exit(1);
|
||||
}
|
||||
shell.echo(`<<< Checked out commitish from the CLI: '${versionObject.Commit}'.`);
|
||||
} else {
|
||||
shell.echo(`WARN: no 'git checkout'. Generating from the HEAD revision.`);
|
||||
}
|
||||
|
||||
shell.echo('>>> Generating TS/JS API from:');
|
||||
|
@@ -20,11 +20,4 @@ export namespace ArduinoCommands {
|
||||
id: 'arduino-open-boards-dialog'
|
||||
};
|
||||
|
||||
export const TOGGLE_ADVANCED_MODE: Command = {
|
||||
id: 'arduino-toggle-advanced-mode'
|
||||
};
|
||||
export const TOGGLE_ADVANCED_MODE_TOOLBAR: Command = {
|
||||
id: 'arduino-toggle-advanced-mode-toolbar'
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
const debounce = require('lodash.debounce');
|
||||
import { MAIN_MENU_BAR, MenuContribution, MenuModelRegistry, SelectionService, ILogger } from '@theia/core';
|
||||
import {
|
||||
ContextMenuRenderer,
|
||||
@@ -23,6 +24,7 @@ import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspac
|
||||
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import * as React from 'react';
|
||||
import { remote } from 'electron';
|
||||
import { MainMenuManager } from '../common/main-menu-manager';
|
||||
import { BoardsService, CoreService, Port, SketchesService, ExecutableService } from '../common/protocol';
|
||||
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
||||
@@ -42,11 +44,10 @@ import { WorkspaceService } from './theia/workspace/workspace-service';
|
||||
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
||||
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
|
||||
const debounce = require('lodash.debounce');
|
||||
import { OutputService } from '../common/protocol/output-service';
|
||||
import { NotificationCenter } from './notification-center';
|
||||
import { Settings } from './contributions/settings';
|
||||
import { ArduinoPreferences } from './arduino-preferences';
|
||||
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
|
||||
import { SaveAsSketch } from './contributions/save-as-sketch';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFrontendContribution implements FrontendApplicationContribution,
|
||||
@@ -147,11 +148,15 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
|
||||
@inject(ExecutableService)
|
||||
protected executableService: ExecutableService;
|
||||
|
||||
@inject(OutputService)
|
||||
protected readonly outputService: OutputService;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
@inject(ArduinoPreferences)
|
||||
protected readonly arduinoPreferences: ArduinoPreferences;
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
protected invalidConfigPopup: Promise<void | 'No' | 'Yes' | undefined> | undefined;
|
||||
|
||||
@@ -192,7 +197,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
viewContribution.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
this.boardsServiceClientImpl.onBoardsConfigChanged(async ({ selectedBoard }) => {
|
||||
const start = async ({ selectedBoard }: BoardsConfig.Config) => {
|
||||
if (selectedBoard) {
|
||||
const { name, fqbn } = selectedBoard;
|
||||
if (fqbn) {
|
||||
@@ -200,20 +205,22 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
this.startLanguageServer(fqbn, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.boardsServiceClientImpl.onBoardsConfigChanged(start);
|
||||
this.arduinoPreferences.onPreferenceChanged(event => {
|
||||
if (event.preferenceName === 'arduino.language.log' && event.newValue !== event.oldValue) {
|
||||
start(this.boardsServiceClientImpl.boardsConfig);
|
||||
}
|
||||
});
|
||||
this.notificationCenter.onConfigChanged(({ config }) => {
|
||||
if (config) {
|
||||
this.invalidConfigPopup = undefined;
|
||||
} else {
|
||||
if (!this.invalidConfigPopup) {
|
||||
this.invalidConfigPopup = this.messageService.error(`Your CLI configuration is invalid. Do you want to correct it now?`, 'No', 'Yes')
|
||||
.then(answer => {
|
||||
if (answer === 'Yes') {
|
||||
this.commandRegistry.executeCommand(Settings.Commands.OPEN_CLI_CONFIG.id)
|
||||
}
|
||||
this.invalidConfigPopup = undefined;
|
||||
});
|
||||
}
|
||||
this.arduinoPreferences.ready.then(() => {
|
||||
const webContents = remote.getCurrentWebContents();
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -221,6 +228,14 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
protected startLanguageServer = debounce((fqbn: string, name: string | undefined) => this.doStartLanguageServer(fqbn, name));
|
||||
protected async doStartLanguageServer(fqbn: string, name: string | undefined): Promise<void> {
|
||||
this.logger.info(`Starting language server: ${fqbn}`);
|
||||
const log = this.arduinoPreferences.get('arduino.language.log');
|
||||
let currentSketchPath: string | undefined = undefined;
|
||||
if (log) {
|
||||
const currentSketch = await this.sketchServiceClient.currentSketch();
|
||||
if (currentSketch) {
|
||||
currentSketchPath = await this.fileSystem.fsPath(new URI(currentSketch.uri));
|
||||
}
|
||||
}
|
||||
const { clangdUri, cliUri, lsUri } = await this.executableService.list();
|
||||
const [clangdPath, cliPath, lsPath] = await Promise.all([
|
||||
this.fileSystem.fsPath(new URI(clangdUri)),
|
||||
@@ -231,6 +246,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
lsPath,
|
||||
cliPath,
|
||||
clangdPath,
|
||||
log: currentSketchPath ? currentSketchPath : log,
|
||||
board: {
|
||||
fqbn,
|
||||
name: name ? `"${name}"` : undefined
|
||||
@@ -253,12 +269,6 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR,
|
||||
tooltip: 'Serial Monitor'
|
||||
});
|
||||
registry.registerItem({
|
||||
id: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
||||
command: ArduinoCommands.TOGGLE_ADVANCED_MODE_TOOLBAR.id,
|
||||
tooltip: this.editorMode.proMode ? 'Switch to Classic Mode' : 'Switch to Advanced Mode',
|
||||
text: this.editorMode.proMode ? '$(toggle-on)' : '$(toggle-off)'
|
||||
});
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
@@ -279,50 +289,44 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
}
|
||||
}
|
||||
});
|
||||
registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE, {
|
||||
isToggled: () => this.editorMode.proMode,
|
||||
execute: () => this.editorMode.toggleProMode()
|
||||
});
|
||||
registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE_TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right',
|
||||
isToggled: () => this.editorMode.proMode,
|
||||
execute: () => this.editorMode.toggleProMode()
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry) {
|
||||
if (!this.editorMode.proMode) {
|
||||
const menuId = (menuPath: string[]): string => {
|
||||
const index = menuPath.length - 1;
|
||||
const menuId = menuPath[index];
|
||||
return menuId;
|
||||
}
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(MonacoMenus.SELECTION));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(EditorMainMenu.GO));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(TerminalMenus.TERMINAL));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(CommonMenus.VIEW));
|
||||
const menuId = (menuPath: string[]): string => {
|
||||
const index = menuPath.length - 1;
|
||||
const menuId = menuPath[index];
|
||||
return menuId;
|
||||
}
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(MonacoMenus.SELECTION));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(EditorMainMenu.GO));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(TerminalMenus.TERMINAL));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(CommonMenus.VIEW));
|
||||
|
||||
registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch');
|
||||
registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools');
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH, {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id,
|
||||
label: 'Optimize for Debugging',
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(CommonMenus.HELP, {
|
||||
commandId: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
||||
label: 'Advanced Mode'
|
||||
order: '4'
|
||||
});
|
||||
}
|
||||
|
||||
protected async openSketchFiles(uri: URI): Promise<void> {
|
||||
try {
|
||||
const sketch = await this.sketchService.loadSketch(uri.toString());
|
||||
const { mainFileUri, otherSketchFileUris, additionalFileUris } = sketch;
|
||||
for (const uri of [mainFileUri, ...otherSketchFileUris, ...additionalFileUris]) {
|
||||
const { mainFileUri, rootFolderFileUris } = sketch;
|
||||
for (const uri of [mainFileUri, ...rootFolderFileUris]) {
|
||||
await this.ensureOpened(uri);
|
||||
}
|
||||
await this.ensureOpened(mainFileUri, true);
|
||||
if (mainFileUri.endsWith('.pde')) {
|
||||
const message = `The '${sketch.name}' still uses the old \`.pde\` format. Do you want to switch to the new \`.ino\` extension?`;
|
||||
this.messageService.info(message, 'Later', 'Yes').then(async answer => {
|
||||
if (answer === 'Yes') {
|
||||
this.commandRegistry.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, { execOnlyIfTemp: false, openAfterMove: true, wipeOriginal: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
const message = e instanceof Error ? e.message : JSON.stringify(e);
|
||||
@@ -362,7 +366,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
light: 'editorWidget.background',
|
||||
hc: 'editorWidget.background'
|
||||
},
|
||||
description: 'Color of the Arduino Pro IDE foreground which is used for dialogs, such as the Select Board dialog.'
|
||||
description: 'Color of the Arduino IDE foreground which is used for dialogs, such as the Select Board dialog.'
|
||||
},
|
||||
{
|
||||
id: 'arduino.toolbar.background',
|
||||
|
@@ -85,7 +85,7 @@ import { WorkspaceFrontendContribution, ArduinoFileMenuContribution } from './th
|
||||
import { Contribution } from './contributions/contribution';
|
||||
import { NewSketch } from './contributions/new-sketch';
|
||||
import { OpenSketch } from './contributions/open-sketch';
|
||||
import { CloseSketch } from './contributions/close-sketch';
|
||||
import { Close } from './contributions/close';
|
||||
import { SaveAsSketch } from './contributions/save-as-sketch';
|
||||
import { SaveSketch } from './contributions/save-sketch';
|
||||
import { VerifySketch } from './contributions/verify-sketch';
|
||||
@@ -130,6 +130,18 @@ import { NavigatorTabBarDecorator } from './theia/navigator/navigator-tab-bar-de
|
||||
import { Debug } from './contributions/debug';
|
||||
import { DebugSessionManager } from './theia/debug/debug-session-manager';
|
||||
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||
import { Sketchbook } from './contributions/sketchbook';
|
||||
import { DebugFrontendApplicationContribution } from './theia/debug/debug-frontend-application-contribution';
|
||||
import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||
import { BoardSelection } from './contributions/board-selection';
|
||||
import { OpenRecentSketch } from './contributions/open-recent-sketch';
|
||||
import { Help } from './contributions/help';
|
||||
import { bindArduinoPreferences } from './arduino-preferences'
|
||||
import { SettingsService, SettingsDialog, SettingsWidget, SettingsDialogProps } from './settings';
|
||||
import { AddFile } from './contributions/add-file';
|
||||
import { ArchiveSketch } from './contributions/archive-sketch';
|
||||
import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
import { OutputToolbarContribution } from './theia/output/output-toolbar-contribution';
|
||||
|
||||
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||
|
||||
@@ -173,6 +185,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
// Sketch list service
|
||||
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
|
||||
bind(SketchesServiceClientImpl).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(SketchesServiceClientImpl);
|
||||
|
||||
// Config service
|
||||
bind(ConfigService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ConfigServicePath)).inSingletonScope();
|
||||
@@ -302,6 +315,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ShellLayoutRestorer).toSelf().inSingletonScope();
|
||||
rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer);
|
||||
|
||||
// No dropdown for the _Output_ view.
|
||||
bind(OutputToolbarContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaOutputToolbarContribution).toService(OutputToolbarContribution);
|
||||
|
||||
bind(ArduinoDaemon).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ArduinoDaemonPath)).inSingletonScope();
|
||||
|
||||
// File-system extension
|
||||
@@ -315,7 +332,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
|
||||
Contribution.configure(bind, NewSketch);
|
||||
Contribution.configure(bind, OpenSketch);
|
||||
Contribution.configure(bind, CloseSketch);
|
||||
Contribution.configure(bind, Close);
|
||||
Contribution.configure(bind, SaveSketch);
|
||||
Contribution.configure(bind, SaveAsSketch);
|
||||
Contribution.configure(bind, VerifySketch);
|
||||
@@ -331,6 +348,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
Contribution.configure(bind, IncludeLibrary);
|
||||
Contribution.configure(bind, About);
|
||||
Contribution.configure(bind, Debug);
|
||||
Contribution.configure(bind, Sketchbook);
|
||||
Contribution.configure(bind, BoardSelection);
|
||||
Contribution.configure(bind, OpenRecentSketch);
|
||||
Contribution.configure(bind, Help);
|
||||
Contribution.configure(bind, AddFile);
|
||||
Contribution.configure(bind, ArchiveSketch);
|
||||
|
||||
bind(OutputServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, outputService) => {
|
||||
WebSocketConnectionProvider.createProxy(container, OutputServicePath, outputService);
|
||||
@@ -339,6 +362,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(OutputService).toService(OutputServiceImpl);
|
||||
|
||||
bind(NotificationCenter).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(NotificationCenter);
|
||||
bind(NotificationServiceServer).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, NotificationServicePath)).inSingletonScope();
|
||||
|
||||
// Enable the dirty indicator on uncloseable widgets.
|
||||
@@ -361,4 +385,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
// To avoid running `Save All` when there are no dirty editors before starting the debug session.
|
||||
bind(DebugSessionManager).toSelf().inSingletonScope();
|
||||
rebind(TheiaDebugSessionManager).toService(DebugSessionManager);
|
||||
// To remove the `Run` menu item from the application menu.
|
||||
bind(DebugFrontendApplicationContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaDebugFrontendApplicationContribution).toService(DebugFrontendApplicationContribution);
|
||||
|
||||
// Preferences
|
||||
bindArduinoPreferences(bind);
|
||||
|
||||
// Settings wrapper for the preferences and the CLI config.
|
||||
bind(SettingsService).toSelf().inSingletonScope();
|
||||
// Settings dialog and widget
|
||||
bind(SettingsWidget).toSelf().inSingletonScope();
|
||||
bind(SettingsDialog).toSelf().inSingletonScope();
|
||||
bind(SettingsDialogProps).toConstantValue({
|
||||
title: 'Preferences'
|
||||
});
|
||||
});
|
||||
|
73
arduino-ide-extension/src/browser/arduino-preferences.ts
Normal file
73
arduino-ide-extension/src/browser/arduino-preferences.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { interfaces } from 'inversify';
|
||||
import {
|
||||
createPreferenceProxy,
|
||||
PreferenceProxy,
|
||||
PreferenceService,
|
||||
PreferenceContribution,
|
||||
PreferenceSchema
|
||||
} from '@theia/core/lib/browser/preferences';
|
||||
|
||||
export const ArduinoConfigSchema: PreferenceSchema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'arduino.language.log': {
|
||||
'type': 'boolean',
|
||||
'description': "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.",
|
||||
'default': false
|
||||
},
|
||||
'arduino.compile.verbose': {
|
||||
'type': 'boolean',
|
||||
'description': 'True for verbose compile output. False by default',
|
||||
'default': false
|
||||
},
|
||||
'arduino.upload.verbose': {
|
||||
'type': 'boolean',
|
||||
'description': 'True for verbose upload output. False by default.',
|
||||
'default': false
|
||||
},
|
||||
'arduino.upload.verify': {
|
||||
'type': 'boolean',
|
||||
'default': false
|
||||
},
|
||||
'arduino.window.autoScale': {
|
||||
'type': 'boolean',
|
||||
'description': 'True if the user interface automatically scales with the font size.',
|
||||
'default': true
|
||||
},
|
||||
'arduino.window.zoomLevel': {
|
||||
'type': 'number',
|
||||
'description': 'Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.',
|
||||
'default': 0
|
||||
},
|
||||
'arduino.ide.autoUpdate': {
|
||||
'type': 'boolean',
|
||||
'description': 'True to enable automatic update checks. The IDE will check for updates automatically and periodically.',
|
||||
'default': true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export interface ArduinoConfiguration {
|
||||
'arduino.language.log': boolean;
|
||||
'arduino.compile.verbose': boolean;
|
||||
'arduino.upload.verbose': boolean;
|
||||
'arduino.upload.verify': boolean;
|
||||
'arduino.window.autoScale': boolean;
|
||||
'arduino.window.zoomLevel': number;
|
||||
'arduino.ide.autoUpdate': boolean;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
bind(PreferenceContribution).toConstantValue({ schema: ArduinoConfigSchema });
|
||||
}
|
@@ -1,14 +1,14 @@
|
||||
import * as PQueue from 'p-queue';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { MenuModelRegistry, MenuNode } from '@theia/core/lib/common/menu';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { BoardsServiceProvider } from './boards-service-provider';
|
||||
import { Board, ConfigOption, Programmer } from '../../common/protocol';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { BoardsDataStore } from './boards-data-store';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
||||
@@ -63,7 +63,7 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
||||
this.menuRegistry.registerSubmenu(menuPath, label);
|
||||
this.toDisposeOnBoardChange.pushAll([
|
||||
...commands.values(),
|
||||
Disposable.create(() => this.unregisterSubmenu(menuPath)), // We cannot dispose submenu entries: https://github.com/eclipse-theia/theia/issues/7299
|
||||
Disposable.create(() => unregisterSubmenu(menuPath, this.menuRegistry)),
|
||||
...Array.from(commands.keys()).map((commandId, i) => {
|
||||
const { label } = commands.get(commandId)!;
|
||||
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: `${i}`, label });
|
||||
@@ -76,7 +76,7 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
||||
const programmersMenuPath = [...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, 'z02_programmers'];
|
||||
const label = selectedProgrammer ? `Programmer: "${selectedProgrammer.name}"` : 'Programmer'
|
||||
this.menuRegistry.registerSubmenu(programmersMenuPath, label);
|
||||
this.toDisposeOnBoardChange.push(Disposable.create(() => this.unregisterSubmenu(programmersMenuPath)));
|
||||
this.toDisposeOnBoardChange.push(Disposable.create(() => unregisterSubmenu(programmersMenuPath, this.menuRegistry)));
|
||||
for (const programmer of programmers) {
|
||||
const { id, name } = programmer;
|
||||
const command = { id: `${fqbn}-programmer--${id}` };
|
||||
@@ -98,20 +98,4 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
||||
});
|
||||
}
|
||||
|
||||
protected unregisterSubmenu(menuPath: string[]): void {
|
||||
if (menuPath.length < 2) {
|
||||
throw new Error(`Expected at least two item as a menu-path. Got ${JSON.stringify(menuPath)} instead.`);
|
||||
}
|
||||
const toRemove = menuPath[menuPath.length - 1];
|
||||
const parentMenuPath = menuPath.slice(0, menuPath.length - 1);
|
||||
// This is unsafe. Calling `getMenu` with a non-existing menu-path will result in a new menu creation.
|
||||
// https://github.com/eclipse-theia/theia/issues/7300
|
||||
const parent = this.menuRegistry.getMenu(parentMenuPath);
|
||||
const index = parent.children.findIndex(({ id }) => id === toRemove);
|
||||
if (index === -1) {
|
||||
throw new Error(`Could not find menu with menu-path: ${JSON.stringify(menuPath)}.`);
|
||||
}
|
||||
(parent.children as Array<MenuNode>).splice(index, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -157,7 +157,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
}
|
||||
|
||||
protected getStorageKey(fqbn: string, version: Installable.Version): string {
|
||||
return `.arduinoProIDE-configOptions-${version}-${fqbn}`;
|
||||
return `.arduinoIDE-configOptions-${version}-${fqbn}`;
|
||||
}
|
||||
|
||||
protected async getBoardDetailsSafe(fqbn: string): Promise<BoardDetails | undefined> {
|
||||
|
@@ -57,6 +57,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
* See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ
|
||||
*/
|
||||
protected latestValidBoardsConfig: RecursiveRequired<BoardsConfig.Config> | undefined = undefined;
|
||||
protected latestBoardsConfig: BoardsConfig.Config | undefined = undefined;
|
||||
protected _boardsConfig: BoardsConfig.Config = {};
|
||||
protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only.
|
||||
protected _availablePorts: Port[] = [];
|
||||
@@ -187,6 +188,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
protected doSetBoardsConfig(config: BoardsConfig.Config): void {
|
||||
this.logger.info('Board config changed: ', JSON.stringify(config));
|
||||
this._boardsConfig = config;
|
||||
this.latestBoardsConfig = this._boardsConfig;
|
||||
if (this.canUploadTo(this._boardsConfig)) {
|
||||
this.latestValidBoardsConfig = this._boardsConfig;
|
||||
}
|
||||
@@ -384,7 +386,10 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
const key = this.getLastSelectedBoardOnPortKey(selectedPort);
|
||||
await this.storageService.setData(key, selectedBoard);
|
||||
}
|
||||
await this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig);
|
||||
await Promise.all([
|
||||
this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig),
|
||||
this.storageService.setData('latest-boards-config', this.latestBoardsConfig)
|
||||
]);
|
||||
}
|
||||
|
||||
protected getLastSelectedBoardOnPortKey(port: Port | string): string {
|
||||
@@ -393,15 +398,21 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
}
|
||||
|
||||
protected async loadState(): Promise<void> {
|
||||
const storedValidBoardsConfig = await this.storageService.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
|
||||
if (storedValidBoardsConfig) {
|
||||
this.latestValidBoardsConfig = storedValidBoardsConfig;
|
||||
const storedLatestValidBoardsConfig = await this.storageService.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
|
||||
if (storedLatestValidBoardsConfig) {
|
||||
this.latestValidBoardsConfig = storedLatestValidBoardsConfig;
|
||||
if (this.canUploadTo(this.latestValidBoardsConfig)) {
|
||||
this.boardsConfig = this.latestValidBoardsConfig;
|
||||
}
|
||||
} else {
|
||||
// If we could not restore the latest valid config, try to restore something, the board at least.
|
||||
const storedLatestBoardsConfig = await this.storageService.getData<BoardsConfig.Config | undefined>('latest-boards-config');
|
||||
if (storedLatestBoardsConfig) {
|
||||
this.latestBoardsConfig = storedLatestBoardsConfig;
|
||||
this.boardsConfig = this.latestBoardsConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,15 +1,11 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
import { BoardsListWidget } from './boards-list-widget';
|
||||
import { BoardsPackage } from '../../common/protocol/boards-service';
|
||||
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
|
||||
|
||||
static readonly OPEN_MANAGER = `${BoardsListWidget.WIDGET_ID}:toggle`;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
widgetId: BoardsListWidget.WIDGET_ID,
|
||||
@@ -18,7 +14,7 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
|
||||
area: 'left',
|
||||
rank: 600
|
||||
},
|
||||
toggleCommandId: BoardsListWidgetFrontendContribution.OPEN_MANAGER,
|
||||
toggleCommandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
|
||||
toggleKeybinding: 'CtrlCmd+Shift+B'
|
||||
});
|
||||
}
|
||||
@@ -27,14 +23,4 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
|
||||
this.openView();
|
||||
}
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
if (this.toggleCommand) {
|
||||
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||
commandId: this.toggleCommand.id,
|
||||
label: 'Boards Manager...',
|
||||
order: '4'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -24,31 +24,28 @@ export class About extends Contribution {
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
// On macOS we will get the `Quit ${YOUR_APP_NAME}` menu item natively, no need to duplicate it.
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, {
|
||||
commandId: About.Commands.ABOUT_APP.id,
|
||||
label: `About${isOSX ? ` ${this.applicationName}` : ''}`,
|
||||
label: `About ${this.applicationName}`,
|
||||
order: '0'
|
||||
});
|
||||
}
|
||||
|
||||
async showAbout(): Promise<void> {
|
||||
const ideStatus = FrontendApplicationConfigProvider.get()['status'];
|
||||
const { version, commit, status: cliStatus } = await this.configService.getVersion();
|
||||
const buildDate = this.buildDate;
|
||||
const detail = (useAgo: boolean) => `
|
||||
Version: ${remote.app.getVersion()}
|
||||
Date: ${buildDate ? buildDate : 'dev build'}${buildDate && useAgo ? ` (${this.ago(buildDate)})` : ''}
|
||||
const detail = (showAll: boolean) => `Version: ${remote.app.getVersion()}
|
||||
Date: ${buildDate ? buildDate : 'dev build'}${buildDate && showAll ? ` (${this.ago(buildDate)})` : ''}
|
||||
CLI Version: ${version}${cliStatus ? ` ${cliStatus}` : ''} [${commit}]
|
||||
|
||||
Copyright © ${new Date().getFullYear()} Arduino SA
|
||||
${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''}
|
||||
`;
|
||||
const ok = 'OK';
|
||||
const copy = 'Copy';
|
||||
const buttons = !isWindows && !isOSX ? [copy, ok] : [ok, copy];
|
||||
const { response } = await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
message: `${this.applicationName}${ideStatus ? ` – ${ideStatus}` : ''}`,
|
||||
title: `${this.applicationName}${ideStatus ? ` – ${ideStatus}` : ''}`,
|
||||
message: `${this.applicationName}`,
|
||||
title: `${this.applicationName}`,
|
||||
type: 'info',
|
||||
detail: detail(true),
|
||||
buttons,
|
||||
@@ -58,7 +55,7 @@ Copyright © ${new Date().getFullYear()} Arduino SA
|
||||
});
|
||||
|
||||
if (buttons[response] === copy) {
|
||||
await this.clipboardService.writeText(detail(false));
|
||||
await this.clipboardService.writeText(detail(false).trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
68
arduino-ide-extension/src/browser/contributions/add-file.ts
Normal file
68
arduino-ide-extension/src/browser/contributions/add-file.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, URI } from './contribution';
|
||||
import { FileDialogService } from '@theia/filesystem/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class AddFile extends SketchContribution {
|
||||
|
||||
@inject(FileDialogService)
|
||||
protected readonly fileDialogService: FileDialogService;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(AddFile.Commands.ADD_FILE, {
|
||||
execute: () => this.addFile()
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
|
||||
commandId: AddFile.Commands.ADD_FILE.id,
|
||||
label: 'Add File...',
|
||||
order: '2'
|
||||
});
|
||||
}
|
||||
|
||||
protected async addFile(): Promise<void> {
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
const toAddUri = await this.fileDialogService.showOpenDialog({
|
||||
title: 'Add File',
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false
|
||||
});
|
||||
if (!toAddUri) {
|
||||
return;
|
||||
}
|
||||
const sketchUri = new URI(sketch.uri);
|
||||
const filename = toAddUri.path.base;
|
||||
const targetUri = sketchUri.resolve('data').resolve(filename);
|
||||
const exists = await this.fileService.exists(targetUri);
|
||||
if (exists) {
|
||||
const { response } = await remote.dialog.showMessageBox({
|
||||
type: 'question',
|
||||
title: 'Replace',
|
||||
buttons: ['Cancel', 'OK'],
|
||||
message: `Replace the existing version of ${filename}?`
|
||||
});
|
||||
if (response === 0) { // Cancel
|
||||
return;
|
||||
}
|
||||
}
|
||||
await this.fileService.copy(toAddUri, targetUri, { overwrite: true });
|
||||
this.messageService.info('One file added to the sketch.', { timeout: 2000 });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace AddFile {
|
||||
export namespace Commands {
|
||||
export const ADD_FILE: Command = {
|
||||
id: 'arduino-add-file'
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import * as dateFormat from 'dateformat';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class ArchiveSketch extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, {
|
||||
execute: () => this.archiveSketch()
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||
commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id,
|
||||
label: 'Archive Sketch',
|
||||
order: '1'
|
||||
});
|
||||
}
|
||||
|
||||
protected async archiveSketch(): Promise<void> {
|
||||
const [sketch, config] = await Promise.all([
|
||||
this.sketchServiceClient.currentSketch(),
|
||||
this.configService.getConfiguration()
|
||||
]);
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
const archiveBasename = `${sketch.name}-${dateFormat(new Date(), 'yymmdd')}a.zip`;
|
||||
const defaultPath = await this.fileService.fsPath(new URI(config.sketchDirUri).resolve(archiveBasename));
|
||||
const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath });
|
||||
if (!filePath || canceled) {
|
||||
return;
|
||||
}
|
||||
const destinationUri = await this.fileSystemExt.getUri(filePath);
|
||||
if (!destinationUri) {
|
||||
return;
|
||||
}
|
||||
await this.sketchService.archive(sketch, destinationUri.toString());
|
||||
this.messageService.info(`Created archive '${archiveBasename}'.`, { timeout: 2000 });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace ArchiveSketch {
|
||||
export namespace Commands {
|
||||
export const ARCHIVE_SKETCH: Command = {
|
||||
id: 'arduino-archive-sketch'
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,222 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { firstToUpperCase } from '../../common/utils';
|
||||
import { BoardsConfig } from '../boards/boards-config';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { BoardsListWidget } from '../boards/boards-list-widget';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { ArduinoMenus, PlaceholderMenuNode, unregisterSubmenu } from '../menu/arduino-menus';
|
||||
import { BoardsService, InstalledBoardWithPackage, AvailablePorts, Port } from '../../common/protocol';
|
||||
import { SketchContribution, Command, CommandRegistry } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class BoardSelection extends SketchContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuModelRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
@inject(BoardsService)
|
||||
protected readonly boardsService: BoardsService;
|
||||
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
|
||||
protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection();
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, {
|
||||
execute: async () => {
|
||||
const { selectedBoard, selectedPort } = this.boardsServiceProvider.boardsConfig;
|
||||
if (!selectedBoard) {
|
||||
this.messageService.info('Please select a board to obtain board info.');
|
||||
return;
|
||||
}
|
||||
if (!selectedBoard.fqbn) {
|
||||
this.messageService.info(`The platform for the selected '${selectedBoard.name}' board is not installed.`);
|
||||
return;
|
||||
}
|
||||
if (!selectedPort) {
|
||||
this.messageService.info('Please select a port to obtain board info.');
|
||||
return;
|
||||
}
|
||||
const boardDetails = await this.boardsService.getBoardDetails({ fqbn: selectedBoard.fqbn });
|
||||
if (boardDetails) {
|
||||
const { VID, PID } = boardDetails;
|
||||
const detail = `BN: ${selectedBoard.name}
|
||||
VID: ${VID}
|
||||
PID: ${PID}`;
|
||||
await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
message: 'Board Info',
|
||||
title: 'Board Info',
|
||||
type: 'info',
|
||||
detail,
|
||||
buttons: ['OK']
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onStart(): void {
|
||||
this.updateMenus();
|
||||
this.notificationCenter.onPlatformInstalled(this.updateMenus.bind(this));
|
||||
this.notificationCenter.onPlatformUninstalled(this.updateMenus.bind(this));
|
||||
this.boardsServiceProvider.onBoardsConfigChanged(this.updateMenus.bind(this));
|
||||
this.boardsServiceProvider.onAvailableBoardsChanged(this.updateMenus.bind(this));
|
||||
}
|
||||
|
||||
protected async updateMenus(): Promise<void> {
|
||||
const [installedBoards, availablePorts, config] = await Promise.all([
|
||||
this.installedBoards(),
|
||||
this.boardsService.getState(),
|
||||
this.boardsServiceProvider.boardsConfig
|
||||
]);
|
||||
this.rebuildMenus(installedBoards, availablePorts, config);
|
||||
}
|
||||
|
||||
protected rebuildMenus(installedBoards: InstalledBoardWithPackage[], availablePorts: AvailablePorts, config: BoardsConfig.Config): void {
|
||||
this.toDisposeBeforeMenuRebuild.dispose();
|
||||
|
||||
// Boards submenu
|
||||
const boardsSubmenuPath = [...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, '1_boards'];
|
||||
const boardsSubmenuLabel = config.selectedBoard?.name;
|
||||
// Note: The submenu order starts from `100` because `Auto Format`, `Serial Monitor`, etc starts from `0` index.
|
||||
// The board specific items, and the rest, have order with `z`. We needed something between `0` and `z` with natural-order.
|
||||
this.menuModelRegistry.registerSubmenu(boardsSubmenuPath, `Board${!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''}`, { order: '100' });
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => unregisterSubmenu(boardsSubmenuPath, this.menuModelRegistry)));
|
||||
|
||||
// Ports submenu
|
||||
const portsSubmenuPath = [...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, '2_ports'];
|
||||
const portsSubmenuLabel = config.selectedPort?.address;
|
||||
this.menuModelRegistry.registerSubmenu(portsSubmenuPath, `Port${!!portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''}`, { order: '101' });
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => unregisterSubmenu(portsSubmenuPath, this.menuModelRegistry)));
|
||||
|
||||
const getBoardInfo = { commandId: BoardSelection.Commands.GET_BOARD_INFO.id, label: 'Get Board Info', order: '103' };
|
||||
this.menuModelRegistry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, getBoardInfo);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.menuModelRegistry.unregisterMenuAction(getBoardInfo)));
|
||||
|
||||
const boardsManagerGroup = [...boardsSubmenuPath, '0_manager'];
|
||||
const boardsPackagesGroup = [...boardsSubmenuPath, '1_packages'];
|
||||
|
||||
this.menuModelRegistry.registerMenuAction(boardsManagerGroup, {
|
||||
commandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
|
||||
label: 'Boards Manager...'
|
||||
});
|
||||
|
||||
// Installed boards
|
||||
for (const board of installedBoards) {
|
||||
const { packageId, packageName, fqbn, name } = board;
|
||||
|
||||
// Platform submenu
|
||||
const platformMenuPath = [...boardsPackagesGroup, packageId];
|
||||
// Note: Registering the same submenu twice is a noop. No need to group the boards per platform.
|
||||
this.menuModelRegistry.registerSubmenu(platformMenuPath, packageName);
|
||||
|
||||
const id = `arduino-select-board--${fqbn}`;
|
||||
const command = { id };
|
||||
const handler = {
|
||||
execute: () => {
|
||||
if (fqbn !== this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn) {
|
||||
this.boardsServiceProvider.boardsConfig = {
|
||||
selectedBoard: {
|
||||
name,
|
||||
fqbn,
|
||||
port: this.boardsServiceProvider.boardsConfig.selectedBoard?.port // TODO: verify!
|
||||
},
|
||||
selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort
|
||||
}
|
||||
}
|
||||
},
|
||||
isToggled: () => fqbn === this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn
|
||||
};
|
||||
|
||||
// Board menu
|
||||
const menuAction = { commandId: id, label: name };
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.commandRegistry.unregisterCommand(command)));
|
||||
this.menuModelRegistry.registerMenuAction(platformMenuPath, menuAction);
|
||||
// Note: we do not dispose the menu actions individually. Calling `unregisterSubmenu` on the parent will wipe the children menu nodes recursively.
|
||||
}
|
||||
|
||||
// Installed ports
|
||||
const registerPorts = (ports: AvailablePorts) => {
|
||||
const addresses = Object.keys(ports);
|
||||
if (!addresses.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register placeholder for protocol
|
||||
const [port] = ports[addresses[0]];
|
||||
const protocol = port.protocol;
|
||||
const menuPath = [...portsSubmenuPath, protocol];
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, `${firstToUpperCase(port.protocol)} ports`);
|
||||
this.menuModelRegistry.registerMenuNode(menuPath, placeholder);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.menuModelRegistry.unregisterMenuNode(placeholder.id)));
|
||||
|
||||
for (const address of addresses) {
|
||||
if (!!ports[address]) {
|
||||
const [port, boards] = ports[address];
|
||||
if (!boards.length) {
|
||||
boards.push({
|
||||
name: ''
|
||||
});
|
||||
}
|
||||
for (const { name, fqbn } of boards) {
|
||||
const id = `arduino-select-port--${address}${fqbn ? `--${fqbn}` : ''}`;
|
||||
const command = { id };
|
||||
const handler = {
|
||||
execute: () => {
|
||||
if (!Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)) {
|
||||
this.boardsServiceProvider.boardsConfig = {
|
||||
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
|
||||
selectedPort: port
|
||||
}
|
||||
}
|
||||
},
|
||||
isToggled: () => Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)
|
||||
};
|
||||
const label = `${address}${name ? ` (${name})` : ''}`;
|
||||
const menuAction = {
|
||||
commandId: id,
|
||||
label,
|
||||
order: `1${label}` // `1` comes after the placeholder which has order `0`
|
||||
};
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.commandRegistry.unregisterCommand(command)));
|
||||
this.menuModelRegistry.registerMenuAction(menuPath, menuAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { serial, network, unknown } = AvailablePorts.groupByProtocol(availablePorts);
|
||||
registerPorts(serial);
|
||||
registerPorts(network);
|
||||
registerPorts(unknown);
|
||||
|
||||
this.mainMenuManager.update();
|
||||
}
|
||||
|
||||
protected async installedBoards(): Promise<InstalledBoardWithPackage[]> {
|
||||
const allBoards = await this.boardsService.allBoards({});
|
||||
return allBoards.filter(InstalledBoardWithPackage.is);
|
||||
}
|
||||
|
||||
}
|
||||
export namespace BoardSelection {
|
||||
export namespace Commands {
|
||||
export const GET_BOARD_INFO: Command = { id: 'arduino-get-board-info' };
|
||||
}
|
||||
}
|
@@ -47,15 +47,19 @@ export class BurnBootloader extends SketchContribution {
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const port = boardsConfig.selectedPort?.address;
|
||||
const [fqbn, { selectedProgrammer: programmer }] = await Promise.all([
|
||||
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn)
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||
this.preferences.get('arduino.upload.verify'),
|
||||
this.preferences.get('arduino.upload.verbose')
|
||||
]);
|
||||
this.outputChannelManager.getChannel('Arduino: bootloader').clear();
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
await this.coreService.burnBootloader({
|
||||
fqbn,
|
||||
programmer,
|
||||
port
|
||||
port,
|
||||
verify,
|
||||
verbose
|
||||
});
|
||||
this.messageService.info('Done burning bootloader.', { timeout: 1000 });
|
||||
} catch (e) {
|
||||
|
@@ -1,20 +1,50 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { toArray } from '@phosphor/algorithm';
|
||||
import { remote } from 'electron';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, URI } from './contribution';
|
||||
import { SaveAsSketch } from './save-as-sketch';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
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';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SaveAsSketch } from './save-as-sketch';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, URI } from './contribution';
|
||||
|
||||
/**
|
||||
* Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window.
|
||||
*/
|
||||
@injectable()
|
||||
export class CloseSketch extends SketchContribution {
|
||||
export class Close extends SketchContribution {
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
protected shell: ApplicationShell;
|
||||
|
||||
onStart(app: FrontendApplication): void {
|
||||
this.shell = app.shell;
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(CloseSketch.Commands.CLOSE_SKETCH, {
|
||||
registry.registerCommand(Close.Commands.CLOSE, {
|
||||
execute: async () => {
|
||||
|
||||
// Close current editor if closeable.
|
||||
const { currentEditor } = this.editorManager;
|
||||
if (currentEditor && currentEditor.title.closable) {
|
||||
currentEditor.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close current widget from the main area if possible.
|
||||
const { currentWidget } = this.shell;
|
||||
if (currentWidget) {
|
||||
const currentWidgetInMain = toArray(this.shell.mainPanel.widgets()).find(widget => widget === currentWidget);
|
||||
if (currentWidgetInMain && currentWidgetInMain.title.closable) {
|
||||
return currentWidgetInMain.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Close the sketch (window).
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
@@ -48,7 +78,7 @@ export class CloseSketch extends SketchContribution {
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: CloseSketch.Commands.CLOSE_SKETCH.id,
|
||||
commandId: Close.Commands.CLOSE.id,
|
||||
label: 'Close',
|
||||
order: '5'
|
||||
});
|
||||
@@ -56,7 +86,7 @@ export class CloseSketch extends SketchContribution {
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: CloseSketch.Commands.CLOSE_SKETCH.id,
|
||||
command: Close.Commands.CLOSE.id,
|
||||
keybinding: 'CtrlCmd+W'
|
||||
});
|
||||
}
|
||||
@@ -80,10 +110,10 @@ export class CloseSketch extends SketchContribution {
|
||||
|
||||
}
|
||||
|
||||
export namespace CloseSketch {
|
||||
export namespace Close {
|
||||
export namespace Commands {
|
||||
export const CLOSE_SKETCH: Command = {
|
||||
id: 'arduino-close-sketch'
|
||||
export const CLOSE: Command = {
|
||||
id: 'arduino-close'
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,20 +1,24 @@
|
||||
import { inject, injectable, interfaces } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { Saveable } from '@theia/core/lib/browser/saveable';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
|
||||
import { MenuModelRegistry, MenuContribution } from '@theia/core/lib/common/menu';
|
||||
import { KeybindingRegistry, KeybindingContribution } from '@theia/core/lib/browser/keybinding';
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { Command, CommandRegistry, CommandContribution, CommandService } from '@theia/core/lib/common/command';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
import { SettingsService } from '../settings';
|
||||
import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl';
|
||||
import { SketchesService, ConfigService, FileSystemExt, Sketch } from '../../common/protocol';
|
||||
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
|
||||
import { ArduinoPreferences } from '../arduino-preferences';
|
||||
|
||||
export { Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry, URI, Sketch, open };
|
||||
|
||||
@@ -39,6 +43,9 @@ export abstract class Contribution implements CommandContribution, MenuContribut
|
||||
@inject(LabelProvider)
|
||||
protected readonly labelProvider: LabelProvider;
|
||||
|
||||
@inject(SettingsService)
|
||||
protected readonly settingsService: SettingsService;
|
||||
|
||||
onStart(app: FrontendApplication): MaybePromise<void> {
|
||||
}
|
||||
|
||||
@@ -77,6 +84,26 @@ export abstract class SketchContribution extends Contribution {
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
@inject(ArduinoPreferences)
|
||||
protected readonly preferences: ArduinoPreferences;
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
protected async sourceOverride(): Promise<Record<string, string>> {
|
||||
const override: Record<string, string> = {};
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (sketch) {
|
||||
for (const editor of this.editorManager.all) {
|
||||
const uri = editor.editor.uri;
|
||||
if (Saveable.isDirty(editor) && Sketch.isInSketch(uri, sketch)) {
|
||||
override[uri.toString()] = editor.editor.document.getText();
|
||||
}
|
||||
}
|
||||
}
|
||||
return override;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Contribution {
|
||||
|
@@ -3,7 +3,6 @@ import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribu
|
||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||
import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service';
|
||||
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
|
||||
import { EDITOR_FONT_DEFAULTS } from '@theia/editor/lib/browser/editor-preferences';
|
||||
import { Contribution, Command, MenuModelRegistry, KeybindingRegistry, CommandRegistry } from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@@ -31,10 +30,28 @@ export class EditContributions extends Contribution {
|
||||
registry.registerCommand(EditContributions.Commands.FIND_PREVIOUS, { execute: () => this.run('editor.action.nextMatchFindAction') });
|
||||
registry.registerCommand(EditContributions.Commands.USE_FOR_FIND, { execute: () => this.run('editor.action.previousSelectionMatchFindAction') });
|
||||
registry.registerCommand(EditContributions.Commands.INCREASE_FONT_SIZE, {
|
||||
execute: () => this.preferences.set('editor.fontSize', this.preferences.get('editor.fontSize', EDITOR_FONT_DEFAULTS.fontSize) + 1)
|
||||
execute: async () => {
|
||||
const settings = await this.settingsService.settings();
|
||||
if (settings.autoScaleInterface) {
|
||||
settings.interfaceScale = settings.interfaceScale + 1;
|
||||
} else {
|
||||
settings.editorFontSize = settings.editorFontSize + 1;
|
||||
}
|
||||
await this.settingsService.update(settings);
|
||||
await this.settingsService.save();
|
||||
}
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.DECREASE_FONT_SIZE, {
|
||||
execute: () => this.preferences.set('editor.fontSize', this.preferences.get('editor.fontSize', EDITOR_FONT_DEFAULTS.fontSize) - 1)
|
||||
execute: async () => {
|
||||
const settings = await this.settingsService.settings();
|
||||
if (settings.autoScaleInterface) {
|
||||
settings.interfaceScale = settings.interfaceScale - 1;
|
||||
} else {
|
||||
settings.editorFontSize = settings.editorFontSize - 1;
|
||||
}
|
||||
await this.settingsService.update(settings);
|
||||
await this.settingsService.save();
|
||||
}
|
||||
});
|
||||
/* Tools */registry.registerCommand(EditContributions.Commands.AUTO_FORMAT, { execute: () => this.run('editor.action.formatDocument') });
|
||||
registry.registerCommand(EditContributions.Commands.COPY_FOR_FORUM, {
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import * as PQueue from 'p-queue';
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { MenuPath, SubMenuOptions, CompositeMenuNode } from '@theia/core/lib/common/menu';
|
||||
import { MenuPath, CompositeMenuNode } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { OpenSketch } from './open-sketch';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { ExamplesService, ExampleContainer } from '../../common/protocol/examples-service';
|
||||
import { SketchContribution, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { Board } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export abstract class Examples extends SketchContribution {
|
||||
@@ -32,10 +33,10 @@ export abstract class Examples extends SketchContribution {
|
||||
|
||||
@postConstruct()
|
||||
init(): void {
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.handleBoardChanged(selectedBoard?.fqbn));
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.handleBoardChanged(selectedBoard));
|
||||
}
|
||||
|
||||
protected handleBoardChanged(fqbn: string | undefined): void {
|
||||
protected handleBoardChanged(board: Board | undefined): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@@ -58,28 +59,33 @@ export abstract class Examples extends SketchContribution {
|
||||
}
|
||||
|
||||
registerRecursively(
|
||||
exampleContainer: ExampleContainer,
|
||||
exampleContainerOrPlaceholder: ExampleContainer | string,
|
||||
menuPath: MenuPath,
|
||||
pushToDispose: DisposableCollection = new DisposableCollection(),
|
||||
options?: SubMenuOptions): void {
|
||||
pushToDispose: DisposableCollection = new DisposableCollection()): void {
|
||||
|
||||
const { label, sketches, children } = exampleContainer;
|
||||
const submenuPath = [...menuPath, label];
|
||||
this.menuRegistry.registerSubmenu(submenuPath, label, options);
|
||||
children.forEach(child => this.registerRecursively(child, submenuPath, pushToDispose));
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
const commandId = `arduino-open-example-${submenuPath.join(':')}--${uri}`;
|
||||
const command = { id: commandId };
|
||||
const handler = {
|
||||
execute: async () => {
|
||||
const sketch = await this.sketchService.cloneExample(uri);
|
||||
this.commandService.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch);
|
||||
}
|
||||
};
|
||||
pushToDispose.push(this.commandRegistry.registerCommand(command, handler));
|
||||
this.menuRegistry.registerMenuAction(submenuPath, { commandId, label: sketch.name });
|
||||
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||
if (typeof exampleContainerOrPlaceholder === 'string') {
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, exampleContainerOrPlaceholder);
|
||||
this.menuRegistry.registerMenuNode(menuPath, placeholder);
|
||||
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id)));
|
||||
} else {
|
||||
const { label, sketches, children } = exampleContainerOrPlaceholder;
|
||||
const submenuPath = [...menuPath, label];
|
||||
this.menuRegistry.registerSubmenu(submenuPath, label);
|
||||
children.forEach(child => this.registerRecursively(child, submenuPath, pushToDispose));
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
const commandId = `arduino-open-example-${submenuPath.join(':')}--${uri}`;
|
||||
const command = { id: commandId };
|
||||
const handler = {
|
||||
execute: async () => {
|
||||
const sketch = await this.sketchService.cloneExample(uri);
|
||||
this.commandService.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch);
|
||||
}
|
||||
};
|
||||
pushToDispose.push(this.commandRegistry.registerCommand(command, handler));
|
||||
this.menuRegistry.registerMenuAction(submenuPath, { commandId, label: sketch.name });
|
||||
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +108,12 @@ export class BuiltInExamples extends Examples {
|
||||
return;
|
||||
}
|
||||
this.toDispose.dispose();
|
||||
for (const container of exampleContainers) {
|
||||
for (const container of ['Built-in examples', ...exampleContainers]) {
|
||||
this.registerRecursively(container, ArduinoMenus.EXAMPLES__BUILT_IN_GROUP, this.toDispose);
|
||||
}
|
||||
this.menuManager.update();
|
||||
// TODO: remove
|
||||
console.log(typeof this.menuRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -124,17 +132,27 @@ export class LibraryExamples extends Examples {
|
||||
this.notificationCenter.onLibraryUninstalled(() => this.register());
|
||||
}
|
||||
|
||||
protected handleBoardChanged(fqbn: string | undefined): void {
|
||||
this.register(fqbn);
|
||||
protected handleBoardChanged(board: Board | undefined): void {
|
||||
this.register(board);
|
||||
}
|
||||
|
||||
protected async register(fqbn: string | undefined = this.boardsServiceClient.boardsConfig.selectedBoard?.fqbn) {
|
||||
protected async register(board: Board | undefined = this.boardsServiceClient.boardsConfig.selectedBoard) {
|
||||
return this.queue.add(async () => {
|
||||
this.toDispose.dispose();
|
||||
if (!fqbn) {
|
||||
if (!board || !board.fqbn) {
|
||||
return;
|
||||
}
|
||||
const { fqbn, name } = board;
|
||||
const { user, current, any } = await this.examplesService.installed({ fqbn });
|
||||
if (user.length) {
|
||||
(user as any).unshift('Examples from Custom Libraries');
|
||||
}
|
||||
if (current.length) {
|
||||
(current as any).unshift(`Examples for ${name}`);
|
||||
}
|
||||
if (any.length) {
|
||||
(any as any).unshift('Examples for any board');
|
||||
}
|
||||
for (const container of user) {
|
||||
this.registerRecursively(container, ArduinoMenus.EXAMPLES__USER_LIBS_GROUP, this.toDispose);
|
||||
}
|
||||
|
137
arduino-ide-extension/src/browser/contributions/help.ts
Normal file
137
arduino-ide-extension/src/browser/contributions/help.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { CommandHandler } from '@theia/core/lib/common/command';
|
||||
import { QuickInputService } from '@theia/core/lib/browser/quick-open/quick-input-service';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { Contribution, Command, MenuModelRegistry, CommandRegistry, KeybindingRegistry } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class Help extends Contribution {
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
@inject(WindowService)
|
||||
protected readonly windowService: WindowService;
|
||||
|
||||
@inject(QuickInputService)
|
||||
protected readonly quickInputService: QuickInputService;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
const open = (url: string) => this.windowService.openNewWindow(url, { external: true });
|
||||
const createOpenHandler = (url: string) => <CommandHandler>{
|
||||
execute: () => open(url)
|
||||
};
|
||||
registry.registerCommand(Help.Commands.GETTING_STARTED, createOpenHandler('https://www.arduino.cc/en/Guide'));
|
||||
registry.registerCommand(Help.Commands.ENVIRONMENT, createOpenHandler('https://www.arduino.cc/en/Guide/Environment'));
|
||||
registry.registerCommand(Help.Commands.TROUBLESHOOTING, createOpenHandler('https://support.arduino.cc/hc/en-us'));
|
||||
registry.registerCommand(Help.Commands.REFERENCE, createOpenHandler('https://www.arduino.cc/reference/en/'));
|
||||
registry.registerCommand(Help.Commands.FIND_IN_REFERENCE, {
|
||||
execute: async () => {
|
||||
let searchFor: string | undefined = undefined;
|
||||
const { currentEditor } = this.editorManager;
|
||||
if (currentEditor && currentEditor.editor instanceof MonacoEditor) {
|
||||
const codeEditor = currentEditor.editor.getControl();
|
||||
const selection = codeEditor.getSelection();
|
||||
const model = codeEditor.getModel();
|
||||
if (model && selection && !monaco.Range.isEmpty(selection)) {
|
||||
searchFor = model.getValueInRange(selection);
|
||||
}
|
||||
}
|
||||
if (!searchFor) {
|
||||
searchFor = await this.quickInputService.open({
|
||||
prompt: 'Search on Arduino.cc',
|
||||
placeHolder: 'Type a keyword'
|
||||
});
|
||||
}
|
||||
if (searchFor) {
|
||||
return open(`https://www.arduino.cc/search?q=${encodeURIComponent(searchFor)}&tab=reference`);
|
||||
}
|
||||
}
|
||||
});
|
||||
registry.registerCommand(Help.Commands.FAQ, createOpenHandler('https://support.arduino.cc/hc/en-us'));
|
||||
registry.registerCommand(Help.Commands.VISIT_ARDUINO, createOpenHandler('https://www.arduino.cc/'));
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||
commandId: Help.Commands.GETTING_STARTED.id,
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||
commandId: Help.Commands.ENVIRONMENT.id,
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||
commandId: Help.Commands.TROUBLESHOOTING.id,
|
||||
order: '2'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||
commandId: Help.Commands.REFERENCE.id,
|
||||
order: '3'
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
|
||||
commandId: Help.Commands.FIND_IN_REFERENCE.id,
|
||||
order: '4'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
|
||||
commandId: Help.Commands.FAQ.id,
|
||||
order: '5'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
|
||||
commandId: Help.Commands.VISIT_ARDUINO.id,
|
||||
order: '6'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: Help.Commands.FIND_IN_REFERENCE.id,
|
||||
keybinding: 'CtrlCmd+Shift+F'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Help {
|
||||
export namespace Commands {
|
||||
export const GETTING_STARTED: Command = {
|
||||
id: 'arduino-getting-started',
|
||||
label: 'Getting Started',
|
||||
category: 'Arduino'
|
||||
};
|
||||
export const ENVIRONMENT: Command = {
|
||||
id: 'arduino-environment',
|
||||
label: 'Environment',
|
||||
category: 'Arduino'
|
||||
};
|
||||
export const TROUBLESHOOTING: Command = {
|
||||
id: 'arduino-troubleshooting',
|
||||
label: 'Troubleshooting',
|
||||
category: 'Arduino'
|
||||
};
|
||||
export const REFERENCE: Command = {
|
||||
id: 'arduino-reference',
|
||||
label: 'Reference',
|
||||
category: 'Arduino'
|
||||
};
|
||||
export const FIND_IN_REFERENCE: Command = {
|
||||
id: 'arduino-find-in-reference',
|
||||
label: 'Find in Reference',
|
||||
category: 'Arduino'
|
||||
};
|
||||
export const FAQ: Command = {
|
||||
id: 'arduino-faq',
|
||||
label: 'Frequently Asked Questions',
|
||||
category: 'Arduino'
|
||||
};
|
||||
export const VISIT_ARDUINO: Command = {
|
||||
id: 'arduino-visit-arduino',
|
||||
label: 'Visit Arduino.cc',
|
||||
category: 'Arduino'
|
||||
};
|
||||
}
|
||||
}
|
@@ -5,8 +5,8 @@ import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { MenuModelRegistry, MenuPath } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { LibraryPackage, LibraryLocation, LibraryService } from '../../common/protocol';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||
import { LibraryPackage, LibraryService } from '../../common/protocol';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { LibraryListWidget } from '../library/library-list-widget';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
@@ -84,19 +84,35 @@ export class IncludeLibrary extends SketchContribution {
|
||||
// `Arduino libraries`
|
||||
const packageMenuPath = [...includeLibMenuPath, '2_arduino'];
|
||||
const userMenuPath = [...includeLibMenuPath, '3_contributed'];
|
||||
for (const library of libraries) {
|
||||
this.toDispose.push(this.registerLibrary(library, library.location === LibraryLocation.USER ? userMenuPath : packageMenuPath));
|
||||
const { user, rest } = LibraryPackage.groupByLocation(libraries);
|
||||
if (rest.length) {
|
||||
(rest as any).unshift('Arduino libraries');
|
||||
}
|
||||
if (user.length) {
|
||||
(user as any).unshift('Contributed libraries');
|
||||
}
|
||||
|
||||
for (const library of user) {
|
||||
this.toDispose.push(this.registerLibrary(library, userMenuPath));
|
||||
}
|
||||
for (const library of rest) {
|
||||
this.toDispose.push(this.registerLibrary(library, packageMenuPath));
|
||||
}
|
||||
|
||||
this.mainMenuManager.update();
|
||||
});
|
||||
}
|
||||
|
||||
protected registerLibrary(library: LibraryPackage, menuPath: MenuPath): Disposable {
|
||||
const commandId = `arduino-include-library--${library.name}:${library.author}`;
|
||||
protected registerLibrary(libraryOrPlaceholder: LibraryPackage | string, menuPath: MenuPath): Disposable {
|
||||
if (typeof libraryOrPlaceholder === 'string') {
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, libraryOrPlaceholder);
|
||||
this.menuRegistry.registerMenuNode(menuPath, placeholder);
|
||||
return Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id));
|
||||
}
|
||||
const commandId = `arduino-include-library--${libraryOrPlaceholder.name}:${libraryOrPlaceholder.author}`;
|
||||
const command = { id: commandId };
|
||||
const handler = { execute: () => this.commandRegistry.executeCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY.id, library) };
|
||||
const menuAction = { commandId, label: library.name };
|
||||
const handler = { execute: () => this.commandRegistry.executeCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY.id, libraryOrPlaceholder) };
|
||||
const menuAction = { commandId, label: libraryOrPlaceholder.name };
|
||||
this.menuRegistry.registerMenuAction(menuPath, menuAction);
|
||||
return new DisposableCollection(
|
||||
this.commandRegistry.registerCommand(command, handler),
|
||||
|
@@ -0,0 +1,62 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { WorkspaceServer } from '@theia/workspace/lib/common/workspace-protocol';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { SketchContribution, CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { OpenSketch } from './open-sketch';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
|
||||
@injectable()
|
||||
export class OpenRecentSketch extends SketchContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
@inject(WorkspaceServer)
|
||||
protected readonly workspaceServer: WorkspaceServer;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
protected toDisposeBeforeRegister = new Map<string, DisposableCollection>();
|
||||
|
||||
onStart(): void {
|
||||
const refreshMenu = (sketches: Sketch[]) => {
|
||||
this.register(sketches);
|
||||
this.mainMenuManager.update();
|
||||
};
|
||||
this.notificationCenter.onRecentSketchesChanged(({ sketches }) => refreshMenu(sketches));
|
||||
this.sketchService.recentlyOpenedSketches().then(refreshMenu);
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerSubmenu(ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, 'Open Recent', { order: '2' });
|
||||
}
|
||||
|
||||
protected register(sketches: Sketch[]): void {
|
||||
let order = 0;
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
const toDispose = this.toDisposeBeforeRegister.get(uri);
|
||||
if (toDispose) {
|
||||
toDispose.dispose();
|
||||
}
|
||||
const command = { id: `arduino-open-recent--${uri}` };
|
||||
const handler = { execute: () => this.commandRegistry.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch) };
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.menuRegistry.registerMenuAction(ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, { commandId: command.id, label: sketch.name, order: String(order) });
|
||||
this.toDisposeBeforeRegister.set(sketch.uri, new DisposableCollection(
|
||||
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
|
||||
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -122,7 +122,7 @@ export class OpenSketch extends SketchContribution {
|
||||
filters: [
|
||||
{
|
||||
name: 'Sketch',
|
||||
extensions: ['ino']
|
||||
extensions: ['ino', 'pde']
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -138,7 +138,7 @@ export class OpenSketch extends SketchContribution {
|
||||
if (sketch) {
|
||||
return sketch;
|
||||
}
|
||||
if (sketchFileUri.endsWith('.ino')) {
|
||||
if (Sketch.isSketchFile(sketchFileUri)) {
|
||||
const name = new URI(sketchFileUri).path.name;
|
||||
const nameWithExt = this.labelProvider.getName(new URI(sketchFileUri));
|
||||
const { response } = await remote.dialog.showMessageBox({
|
||||
|
@@ -60,8 +60,10 @@ export class SaveAsSketch extends SketchContribution {
|
||||
}
|
||||
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
|
||||
if (workspaceUri && openAfterMove) {
|
||||
if (wipeOriginal) {
|
||||
await this.fileService.delete(new URI(sketch.uri));
|
||||
if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) {
|
||||
try {
|
||||
await this.fileService.delete(new URI(sketch.uri), { recursive: true });
|
||||
} catch { /* NOOP: from time to time, it's not possible to wipe the old resource from the temp dir on Windows */ }
|
||||
}
|
||||
this.workspaceService.open(new URI(workspaceUri), { preserveWindow: true });
|
||||
}
|
||||
|
@@ -1,27 +1,49 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { URI, Command, MenuModelRegistry, CommandRegistry, SketchContribution, open } from './contribution';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { Command, MenuModelRegistry, CommandRegistry, SketchContribution, KeybindingRegistry } from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { Settings as Preferences, SettingsDialog } from '../settings';
|
||||
|
||||
@injectable()
|
||||
export class Settings extends SketchContribution {
|
||||
|
||||
@inject(SettingsDialog)
|
||||
protected readonly settingsDialog: SettingsDialog;
|
||||
|
||||
protected settingsOpened = false;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(Settings.Commands.OPEN_CLI_CONFIG, {
|
||||
execute: () => this.configService.getCliConfigFileUri().then(uri => open(this.openerService, new URI(uri)))
|
||||
registry.registerCommand(Settings.Commands.OPEN, {
|
||||
execute: async () => {
|
||||
let settings: Preferences | undefined = undefined;
|
||||
try {
|
||||
this.settingsOpened = true;
|
||||
settings = await this.settingsDialog.open();
|
||||
} finally {
|
||||
this.settingsOpened = false;
|
||||
}
|
||||
if (settings) {
|
||||
await this.settingsService.update(settings);
|
||||
await this.settingsService.save();
|
||||
} else {
|
||||
await this.settingsService.reset();
|
||||
}
|
||||
},
|
||||
isEnabled: () => !this.settingsOpened
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SETTINGS_GROUP, {
|
||||
commandId: CommonCommands.OPEN_PREFERENCES.id,
|
||||
commandId: Settings.Commands.OPEN.id,
|
||||
label: 'Preferences...',
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SETTINGS_GROUP, {
|
||||
commandId: Settings.Commands.OPEN_CLI_CONFIG.id,
|
||||
label: 'Open CLI Configuration',
|
||||
order: '1',
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: Settings.Commands.OPEN.id,
|
||||
keybinding: 'CtrlCmd+,',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,9 +51,9 @@ export class Settings extends SketchContribution {
|
||||
|
||||
export namespace Settings {
|
||||
export namespace Commands {
|
||||
export const OPEN_CLI_CONFIG: Command = {
|
||||
id: 'arduino-open-cli-config',
|
||||
label: 'Open CLI Configuration',
|
||||
export const OPEN: Command = {
|
||||
id: 'arduino-settings-open',
|
||||
label: 'Open Preferences...',
|
||||
category: 'Arduino'
|
||||
}
|
||||
}
|
||||
|
@@ -40,8 +40,8 @@ export class SketchControl extends SketchContribution {
|
||||
return;
|
||||
}
|
||||
|
||||
const { mainFileUri, otherSketchFileUris, additionalFileUris } = await this.sketchService.loadSketch(sketch.uri);
|
||||
const uris = [mainFileUri, ...otherSketchFileUris, ...additionalFileUris];
|
||||
const { mainFileUri, rootFolderFileUris } = await this.sketchService.loadSketch(sketch.uri);
|
||||
const uris = [mainFileUri, ...rootFolderFileUris];
|
||||
for (let i = 0; i < uris.length; i++) {
|
||||
const uri = new URI(uris[i]);
|
||||
const command = { id: `arduino-focus-file--${uri.toString()}` };
|
||||
|
@@ -0,0 +1,69 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { SketchContribution, CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { OpenSketch } from './open-sketch';
|
||||
|
||||
@injectable()
|
||||
export class Sketchbook extends SketchContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
protected toDisposePerSketch = new Map<string, DisposableCollection>();
|
||||
|
||||
onStart(): void {
|
||||
this.sketchService.getSketches().then(sketches => {
|
||||
this.register(sketches);
|
||||
this.mainMenuManager.update();
|
||||
});
|
||||
this.sketchServiceClient.onSketchbookDidChange(({ created, removed }) => {
|
||||
this.unregister(removed);
|
||||
this.register(created);
|
||||
this.mainMenuManager.update();
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerSubmenu(ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, 'Sketchbook', { order: '3' });
|
||||
}
|
||||
|
||||
protected register(sketches: Sketch[]): void {
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
const toDispose = this.toDisposePerSketch.get(uri);
|
||||
if (toDispose) {
|
||||
toDispose.dispose();
|
||||
}
|
||||
const command = { id: `arduino-sketchbook-open--${uri}` };
|
||||
const handler = { execute: () => this.commandRegistry.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch) };
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.menuRegistry.registerMenuAction(ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, { commandId: command.id, label: sketch.name });
|
||||
this.toDisposePerSketch.set(sketch.uri, new DisposableCollection(
|
||||
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
|
||||
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected unregister(sketches: Sketch[]): void {
|
||||
for (const { uri } of sketches) {
|
||||
const toDispose = this.toDisposePerSketch.get(uri);
|
||||
if (toDispose) {
|
||||
toDispose.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -43,12 +43,12 @@ export class UploadSketch extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||
label: 'Upload',
|
||||
order: '0'
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
|
||||
label: 'Upload Using Programmer',
|
||||
order: '1'
|
||||
order: '2'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,8 +73,8 @@ export class UploadSketch extends SketchContribution {
|
||||
}
|
||||
|
||||
async uploadSketch(usingProgrammer: boolean = false): Promise<void> {
|
||||
const uri = await this.sketchServiceClient.currentSketchFile();
|
||||
if (!uri) {
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
let shouldAutoConnect = false;
|
||||
@@ -88,13 +88,16 @@ export class UploadSketch extends SketchContribution {
|
||||
}
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const [fqbn, { selectedProgrammer }] = await Promise.all([
|
||||
const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn)
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||
this.preferences.get('arduino.upload.verify'),
|
||||
this.preferences.get('arduino.upload.verbose'),
|
||||
this.sourceOverride()
|
||||
]);
|
||||
|
||||
let options: CoreService.Upload.Options | undefined = undefined;
|
||||
const sketchUri = uri;
|
||||
const sketchUri = sketch.uri;
|
||||
const optimizeForDebug = this.editorMode.compileForDebug;
|
||||
const { selectedPort } = boardsConfig;
|
||||
const port = selectedPort?.address;
|
||||
@@ -106,17 +109,23 @@ export class UploadSketch extends SketchContribution {
|
||||
fqbn,
|
||||
optimizeForDebug,
|
||||
programmer,
|
||||
port
|
||||
port,
|
||||
verbose,
|
||||
verify,
|
||||
sourceOverride
|
||||
};
|
||||
} else {
|
||||
options = {
|
||||
sketchUri,
|
||||
fqbn,
|
||||
optimizeForDebug,
|
||||
port
|
||||
port,
|
||||
verbose,
|
||||
verify,
|
||||
sourceOverride
|
||||
};
|
||||
}
|
||||
this.outputChannelManager.getChannel('Arduino: upload').clear();
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
if (usingProgrammer) {
|
||||
await this.coreService.uploadUsingProgrammer(options);
|
||||
} else {
|
||||
|
@@ -26,6 +26,9 @@ export class VerifySketch extends SketchContribution {
|
||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
|
||||
execute: () => this.verifySketch()
|
||||
});
|
||||
registry.registerCommand(VerifySketch.Commands.EXPORT_BINARIES, {
|
||||
execute: () => this.verifySketch(true)
|
||||
});
|
||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: () => registry.executeCommand(VerifySketch.Commands.VERIFY_SKETCH.id)
|
||||
@@ -36,7 +39,12 @@ export class VerifySketch extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: VerifySketch.Commands.VERIFY_SKETCH.id,
|
||||
label: 'Verify/Compile',
|
||||
order: '2'
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: VerifySketch.Commands.EXPORT_BINARIES.id,
|
||||
label: 'Export compiled Binary',
|
||||
order: '3'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,6 +53,10 @@ export class VerifySketch extends SketchContribution {
|
||||
command: VerifySketch.Commands.VERIFY_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+R'
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: VerifySketch.Commands.EXPORT_BINARIES.id,
|
||||
keybinding: 'CtrlCmd+Alt+S'
|
||||
});
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
@@ -56,19 +68,26 @@ export class VerifySketch extends SketchContribution {
|
||||
});
|
||||
}
|
||||
|
||||
async verifySketch(): Promise<void> {
|
||||
const uri = await this.sketchServiceClient.currentSketchFile();
|
||||
if (!uri) {
|
||||
async verifySketch(exportBinaries?: boolean): Promise<void> {
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const fqbn = await this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn);
|
||||
this.outputChannelManager.getChannel('Arduino: compile').clear();
|
||||
const [fqbn, sourceOverride] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||
this.sourceOverride()
|
||||
]);
|
||||
const verbose = this.preferences.get('arduino.compile.verbose');
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
await this.coreService.compile({
|
||||
sketchUri: uri,
|
||||
sketchUri: sketch.uri,
|
||||
fqbn,
|
||||
optimizeForDebug: this.editorMode.compileForDebug
|
||||
optimizeForDebug: this.editorMode.compileForDebug,
|
||||
verbose,
|
||||
exportBinaries,
|
||||
sourceOverride
|
||||
});
|
||||
this.messageService.info('Done compiling.', { timeout: 1000 });
|
||||
} catch (e) {
|
||||
@@ -83,6 +102,9 @@ export namespace VerifySketch {
|
||||
export const VERIFY_SKETCH: Command = {
|
||||
id: 'arduino-verify-sketch'
|
||||
};
|
||||
export const EXPORT_BINARIES: Command = {
|
||||
id: 'arduino-export-binaries'
|
||||
};
|
||||
export const VERIFY_SKETCH_TOOLBAR: Command = {
|
||||
id: 'arduino-verify-sketch--toolbar'
|
||||
};
|
||||
|
@@ -1,10 +1,6 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { ApplicationShell, FrontendApplicationContribution, FrontendApplication, Widget } from '@theia/core/lib/browser';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
|
||||
import { MainMenuManager } from '../common/main-menu-manager';
|
||||
import { BoardsListWidget } from './boards/boards-list-widget';
|
||||
import { LibraryListWidget } from './library/library-list-widget';
|
||||
|
||||
@injectable()
|
||||
export class EditorMode implements FrontendApplicationContribution {
|
||||
@@ -16,41 +12,6 @@ export class EditorMode implements FrontendApplicationContribution {
|
||||
|
||||
onStart(app: FrontendApplication): void {
|
||||
this.app = app;
|
||||
if (this.proMode) {
|
||||
// We use this CSS class on the body to modify the visibility of the close button for the editors and views.
|
||||
document.body.classList.add(EditorMode.PRO_MODE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
get proMode(): boolean {
|
||||
const value = window.localStorage.getItem(EditorMode.PRO_MODE_KEY);
|
||||
return value === 'true';
|
||||
}
|
||||
|
||||
async toggleProMode(): Promise<void> {
|
||||
const oldState = this.proMode;
|
||||
const inAdvancedMode = !oldState;
|
||||
window.localStorage.setItem(EditorMode.PRO_MODE_KEY, String(inAdvancedMode));
|
||||
if (!inAdvancedMode) {
|
||||
const { shell } = this.app;
|
||||
// Close all widgets that are neither editor nor `Output` / `Boards Manager` / `Library Manager`.
|
||||
for (const area of ['left', 'right', 'bottom', 'main'] as Array<ApplicationShell.Area>) {
|
||||
shell.closeTabs(area, title => !this.isInSimpleMode(title.owner));
|
||||
}
|
||||
}
|
||||
// `storeLayout` has a sync API but the implementation is async, we store the layout manually before we reload the page.
|
||||
// See: https://github.com/eclipse-theia/theia/issues/6579
|
||||
// XXX: hack instead of injecting the `ArduinoShellLayoutRestorer` we have to retrieve it from the application to avoid DI cycle.
|
||||
const layoutRestorer = (this.app as any).layoutRestorer as { storeLayoutAsync(app: FrontendApplication): Promise<void> };
|
||||
await layoutRestorer.storeLayoutAsync(this.app);
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
protected isInSimpleMode(widget: Widget): boolean {
|
||||
return widget instanceof EditorWidget
|
||||
|| widget instanceof OutputWidget
|
||||
|| widget instanceof BoardsListWidget
|
||||
|| widget instanceof LibraryListWidget;
|
||||
}
|
||||
|
||||
get compileForDebug(): boolean {
|
||||
@@ -68,6 +29,5 @@ export class EditorMode implements FrontendApplicationContribution {
|
||||
}
|
||||
|
||||
export namespace EditorMode {
|
||||
export const PRO_MODE_KEY = 'arduino-advanced-mode';
|
||||
export const COMPILE_FOR_DEBUG_KEY = 'arduino-compile-for-debug';
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { MAIN_MENU_BAR } from '@theia/core/lib/common/menu';
|
||||
import { MAIN_MENU_BAR, MenuModelRegistry, MenuNode, MenuPath, SubMenuOptions } from '@theia/core/lib/common/menu';
|
||||
|
||||
export namespace ArduinoMenus {
|
||||
|
||||
@@ -12,8 +12,14 @@ export namespace ArduinoMenus {
|
||||
export const FILE__SETTINGS_GROUP = [...(isOSX ? MAIN_MENU_BAR : CommonMenus.FILE), '2_settings'];
|
||||
export const FILE__QUIT_GROUP = [...CommonMenus.FILE, '3_quit'];
|
||||
|
||||
// -- File / Open Recent
|
||||
export const FILE__OPEN_RECENT_SUBMENU = [...FILE__SKETCH_GROUP, '0_open_recent'];
|
||||
|
||||
// -- File / Sketchbook
|
||||
export const FILE__SKETCHBOOK_SUBMENU = [...FILE__SKETCH_GROUP, '1_sketchbook'];
|
||||
|
||||
// -- File / Examples
|
||||
export const FILE__EXAMPLES_SUBMENU = [...FILE__SKETCH_GROUP, '0_examples'];
|
||||
export const FILE__EXAMPLES_SUBMENU = [...FILE__SKETCH_GROUP, '2_examples'];
|
||||
export const EXAMPLES__BUILT_IN_GROUP = [...FILE__EXAMPLES_SUBMENU, '0_built_ins'];
|
||||
export const EXAMPLES__ANY_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '1_any_board'];
|
||||
export const EXAMPLES__CURRENT_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '2_current_board'];
|
||||
@@ -37,10 +43,19 @@ export namespace ArduinoMenus {
|
||||
export const TOOLS = [...MAIN_MENU_BAR, '4_tools'];
|
||||
// `Auto Format`, `Library Manager...`, `Boards Manager...`
|
||||
export const TOOLS__MAIN_GROUP = [...TOOLS, '0_main'];
|
||||
// `Board`, `Port`, and `Get Board Info`.
|
||||
export const TOOLS__BOARD_SELECTION_GROUP = [...TOOLS, '2_board_selection'];
|
||||
// Core settings, such as `Processor` and `Programmers` for the board and `Burn Bootloader`
|
||||
export const TOOLS__BOARD_SETTINGS_GROUP = [...TOOLS, '1_board_settings'];
|
||||
export const TOOLS__BOARD_SETTINGS_GROUP = [...TOOLS, '3_board_settings'];
|
||||
|
||||
// -- Help
|
||||
// `Getting Started`, `Environment`, `Troubleshooting`, etc.
|
||||
export const HELP__MAIN_GROUP = [...CommonMenus.HELP, '0_main'];
|
||||
// `Find in reference`, `FAQ`, etc.
|
||||
export const HELP__FIND_GROUP = [...CommonMenus.HELP, '1_find'];
|
||||
// `Advanced Mode`.
|
||||
// XXX: this will be removed.
|
||||
export const HELP__CONTROL_GROUP = [...CommonMenus.HELP, '2_control'];
|
||||
// `About` group
|
||||
// XXX: on macOS, the about group is not under `Help`
|
||||
export const HELP__ABOUT_GROUP = [...(isOSX ? MAIN_MENU_BAR : CommonMenus.HELP), '999_about'];
|
||||
@@ -64,3 +79,44 @@ export namespace ArduinoMenus {
|
||||
export const SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP = [...SKETCH_CONTROL__CONTEXT, '2_resources'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a hack. It removes a submenu with all its children if any.
|
||||
* Theia cannot dispose submenu entries with a proper API: https://github.com/eclipse-theia/theia/issues/7299
|
||||
*/
|
||||
export function unregisterSubmenu(menuPath: string[], menuRegistry: MenuModelRegistry): void {
|
||||
if (menuPath.length < 2) {
|
||||
throw new Error(`Expected at least two item as a menu-path. Got ${JSON.stringify(menuPath)} instead.`);
|
||||
}
|
||||
const toRemove = menuPath[menuPath.length - 1];
|
||||
const parentMenuPath = menuPath.slice(0, menuPath.length - 1);
|
||||
// This is unsafe. Calling `getMenu` with a non-existing menu-path will result in a new menu creation.
|
||||
// https://github.com/eclipse-theia/theia/issues/7300
|
||||
const parent = menuRegistry.getMenu(parentMenuPath);
|
||||
const index = parent.children.findIndex(({ id }) => id === toRemove);
|
||||
if (index === -1) {
|
||||
throw new Error(`Could not find menu with menu-path: ${JSON.stringify(menuPath)}.`);
|
||||
}
|
||||
(parent.children as Array<MenuNode>).splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Special menu node that is not backed by any commands and is always disabled.
|
||||
*/
|
||||
export class PlaceholderMenuNode implements MenuNode {
|
||||
|
||||
constructor(protected readonly menuPath: MenuPath, readonly label: string, protected options: SubMenuOptions = { order: '0' }) { }
|
||||
|
||||
get icon(): string | undefined {
|
||||
return this.options?.iconClass;
|
||||
}
|
||||
|
||||
get sortString(): string {
|
||||
return this.options?.order || this.label;
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return [...this.menuPath, 'placeholder'].join('-');
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { NotificationServiceClient, NotificationServiceServer } from '../common/protocol/notification-service';
|
||||
import { AttachedBoardsChangeEvent, BoardsPackage, LibraryPackage, Config } from '../common/protocol';
|
||||
import { AttachedBoardsChangeEvent, BoardsPackage, LibraryPackage, Config, Sketch } from '../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class NotificationCenter implements NotificationServiceClient, FrontendApplicationContribution {
|
||||
@@ -21,6 +21,7 @@ export class NotificationCenter implements NotificationServiceClient, FrontendAp
|
||||
protected readonly libraryInstalledEmitter = new Emitter<{ item: LibraryPackage }>();
|
||||
protected readonly libraryUninstalledEmitter = new Emitter<{ item: LibraryPackage }>();
|
||||
protected readonly attachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
|
||||
protected readonly recentSketchesChangedEmitter = new Emitter<{ sketches: Sketch[] }>();
|
||||
|
||||
protected readonly toDispose = new DisposableCollection(
|
||||
this.indexUpdatedEmitter,
|
||||
@@ -43,6 +44,7 @@ export class NotificationCenter implements NotificationServiceClient, FrontendAp
|
||||
readonly onLibraryInstalled = this.libraryInstalledEmitter.event;
|
||||
readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event;
|
||||
readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event;
|
||||
readonly onRecentSketchesChanged = this.recentSketchesChangedEmitter.event;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
@@ -89,4 +91,8 @@ export class NotificationCenter implements NotificationServiceClient, FrontendAp
|
||||
this.attachedBoardsChangedEmitter.fire(event);
|
||||
}
|
||||
|
||||
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void {
|
||||
this.recentSketchesChangedEmitter.fire(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -13,16 +13,10 @@ export class OutputServiceImpl implements OutputService {
|
||||
protected outputChannelManager: OutputChannelManager;
|
||||
|
||||
append(message: OutputMessage): void {
|
||||
const { name, chunk } = message;
|
||||
const channel = this.outputChannelManager.getChannel(`Arduino: ${name}`);
|
||||
// Zen-mode: we do not reveal the output for daemon messages.
|
||||
const show: Promise<any> = name === 'daemon'
|
||||
// This will open and reveal the view but won't show it. You will see the toggle bottom panel on the status bar.
|
||||
? this.outputContribution.openView({ activate: false, reveal: false })
|
||||
// This will open, reveal but do not activate the Output view.
|
||||
: Promise.resolve(channel.show({ preserveFocus: true }));
|
||||
|
||||
show.then(() => channel.append(chunk));
|
||||
const { chunk } = message;
|
||||
const channel = this.outputChannelManager.getChannel(`Arduino`);
|
||||
channel.show({ preserveFocus: true });
|
||||
channel.append(chunk);
|
||||
}
|
||||
|
||||
}
|
||||
|
791
arduino-ide-extension/src/browser/settings.tsx
Normal file
791
arduino-ide-extension/src/browser/settings.tsx
Normal file
@@ -0,0 +1,791 @@
|
||||
import * as React from 'react';
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import { Widget } from '@phosphor/widgets';
|
||||
import { Message } from '@phosphor/messaging';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
import 'react-tabs/style/react-tabs.css';
|
||||
import { Disable } from 'react-disable';
|
||||
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 { deepClone } from '@theia/core/lib/common/objects';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog/file-dialog-service';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { AbstractDialog, DialogProps, PreferenceService, PreferenceScope, DialogError, ReactWidget } from '@theia/core/lib/browser';
|
||||
import { Index } from '../common/types';
|
||||
import { ConfigService, FileSystemExt, Network, ProxySettings } from '../common/protocol';
|
||||
|
||||
export interface Settings extends Index {
|
||||
editorFontSize: number; // `editor.fontSize`
|
||||
themeId: string; // `workbench.colorTheme`
|
||||
autoSave: 'on' | 'off'; // `editor.autoSave`
|
||||
|
||||
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`
|
||||
verboseOnUpload: boolean; // `arduino.upload.verbose`
|
||||
verifyAfterUpload: boolean; // `arduino.upload.verify`
|
||||
enableLsLogs: boolean; // `arduino.language.log`
|
||||
|
||||
sketchbookPath: string; // CLI
|
||||
additionalUrls: string[]; // CLI
|
||||
network: Network; // CLI
|
||||
}
|
||||
export namespace Settings {
|
||||
|
||||
export function belongsToCli<K extends keyof Settings>(key: K): boolean {
|
||||
return key === 'sketchbookPath' || key === 'additionalUrls';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SettingsService {
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@inject(FileSystemExt)
|
||||
protected readonly fileSystemExt: FileSystemExt;
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(PreferenceService)
|
||||
protected readonly preferenceService: PreferenceService;
|
||||
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
protected readonly onDidChangeEmitter = new Emitter<Readonly<Settings>>();
|
||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
|
||||
protected ready = new Deferred<void>();
|
||||
protected _settings: Settings;
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
protected async loadSettings(): Promise<Settings> {
|
||||
await this.preferenceService.ready;
|
||||
const [
|
||||
editorFontSize,
|
||||
themeId,
|
||||
autoSave,
|
||||
autoScaleInterface,
|
||||
interfaceScale,
|
||||
// checkForUpdates,
|
||||
verboseOnCompile,
|
||||
verboseOnUpload,
|
||||
verifyAfterUpload,
|
||||
enableLsLogs,
|
||||
cliConfig
|
||||
] = await Promise.all([
|
||||
this.preferenceService.get<number>('editor.fontSize', 12),
|
||||
this.preferenceService.get<string>('workbench.colorTheme', 'arduino-theme'),
|
||||
this.preferenceService.get<'on' | 'off'>('editor.autoSave', 'on'),
|
||||
this.preferenceService.get<boolean>('arduino.window.autoScale', true),
|
||||
this.preferenceService.get<number>('arduino.window.zoomLevel', 0),
|
||||
// this.preferenceService.get<string>('arduino.ide.autoUpdate', true),
|
||||
this.preferenceService.get<boolean>('arduino.compile.verbose', true),
|
||||
this.preferenceService.get<boolean>('arduino.upload.verbose', true),
|
||||
this.preferenceService.get<boolean>('arduino.upload.verify', true),
|
||||
this.preferenceService.get<boolean>('arduino.language.log', true),
|
||||
this.configService.getConfiguration()
|
||||
]);
|
||||
const { additionalUrls, sketchDirUri, network } = cliConfig;
|
||||
const sketchbookPath = await this.fileService.fsPath(new URI(sketchDirUri));
|
||||
return {
|
||||
editorFontSize,
|
||||
themeId,
|
||||
autoSave,
|
||||
autoScaleInterface,
|
||||
interfaceScale,
|
||||
// checkForUpdates,
|
||||
verboseOnCompile,
|
||||
verboseOnUpload,
|
||||
verifyAfterUpload,
|
||||
enableLsLogs,
|
||||
additionalUrls,
|
||||
sketchbookPath,
|
||||
network
|
||||
};
|
||||
}
|
||||
|
||||
async settings(): Promise<Settings> {
|
||||
await this.ready.promise;
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
async update(settings: Settings, fireDidChange: boolean = false): Promise<void> {
|
||||
await this.ready.promise;
|
||||
for (const key of Object.keys(settings)) {
|
||||
this._settings[key] = settings[key];
|
||||
}
|
||||
if (fireDidChange) {
|
||||
this.onDidChangeEmitter.fire(this._settings);
|
||||
}
|
||||
}
|
||||
|
||||
async reset(): Promise<void> {
|
||||
const settings = await this.loadSettings();
|
||||
return this.update(settings, true);
|
||||
}
|
||||
|
||||
async validate(settings: MaybePromise<Settings> = this.settings()): Promise<string | true> {
|
||||
try {
|
||||
const { sketchbookPath, editorFontSize, themeId } = await settings;
|
||||
const sketchbookDir = await this.fileSystemExt.getUri(sketchbookPath);
|
||||
if (!await this.fileService.exists(new URI(sketchbookDir))) {
|
||||
return `Invalid sketchbook location: ${sketchbookPath}`;
|
||||
}
|
||||
if (editorFontSize <= 0) {
|
||||
return `Invalid editor font size. It must be a positive integer.`;
|
||||
}
|
||||
if (!ThemeService.get().getThemes().find(({ id }) => id === themeId)) {
|
||||
return `Invalid theme.`;
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return err.message;
|
||||
}
|
||||
return String(err);
|
||||
}
|
||||
}
|
||||
|
||||
async save(): Promise<string | true> {
|
||||
await this.ready.promise;
|
||||
const {
|
||||
editorFontSize,
|
||||
themeId,
|
||||
autoSave,
|
||||
autoScaleInterface,
|
||||
interfaceScale,
|
||||
// checkForUpdates,
|
||||
verboseOnCompile,
|
||||
verboseOnUpload,
|
||||
verifyAfterUpload,
|
||||
enableLsLogs,
|
||||
sketchbookPath,
|
||||
additionalUrls,
|
||||
network
|
||||
} = this._settings;
|
||||
const [config, sketchDirUri] = await Promise.all([
|
||||
this.configService.getConfiguration(),
|
||||
this.fileSystemExt.getUri(sketchbookPath)
|
||||
]);
|
||||
(config as any).additionalUrls = additionalUrls;
|
||||
(config as any).sketchDirUri = sketchDirUri;
|
||||
(config as any).network = network;
|
||||
|
||||
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('arduino.window.autoScale', autoScaleInterface, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.window.zoomLevel', interfaceScale, PreferenceScope.User),
|
||||
// this.preferenceService.set('arduino.ide.autoUpdate', checkForUpdates, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.compile.verbose', verboseOnCompile, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.upload.verbose', verboseOnUpload, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.upload.verify', verifyAfterUpload, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.language.log', enableLsLogs, PreferenceScope.User),
|
||||
this.configService.setConfiguration(config)
|
||||
]);
|
||||
this.onDidChangeEmitter.fire(this._settings);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class SettingsComponent extends React.Component<SettingsComponent.Props, SettingsComponent.State> {
|
||||
|
||||
readonly toDispose = new DisposableCollection();
|
||||
|
||||
constructor(props: SettingsComponent.Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidUpdate(_: SettingsComponent.Props, prevState: SettingsComponent.State): void {
|
||||
if (this.state && prevState && JSON.stringify(this.state) !== JSON.stringify(prevState)) {
|
||||
this.props.settingsService.update(this.state, true);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.props.settingsService.settings().then(settings => this.setState(settings));
|
||||
this.toDispose.push(this.props.settingsService.onDidChange(settings => this.setState(settings)));
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
if (!this.state) {
|
||||
return <div />;
|
||||
}
|
||||
return <Tabs>
|
||||
<TabList>
|
||||
<Tab>Settings</Tab>
|
||||
<Tab>Network</Tab>
|
||||
</TabList>
|
||||
<TabPanel>
|
||||
{this.renderSettings()}
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
{this.renderNetwork()}
|
||||
</TabPanel>
|
||||
</Tabs>;
|
||||
}
|
||||
|
||||
protected renderSettings(): React.ReactNode {
|
||||
return <div className='content noselect'>
|
||||
Sketchbook location:
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input stretch'
|
||||
type='text'
|
||||
value={this.state.sketchbookPath}
|
||||
onChange={this.sketchpathDidChange} />
|
||||
<button className='theia-button shrink' onClick={this.browseSketchbookDidClick}>Browse</button>
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<div className='column'>
|
||||
<div className='flex-line'>Editor font size:</div>
|
||||
<div className='flex-line'>Interface scale:</div>
|
||||
<div className='flex-line'>Theme:</div>
|
||||
<div className='flex-line'>Show verbose output during:</div>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input small'
|
||||
type='number'
|
||||
step={1}
|
||||
pattern='[0-9]+'
|
||||
onKeyDown={this.numbersOnlyKeyDown}
|
||||
value={this.state.editorFontSize}
|
||||
onChange={this.editorFontSizeDidChange} />
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.autoScaleInterface}
|
||||
onChange={this.autoScaleInterfaceDidChange} />
|
||||
Automatic
|
||||
</label>
|
||||
<input
|
||||
className='theia-input small with-margin'
|
||||
type='number'
|
||||
step={20}
|
||||
pattern='[0-9]+'
|
||||
onKeyDown={this.noopKeyDown}
|
||||
value={100 + this.state.interfaceScale * 20}
|
||||
onChange={this.interfaceScaleDidChange} />
|
||||
%
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<select
|
||||
className='theia-select'
|
||||
value={ThemeService.get().getThemes().find(({ id }) => id === this.state.themeId)?.label || 'Unknown'}
|
||||
onChange={this.themeDidChange}>
|
||||
{ThemeService.get().getThemes().map(({ id, label }) => <option key={id} value={label}>{label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.verboseOnCompile}
|
||||
onChange={this.verboseOnCompileDidChange} />
|
||||
compile
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.verboseOnUpload}
|
||||
onChange={this.verboseOnUploadDidChange} />
|
||||
upload
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.verifyAfterUpload}
|
||||
onChange={this.verifyAfterUploadDidChange} />
|
||||
Verify code after upload
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.checkForUpdates}
|
||||
onChange={this.checkForUpdatesDidChange}
|
||||
disabled={true} />
|
||||
Check for updates on startup
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.autoSave === 'on'}
|
||||
onChange={this.autoSaveDidChange} />
|
||||
Auto save
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.enableLsLogs}
|
||||
onChange={this.enableLsLogsDidChange} />
|
||||
Enable language server logging
|
||||
</label>
|
||||
<div className='flex-line'>
|
||||
Additional boards manager URLs:
|
||||
<input
|
||||
className='theia-input stretch with-margin'
|
||||
type='text'
|
||||
value={this.state.additionalUrls.join(',')}
|
||||
onChange={this.additionalUrlsDidChange} />
|
||||
<i className='fa fa-window-restore theia-button shrink' onClick={this.editAdditionalUrlDidClick} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
protected renderNetwork(): React.ReactNode {
|
||||
return <div className='content noselect'>
|
||||
<form>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='radio'
|
||||
checked={this.state.network === 'none'}
|
||||
onChange={this.noProxyDidChange} />
|
||||
No proxy
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='radio'
|
||||
checked={this.state.network !== 'none'}
|
||||
onChange={this.manualProxyDidChange} />
|
||||
Manual proxy configuration
|
||||
</label>
|
||||
</form>
|
||||
{this.renderProxySettings()}
|
||||
</div>;
|
||||
}
|
||||
|
||||
protected renderProxySettings(): React.ReactNode {
|
||||
const disabled = this.state.network === 'none';
|
||||
return <Disable disabled={disabled}>
|
||||
<div className='proxy-settings' aria-disabled={disabled}>
|
||||
<form className='flex-line'>
|
||||
<input
|
||||
type='radio'
|
||||
checked={this.state.network === 'none' ? true : this.state.network.protocol === 'http'}
|
||||
onChange={this.httpProtocolDidChange} />
|
||||
HTTP
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='radio'
|
||||
checked={this.state.network === 'none' ? false : this.state.network.protocol !== 'http'}
|
||||
onChange={this.socksProtocolDidChange} />
|
||||
SOCKS
|
||||
</label>
|
||||
</form>
|
||||
<div className='flex-line proxy-settings'>
|
||||
<div className='column'>
|
||||
<div className='flex-line'>Host name:</div>
|
||||
<div className='flex-line'>Port number:</div>
|
||||
<div className='flex-line'>Username:</div>
|
||||
<div className='flex-line'>Password:</div>
|
||||
</div>
|
||||
<div className='column stretch'>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input stretch with-margin'
|
||||
type='text'
|
||||
value={this.state.network === 'none' ? '' : this.state.network.hostname}
|
||||
onChange={this.hostnameDidChange} />
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input small with-margin'
|
||||
type='number'
|
||||
pattern='[0-9]'
|
||||
value={this.state.network === 'none' ? '' : this.state.network.port}
|
||||
onKeyDown={this.numbersOnlyKeyDown}
|
||||
onChange={this.portDidChange} />
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input stretch with-margin'
|
||||
type='text'
|
||||
value={this.state.network === 'none' ? '' : this.state.network.username}
|
||||
onChange={this.usernameDidChange} />
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input stretch with-margin'
|
||||
type='password'
|
||||
value={this.state.network === 'none' ? '' : this.state.network.password}
|
||||
onChange={this.passwordDidChange} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Disable>;
|
||||
}
|
||||
|
||||
private isControlKey(event: React.KeyboardEvent<HTMLInputElement>): boolean {
|
||||
return !!event.key && ['tab', 'delete', 'backspace', 'arrowleft', 'arrowright'].some(key => event.key.toLocaleLowerCase() === key);
|
||||
}
|
||||
|
||||
protected noopKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (this.isControlKey(event)) {
|
||||
return;
|
||||
}
|
||||
event.nativeEvent.preventDefault();
|
||||
event.nativeEvent.returnValue = false;
|
||||
}
|
||||
|
||||
protected numbersOnlyKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (this.isControlKey(event)) {
|
||||
return;
|
||||
}
|
||||
const key = Number(event.key)
|
||||
if (isNaN(key) || event.key === null || event.key === ' ') {
|
||||
event.nativeEvent.preventDefault();
|
||||
event.nativeEvent.returnValue = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected browseSketchbookDidClick = async () => {
|
||||
const uri = await this.props.fileDialogService.showOpenDialog({
|
||||
title: 'Select new sketchbook location',
|
||||
openLabel: 'Chose',
|
||||
canSelectFiles: false,
|
||||
canSelectMany: false,
|
||||
canSelectFolders: true
|
||||
});
|
||||
if (uri) {
|
||||
const sketchbookPath = await this.props.fileService.fsPath(uri);
|
||||
this.setState({ sketchbookPath });
|
||||
}
|
||||
};
|
||||
|
||||
protected editAdditionalUrlDidClick = async () => {
|
||||
const additionalUrls = await new AdditionalUrlsDialog(this.state.additionalUrls, this.props.windowService).open();
|
||||
if (additionalUrls) {
|
||||
this.setState({ additionalUrls });
|
||||
}
|
||||
};
|
||||
|
||||
protected editorFontSizeDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
if (value) {
|
||||
this.setState({ editorFontSize: parseInt(value, 10) });
|
||||
}
|
||||
};
|
||||
|
||||
protected additionalUrlsDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ additionalUrls: event.target.value.split(',').map(url => url.trim()) });
|
||||
};
|
||||
|
||||
protected autoScaleInterfaceDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ autoScaleInterface: event.target.checked });
|
||||
};
|
||||
|
||||
protected enableLsLogsDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ enableLsLogs: event.target.checked });
|
||||
};
|
||||
|
||||
protected interfaceScaleDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
const percentage = parseInt(value, 10);
|
||||
if (isNaN(percentage)) {
|
||||
return;
|
||||
}
|
||||
let interfaceScale = (percentage - 100) / 20;
|
||||
if (!isNaN(interfaceScale)) {
|
||||
this.setState({ interfaceScale });
|
||||
}
|
||||
};
|
||||
|
||||
protected verifyAfterUploadDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ verifyAfterUpload: event.target.checked });
|
||||
};
|
||||
|
||||
protected checkForUpdatesDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ checkForUpdates: event.target.checked });
|
||||
};
|
||||
|
||||
protected autoSaveDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ autoSave: event.target.checked ? 'on' : 'off' });
|
||||
};
|
||||
|
||||
protected themeDidChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const { selectedIndex } = event.target.options;
|
||||
const theme = ThemeService.get().getThemes()[selectedIndex];
|
||||
if (theme) {
|
||||
this.setState({ themeId: theme.id });
|
||||
}
|
||||
};
|
||||
|
||||
protected verboseOnCompileDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ verboseOnCompile: event.target.checked });
|
||||
};
|
||||
|
||||
protected verboseOnUploadDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ verboseOnUpload: event.target.checked });
|
||||
};
|
||||
|
||||
protected sketchpathDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const sketchbookPath = event.target.value;
|
||||
if (sketchbookPath) {
|
||||
this.setState({ sketchbookPath });
|
||||
}
|
||||
};
|
||||
|
||||
protected noProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
this.setState({ network: 'none' });
|
||||
} else {
|
||||
this.setState({ network: Network.Default() });
|
||||
}
|
||||
};
|
||||
|
||||
protected manualProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
this.setState({ network: Network.Default() });
|
||||
} else {
|
||||
this.setState({ network: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
protected httpProtocolDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.protocol = event.target.checked ? 'http' : 'socks';
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected socksProtocolDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.protocol = event.target.checked ? 'socks' : 'http';
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected hostnameDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.hostname = event.target.value;
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected portDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.port = event.target.value;
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected usernameDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.username = event.target.value;
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
protected passwordDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.state.network !== 'none') {
|
||||
const network = this.cloneProxySettings;
|
||||
network.password = event.target.value;
|
||||
this.setState({ network });
|
||||
}
|
||||
};
|
||||
|
||||
private get cloneProxySettings(): ProxySettings {
|
||||
const { network } = this.state;
|
||||
if (network === 'none') {
|
||||
throw new Error('Must be called when proxy is enabled.');
|
||||
}
|
||||
const copyNetwork = deepClone(network);
|
||||
return copyNetwork;
|
||||
}
|
||||
|
||||
}
|
||||
export namespace SettingsComponent {
|
||||
export interface Props {
|
||||
readonly settingsService: SettingsService;
|
||||
readonly fileService: FileService;
|
||||
readonly fileDialogService: FileDialogService;
|
||||
readonly windowService: WindowService;
|
||||
}
|
||||
export interface State extends Settings { }
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SettingsWidget extends ReactWidget {
|
||||
|
||||
@inject(SettingsService)
|
||||
protected readonly settingsService: SettingsService;
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@inject(FileDialogService)
|
||||
protected readonly fileDialogService: FileDialogService;
|
||||
|
||||
@inject(WindowService)
|
||||
protected readonly windowService: WindowService;
|
||||
|
||||
protected render(): React.ReactNode {
|
||||
return <SettingsComponent
|
||||
settingsService={this.settingsService}
|
||||
fileService={this.fileService}
|
||||
fileDialogService={this.fileDialogService}
|
||||
windowService={this.windowService} />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SettingsDialogProps extends DialogProps {
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
|
||||
|
||||
@inject(SettingsService)
|
||||
protected readonly settingsService: SettingsService;
|
||||
|
||||
@inject(SettingsWidget)
|
||||
protected readonly widget: SettingsWidget;
|
||||
|
||||
constructor(@inject(SettingsDialogProps) protected readonly props: SettingsDialogProps) {
|
||||
super(props);
|
||||
this.contentNode.classList.add('arduino-settings-dialog');
|
||||
this.appendCloseButton('CANCEL');
|
||||
this.appendAcceptButton('OK');
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.toDispose.push(this.settingsService.onDidChange(this.validate.bind(this)));
|
||||
}
|
||||
|
||||
protected async isValid(settings: Promise<Settings>): Promise<DialogError> {
|
||||
const result = await this.settingsService.validate(settings);
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
get value(): Promise<Settings> {
|
||||
return this.settingsService.settings();
|
||||
}
|
||||
|
||||
protected onAfterAttach(msg: Message): void {
|
||||
if (this.widget.isAttached) {
|
||||
Widget.detach(this.widget);
|
||||
}
|
||||
Widget.attach(this.widget, this.contentNode);
|
||||
this.toDisposeOnDetach.push(this.settingsService.onDidChange(() => this.update()));
|
||||
super.onAfterAttach(msg);
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected onUpdateRequest(msg: Message) {
|
||||
super.onUpdateRequest(msg);
|
||||
this.widget.update();
|
||||
}
|
||||
|
||||
protected onActivateRequest(msg: Message): void {
|
||||
super.onActivateRequest(msg);
|
||||
this.widget.activate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
|
||||
|
||||
protected readonly textArea: HTMLTextAreaElement;
|
||||
|
||||
constructor(urls: string[], windowService: WindowService) {
|
||||
super({ title: 'Additional Boards Manager URLs' });
|
||||
|
||||
this.contentNode.classList.add('additional-urls-dialog');
|
||||
|
||||
const description = document.createElement('div');
|
||||
description.textContent = 'Enter additional URLs, one for each row';
|
||||
description.style.marginBottom = '5px';
|
||||
this.contentNode.appendChild(description);
|
||||
|
||||
this.textArea = document.createElement('textarea');
|
||||
this.textArea.className = 'theia-input';
|
||||
this.textArea.setAttribute('style', 'flex: 0;');
|
||||
this.textArea.value = urls.filter(url => url.trim()).filter(url => !!url).join('\n');
|
||||
this.textArea.wrap = 'soft';
|
||||
this.textArea.cols = 90;
|
||||
this.textArea.rows = 5;
|
||||
this.contentNode.appendChild(this.textArea);
|
||||
|
||||
const anchor = document.createElement('div');
|
||||
anchor.classList.add('link');
|
||||
anchor.textContent = 'Click for a list of unofficial board support URLs';
|
||||
anchor.style.marginTop = '5px';
|
||||
anchor.style.cursor = 'pointer';
|
||||
this.addEventListener(
|
||||
anchor,
|
||||
'click',
|
||||
() => windowService.openNewWindow('https://github.com/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls', { external: true })
|
||||
);
|
||||
this.contentNode.appendChild(anchor);
|
||||
|
||||
this.appendAcceptButton('OK');
|
||||
this.appendCloseButton('Cancel');
|
||||
}
|
||||
|
||||
get value(): string[] {
|
||||
return this.textArea.value.split('\n').map(url => url.trim());
|
||||
}
|
||||
|
||||
protected onAfterAttach(message: Message): void {
|
||||
super.onAfterAttach(message);
|
||||
this.addUpdateListener(this.textArea, 'input');
|
||||
}
|
||||
|
||||
protected onActivateRequest(message: Message): void {
|
||||
super.onActivateRequest(message);
|
||||
this.textArea.focus();
|
||||
}
|
||||
|
||||
protected handleEnter(event: KeyboardEvent): boolean | void {
|
||||
if (event.target instanceof HTMLInputElement) {
|
||||
return super.handleEnter(event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@@ -135,18 +135,6 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
||||
width: 740px;
|
||||
}
|
||||
|
||||
button.theia-button {
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
button.theia-button.secondary {
|
||||
background-color: var(--theia-secondaryButton-background);
|
||||
color: var(--theia-foreground);
|
||||
}
|
||||
|
||||
button.theia-button.main {
|
||||
color: var(--theia-button-foreground);
|
||||
}
|
||||
|
||||
.dialogControl {
|
||||
margin: 0 20px 30px 0;
|
||||
@@ -203,12 +191,14 @@ button.theia-button.main {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0px 3px 0px 3px;
|
||||
border: 1px solid var(--theia-dropdown-border);
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-list {
|
||||
border: 3px solid var(--theia-activityBar-background);
|
||||
margin: -3px;
|
||||
margin: -1px;
|
||||
z-index: 1;
|
||||
border: 1px solid var(--theia-dropdown-border);
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item {
|
||||
@@ -218,6 +208,7 @@ button.theia-button.main {
|
||||
cursor: pointer;
|
||||
color: var(--theia-foreground);
|
||||
background: var(--theia-tab-unfocusedActiveBackground);
|
||||
border: 1px solid var(--theia-tab-unfocusedActiveBackground);
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item .fa-check {
|
||||
@@ -227,5 +218,5 @@ button.theia-button.main {
|
||||
|
||||
.arduino-boards-dropdown-item.selected,
|
||||
.arduino-boards-dropdown-item:hover {
|
||||
background: var(--theia-list-hoverBackground);
|
||||
border: 1px solid var(--theia-focusBorder);
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@
|
||||
@import './status-bar.css';
|
||||
@import './terminal.css';
|
||||
@import './editor.css';
|
||||
@import './settings-dialog.css';
|
||||
|
||||
.theia-input.warning:focus {
|
||||
outline-width: 1px;
|
||||
@@ -35,3 +36,28 @@
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
}
|
||||
|
||||
/* Overrule the default Theia CSS button styles. */
|
||||
|
||||
button.theia-button,
|
||||
.theia-button {
|
||||
border: 1px solid var(--theia-dropdown-border);
|
||||
}
|
||||
|
||||
button.theia-button:hover,
|
||||
.theia-button:hover {
|
||||
border: 1px solid var(--theia-focusBorder);
|
||||
}
|
||||
|
||||
button.theia-button {
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
button.theia-button.secondary {
|
||||
background-color: var(--theia-secondaryButton-background);
|
||||
color: var(--theia-foreground);
|
||||
}
|
||||
|
||||
button.theia-button.main {
|
||||
color: var(--theia-button-foreground);
|
||||
}
|
||||
|
57
arduino-ide-extension/src/browser/style/settings-dialog.css
Normal file
57
arduino-ide-extension/src/browser/style/settings-dialog.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.arduino-settings-dialog {
|
||||
width: 740px;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .content {
|
||||
padding: 5px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .flex-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .with-margin {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .theia-select {
|
||||
background: var(--theia-input-background) !important;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .column > div {
|
||||
height: 26px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .stretch {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .flex-line .theia-button.shrink {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .proxy-settings {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog input[type="radio"] {
|
||||
margin: 3px !important;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .theia-input.small {
|
||||
max-width: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.additional-urls-dialog .link:hover {
|
||||
color: var(--theia-textLink-activeForeground);
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .react-tabs__tab-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
@@ -7,16 +7,12 @@ import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import { ConnectionStatusService, ConnectionStatus } from '@theia/core/lib/browser/connection-status-service';
|
||||
import { ApplicationShell as TheiaApplicationShell, Widget } from '@theia/core/lib/browser';
|
||||
import { Sketch } from '../../../common/protocol';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
import { SaveAsSketch } from '../../contributions/save-as-sketch';
|
||||
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
||||
|
||||
@injectable()
|
||||
export class ApplicationShell extends TheiaApplicationShell {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@@ -34,7 +30,7 @@ export class ApplicationShell extends TheiaApplicationShell {
|
||||
if (widget instanceof OutputWidget) {
|
||||
widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
|
||||
}
|
||||
if (!this.editorMode.proMode && widget instanceof EditorWidget) {
|
||||
if (widget instanceof EditorWidget) {
|
||||
// Make the editor un-closeable asynchronously.
|
||||
this.sketchesServiceClient.currentSketch().then(sketch => {
|
||||
if (sketch) {
|
||||
|
@@ -20,7 +20,8 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
|
||||
CommonCommands.OPEN_PREFERENCES,
|
||||
CommonCommands.SELECT_ICON_THEME,
|
||||
CommonCommands.SELECT_COLOR_THEME,
|
||||
CommonCommands.ABOUT_COMMAND
|
||||
CommonCommands.ABOUT_COMMAND,
|
||||
CommonCommands.SAVE_WITHOUT_FORMATTING // Patched for https://github.com/eclipse-theia/theia/pull/8877
|
||||
]) {
|
||||
registry.unregisterMenuAction(command);
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { SketchesService } from '../../../common/protocol';
|
||||
import { ArduinoCommands } from '../../arduino-commands';
|
||||
|
||||
@injectable()
|
||||
@@ -17,12 +18,16 @@ export class FrontendApplication extends TheiaFrontendApplication {
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
|
||||
protected async initializeLayout(): Promise<void> {
|
||||
await super.initializeLayout();
|
||||
const roots = await this.workspaceService.roots;
|
||||
for (const root of roots) {
|
||||
const exists = await this.fileService.exists(root.resource);
|
||||
if (exists) {
|
||||
this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu
|
||||
await this.commandService.executeCommand(ArduinoCommands.OPEN_SKETCH_FILES.id, root.resource);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,14 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution, DebugMenus } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||
import { unregisterSubmenu } from '../../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class DebugFrontendApplicationContribution extends TheiaDebugFrontendApplicationContribution {
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
unregisterSubmenu(DebugMenus.DEBUG, registry);
|
||||
}
|
||||
|
||||
}
|
@@ -24,13 +24,7 @@ export class EditorManager extends TheiaEditorManager {
|
||||
}
|
||||
|
||||
protected async isReadOnly(uri: URI): Promise<boolean> {
|
||||
const [config, configFileUri] = await Promise.all([
|
||||
this.configService.getConfiguration(),
|
||||
this.configService.getCliConfigFileUri()
|
||||
]);
|
||||
if (new URI(configFileUri).toString(true) === uri.toString(true)) {
|
||||
return false;
|
||||
}
|
||||
const config = await this.configService.getConfiguration();
|
||||
return new URI(config.dataDirUri).isEqualOrParent(uri)
|
||||
}
|
||||
|
||||
|
@@ -1,26 +1,18 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { injectable } from 'inversify';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser';
|
||||
import { ProblemStat } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ProblemContribution as TheiaProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ProblemContribution extends TheiaProblemContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
// NOOP
|
||||
}
|
||||
|
||||
protected setStatusBarElement(problemStat: ProblemStat): void {
|
||||
if (this.editorMode.proMode) {
|
||||
super.setStatusBarElement(problemStat);
|
||||
}
|
||||
// NOOP
|
||||
}
|
||||
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
|
@@ -1,20 +1,14 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { injectable } from 'inversify';
|
||||
import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FileNavigatorContribution as TheiaFileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class FileNavigatorContribution extends TheiaFileNavigatorContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
// NOOP
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
|
@@ -1,14 +1,10 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { injectable } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class OutlineViewContribution extends TheiaOutlineViewContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.options.defaultWidgetOptions = {
|
||||
@@ -18,9 +14,7 @@ export class OutlineViewContribution extends TheiaOutlineViewContribution {
|
||||
}
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,15 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { ReactTabBarToolbarItem, TabBarToolbarItem, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
|
||||
@injectable()
|
||||
export class OutputToolbarContribution extends TheiaOutputToolbarContribution {
|
||||
|
||||
async registerToolbarItems(registry: TabBarToolbarRegistry): Promise<void> {
|
||||
await super.registerToolbarItems(registry); // Why is it async?
|
||||
// It's a hack. Currently, it's not possible to unregister a toolbar contribution via API.
|
||||
((registry as any).items as Map<string, TabBarToolbarItem | ReactTabBarToolbarItem>).delete('channels');
|
||||
(registry as any).fireOnDidChange();
|
||||
}
|
||||
|
||||
}
|
@@ -15,11 +15,7 @@ export class PreferencesContribution extends TheiaPreferencesContribution {
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
// https://github.com/eclipse-theia/theia/issues/8202
|
||||
registry.registerKeybinding({
|
||||
command: CommonCommands.OPEN_PREFERENCES.id,
|
||||
keybinding: 'CtrlCmd+,',
|
||||
});
|
||||
registry.unregisterKeybinding(CommonCommands.OPEN_PREFERENCES.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,24 +1,16 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { injectable } from 'inversify';
|
||||
import { ScmContribution as TheiaScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ScmContribution extends TheiaScmContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout();
|
||||
}
|
||||
// NOOP
|
||||
}
|
||||
|
||||
protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
|
||||
if (this.editorMode.proMode) {
|
||||
super.setStatusBarEntry(id, entry);
|
||||
}
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,19 +1,14 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution, SearchInWorkspaceCommands } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
|
||||
@injectable()
|
||||
export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspaceFrontendContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
// NOOP
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
@@ -21,4 +16,9 @@ export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspac
|
||||
registry.unregisterMenuAction(SearchInWorkspaceCommands.OPEN_SIW_WIDGET);
|
||||
}
|
||||
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
super.registerKeybindings(keybindings);
|
||||
keybindings.unregisterKeybinding(SearchInWorkspaceCommands.OPEN_SIW_WIDGET);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,9 +7,8 @@ import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FocusTracker, Widget } from '@theia/core/lib/browser';
|
||||
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
|
||||
import { ConfigService } from '../../../common/protocol/config-service';
|
||||
import { SketchesService } from '../../../common/protocol/sketches-service';
|
||||
import { SketchesService, Sketch } from '../../../common/protocol/sketches-service';
|
||||
import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver';
|
||||
|
||||
@injectable()
|
||||
@@ -85,7 +84,7 @@ export class WorkspaceService extends TheiaWorkspaceService {
|
||||
protected onCurrentWidgetChange({ newValue }: FocusTracker.IChangedArgs<Widget>): void {
|
||||
if (newValue instanceof EditorWidget) {
|
||||
const { uri } = newValue.editor;
|
||||
if (uri.toString().endsWith('.ino')) {
|
||||
if (Sketch.isSketchFile(uri.toString())) {
|
||||
this.updateTitle();
|
||||
} else {
|
||||
const title = this.workspaceTitle;
|
||||
@@ -98,9 +97,8 @@ export class WorkspaceService extends TheiaWorkspaceService {
|
||||
}
|
||||
|
||||
protected formatTitle(title?: string): string {
|
||||
const status = FrontendApplicationConfigProvider.get()['status'];
|
||||
const version = this.version ? ` ${this.version}` : '';
|
||||
const name = `${this.applicationName}${status ? ` – ${status}` : ''} ${version}`;
|
||||
const name = `${this.applicationName} ${version}`;
|
||||
return title ? `${title} | ${name}` : name;
|
||||
}
|
||||
|
||||
|
@@ -6,13 +6,13 @@ import { ThemeConfig } from 'react-select/src/theme';
|
||||
|
||||
export class ArduinoSelect<T> extends Select<T> {
|
||||
|
||||
constructor(props: Readonly<Props<T>>) {
|
||||
constructor(props: Readonly<Props<T, false>>) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const controlHeight = 27; // from `monitor.css` -> `.serial-monitor-container .head` (`height: 27px;`)
|
||||
const styles: Styles = {
|
||||
const styles: Styles<T, false> = {
|
||||
control: styles => ({
|
||||
...styles,
|
||||
minWidth: 120,
|
||||
@@ -51,6 +51,7 @@ export class ArduinoSelect<T> extends Select<T> {
|
||||
const DropdownIndicator = () => <span className='fa fa-caret-down caret' />;
|
||||
return <Select
|
||||
{...this.props}
|
||||
className='theia-select'
|
||||
components={{ DropdownIndicator }}
|
||||
theme={theme}
|
||||
styles={styles}
|
||||
|
@@ -10,4 +10,8 @@ export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent>
|
||||
async initializeLayout(): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
||||
registerMenus(): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -5,6 +5,25 @@ import { Installable } from './installable';
|
||||
import { ArduinoComponent } from './arduino-component';
|
||||
|
||||
export type AvailablePorts = Record<string, [Port, Array<Board>]>;
|
||||
export namespace AvailablePorts {
|
||||
export function groupByProtocol(availablePorts: AvailablePorts): { serial: AvailablePorts, network: AvailablePorts, unknown: AvailablePorts } {
|
||||
const serial: AvailablePorts = {};
|
||||
const network: AvailablePorts = {};
|
||||
const unknown: AvailablePorts = {};
|
||||
for (const key of Object.keys(availablePorts)) {
|
||||
const [port, boards] = availablePorts[key];
|
||||
const { protocol } = port;
|
||||
if (protocol === 'serial') {
|
||||
serial[key] = [port, boards];
|
||||
} else if (protocol === 'network') {
|
||||
network[key] = [port, boards];
|
||||
} else {
|
||||
unknown[key] = [port, boards];
|
||||
}
|
||||
}
|
||||
return { serial, network, unknown };
|
||||
}
|
||||
}
|
||||
|
||||
export interface AttachedBoardsChangeEvent {
|
||||
readonly oldState: Readonly<{ boards: Board[], ports: Port[] }>;
|
||||
@@ -267,12 +286,25 @@ export namespace BoardWithPackage {
|
||||
|
||||
}
|
||||
|
||||
export interface InstalledBoardWithPackage extends BoardWithPackage {
|
||||
readonly fqbn: string;
|
||||
}
|
||||
export namespace InstalledBoardWithPackage {
|
||||
|
||||
export function is(boardWithPackage: BoardWithPackage): boardWithPackage is InstalledBoardWithPackage {
|
||||
return !!boardWithPackage.fqbn;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface BoardDetails {
|
||||
readonly fqbn: string;
|
||||
readonly requiredTools: Tool[];
|
||||
readonly configOptions: ConfigOption[];
|
||||
readonly programmers: Programmer[];
|
||||
readonly debuggingSupported: boolean;
|
||||
readonly VID: string;
|
||||
readonly PID: string;
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
|
@@ -3,15 +3,100 @@ export const ConfigService = Symbol('ConfigService');
|
||||
export interface ConfigService {
|
||||
getVersion(): Promise<Readonly<{ version: string, commit: string, status?: string }>>;
|
||||
getConfiguration(): Promise<Config>;
|
||||
getCliConfigFileUri(): Promise<string>;
|
||||
getConfigurationFileSchemaUri(): Promise<string>;
|
||||
setConfiguration(config: Config): Promise<void>;
|
||||
isInDataDir(uri: string): Promise<boolean>;
|
||||
isInSketchDir(uri: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface ProxySettings {
|
||||
protocol: string;
|
||||
hostname: string;
|
||||
port: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
export type Network = 'none' | ProxySettings;
|
||||
export namespace Network {
|
||||
|
||||
export function Default(): Network {
|
||||
return {
|
||||
protocol: 'http',
|
||||
hostname: '',
|
||||
port: '',
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
}
|
||||
|
||||
export function parse(raw: string | undefined): Network {
|
||||
if (!raw) {
|
||||
return 'none';
|
||||
}
|
||||
try {
|
||||
// Patter: PROTOCOL://USER:PASS@HOSTNAME:PORT/
|
||||
const { protocol, hostname, password, username, port } = new URL(raw);
|
||||
return {
|
||||
protocol,
|
||||
hostname,
|
||||
password,
|
||||
username,
|
||||
port
|
||||
};
|
||||
} catch {
|
||||
return 'none';
|
||||
}
|
||||
};
|
||||
|
||||
export function stringify(network: Network): string | undefined {
|
||||
if (network === 'none') {
|
||||
return undefined;
|
||||
}
|
||||
const { protocol, hostname, password, username, port } = network;
|
||||
try {
|
||||
const defaultUrl = new URL(`${protocol ? protocol : 'http'}://${hostname ? hostname : '_'}`);
|
||||
return Object.assign(defaultUrl, { protocol, hostname, password, username, port }).toString();
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function sameAs(left: Network, right: Network): boolean {
|
||||
if (left === 'none') {
|
||||
return right === 'none';
|
||||
}
|
||||
if (right === 'none') {
|
||||
return false;
|
||||
}
|
||||
return left.hostname === right.hostname
|
||||
&& left.password === right.password
|
||||
&& left.protocol === right.protocol
|
||||
&& left.username === right.username;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
readonly sketchDirUri: string;
|
||||
readonly dataDirUri: string;
|
||||
readonly downloadsDirUri: string;
|
||||
readonly additionalUrls: string[];
|
||||
readonly network: Network;
|
||||
}
|
||||
export namespace Config {
|
||||
export function sameAs(left: Config, right: Config): boolean {
|
||||
const leftUrls = left.additionalUrls.sort();
|
||||
const rightUrls = right.additionalUrls.sort();
|
||||
if (leftUrls.length !== rightUrls.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < leftUrls.length; i++) {
|
||||
if (leftUrls[i] !== rightUrls[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return left.dataDirUri === right.dataDirUri
|
||||
&& left.downloadsDirUri === right.downloadsDirUri
|
||||
&& left.sketchDirUri === right.sketchDirUri
|
||||
&& Network.sameAs(left.network, right.network);
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import { Programmer } from './boards-service';
|
||||
export const CoreServicePath = '/services/core-service';
|
||||
export const CoreService = Symbol('CoreService');
|
||||
export interface CoreService {
|
||||
compile(options: CoreService.Compile.Options): Promise<void>;
|
||||
compile(options: CoreService.Compile.Options & Readonly<{ exportBinaries?: boolean }>): Promise<void>;
|
||||
upload(options: CoreService.Upload.Options): Promise<void>;
|
||||
uploadUsingProgrammer(options: CoreService.Upload.Options): Promise<void>;
|
||||
burnBootloader(options: CoreService.Bootloader.Options): Promise<void>;
|
||||
@@ -13,9 +13,14 @@ export namespace CoreService {
|
||||
|
||||
export namespace Compile {
|
||||
export interface Options {
|
||||
/**
|
||||
* `file` URI to the sketch folder.
|
||||
*/
|
||||
readonly sketchUri: string;
|
||||
readonly fqbn?: string | undefined;
|
||||
readonly optimizeForDebug: boolean;
|
||||
readonly verbose: boolean;
|
||||
readonly sourceOverride: Record<string, string>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +28,7 @@ export namespace CoreService {
|
||||
export interface Options extends Compile.Options {
|
||||
readonly port?: string | undefined;
|
||||
readonly programmer?: Programmer | undefined;
|
||||
readonly verify: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +37,8 @@ export namespace CoreService {
|
||||
readonly fqbn?: string | undefined;
|
||||
readonly port?: string | undefined;
|
||||
readonly programmer?: Programmer | undefined;
|
||||
readonly verbose: boolean;
|
||||
readonly verify: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -69,4 +69,17 @@ export namespace LibraryPackage {
|
||||
return left.name === right.name && left.author === right.author;
|
||||
}
|
||||
|
||||
export function groupByLocation(packages: LibraryPackage[]): { user: LibraryPackage[], rest: LibraryPackage[] } {
|
||||
const user: LibraryPackage[] = [];
|
||||
const rest: LibraryPackage[] = [];
|
||||
for (const pkg of packages) {
|
||||
if (pkg.location === LibraryLocation.USER) {
|
||||
user.push(pkg);
|
||||
} else {
|
||||
rest.push(pkg);
|
||||
}
|
||||
}
|
||||
return { user, rest };
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { LibraryPackage } from './library-service';
|
||||
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import { BoardsPackage, AttachedBoardsChangeEvent } from './boards-service';
|
||||
import { Config } from './config-service';
|
||||
import { Sketch, Config, BoardsPackage, AttachedBoardsChangeEvent } from '../protocol';
|
||||
|
||||
export interface NotificationServiceClient {
|
||||
notifyIndexUpdated(): void;
|
||||
@@ -13,6 +12,7 @@ export interface NotificationServiceClient {
|
||||
notifyLibraryInstalled(event: { item: LibraryPackage }): void;
|
||||
notifyLibraryUninstalled(event: { item: LibraryPackage }): void;
|
||||
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void;
|
||||
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void;
|
||||
}
|
||||
|
||||
export const NotificationServicePath = '/services/notification-service';
|
||||
|
@@ -1,5 +1,4 @@
|
||||
export interface OutputMessage {
|
||||
readonly name: string;
|
||||
readonly chunk: string;
|
||||
readonly severity?: 'error' | 'warning' | 'info'; // Currently not used!
|
||||
}
|
||||
|
@@ -5,9 +5,13 @@ import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { Sketch, SketchesService } from '../../common/protocol';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { ConfigService } from './config-service';
|
||||
import { DisposableCollection, Emitter } from '@theia/core';
|
||||
import { FileChangeType } from '@theia/filesystem/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class SketchesServiceClientImpl {
|
||||
export class SketchesServiceClientImpl implements FrontendApplicationContribution {
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
@@ -21,6 +25,57 @@ export class SketchesServiceClientImpl {
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
protected toDispose = new DisposableCollection();
|
||||
protected sketches = new Map<string, Sketch>();
|
||||
protected sketchbookDidChangeEmitter = new Emitter<{ created: Sketch[], removed: Sketch[] }>();
|
||||
readonly onSketchbookDidChange = this.sketchbookDidChangeEmitter.event;
|
||||
|
||||
onStart(): void {
|
||||
this.configService.getConfiguration().then(({ sketchDirUri }) => {
|
||||
this.sketchService.getSketches(sketchDirUri).then(sketches => {
|
||||
const sketchbookUri = new URI(sketchDirUri);
|
||||
for (const sketch of sketches) {
|
||||
this.sketches.set(sketch.uri, sketch);
|
||||
}
|
||||
this.toDispose.push(this.fileService.watch(new URI(sketchDirUri), { recursive: true, excludes: [] }));
|
||||
this.toDispose.push(this.fileService.onDidFilesChange(async event => {
|
||||
for (const { type, resource } of event.changes) {
|
||||
// We track main sketch files changes only. // TODO: check sketch folder changes. One can rename the folder without renaming the `.ino` file.
|
||||
if (sketchbookUri.isEqualOrParent(resource)) {
|
||||
if (Sketch.isSketchFile(resource)) {
|
||||
if (type === FileChangeType.ADDED) {
|
||||
try {
|
||||
const toAdd = await this.sketchService.loadSketch(resource.parent.toString());
|
||||
if (!this.sketches.has(toAdd.uri)) {
|
||||
console.log(`New sketch '${toAdd.name}' was crated in sketchbook '${sketchDirUri}'.`);
|
||||
this.sketches.set(toAdd.uri, toAdd);
|
||||
this.fireSoon(toAdd, 'created');
|
||||
}
|
||||
} catch { }
|
||||
} else if (type === FileChangeType.DELETED) {
|
||||
const uri = resource.parent.toString();
|
||||
const toDelete = this.sketches.get(uri);
|
||||
if (toDelete) {
|
||||
console.log(`Sketch '${toDelete.name}' was removed from sketchbook '${sketchbookUri}'.`);
|
||||
this.sketches.delete(uri);
|
||||
this.fireSoon(toDelete, 'removed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onStop(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
async currentSketch(): Promise<Sketch | undefined> {
|
||||
const sketches = (await Promise.all(this.workspaceService.tryGetRoots().map(({ resource }) => this.sketchService.getSketchFolder(resource.toString())))).filter(notEmpty);
|
||||
if (!sketches.length) {
|
||||
@@ -46,4 +101,31 @@ export class SketchesServiceClientImpl {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private fireSoonHandle?: number;
|
||||
private bufferedSketchbookEvents: { type: 'created' | 'removed', sketch: Sketch }[] = [];
|
||||
|
||||
private fireSoon(sketch: Sketch, type: 'created' | 'removed'): void {
|
||||
this.bufferedSketchbookEvents.push({ type, sketch });
|
||||
|
||||
if (typeof this.fireSoonHandle === 'number') {
|
||||
window.clearTimeout(this.fireSoonHandle);
|
||||
}
|
||||
|
||||
this.fireSoonHandle = window.setTimeout(() => {
|
||||
const event: { created: Sketch[], removed: Sketch[] } = {
|
||||
created: [],
|
||||
removed: []
|
||||
};
|
||||
for (const { type, sketch } of this.bufferedSketchbookEvents) {
|
||||
if (type === 'created') {
|
||||
event.created.push(sketch);
|
||||
} else {
|
||||
event.removed.push(sketch);
|
||||
}
|
||||
}
|
||||
this.sketchbookDidChangeEmitter.fire(event);
|
||||
this.bufferedSketchbookEvents.length = 0;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -48,6 +48,21 @@ export interface SketchesService {
|
||||
*/
|
||||
getSketchFolder(uri: string): Promise<Sketch | undefined>;
|
||||
|
||||
/**
|
||||
* Marks the sketch with the given URI as recently opened. It does nothing if the sketch is temp or not valid.
|
||||
*/
|
||||
markAsRecentlyOpened(uri: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Resolves to an array of sketches in inverse chronological order. The newest is the first.
|
||||
*/
|
||||
recentlyOpenedSketches(): Promise<Sketch[]>;
|
||||
|
||||
/**
|
||||
* Archives the sketch, resolves to the archive URI.
|
||||
*/
|
||||
archive(sketch: Sketch, destinationUri: string): Promise<string>;
|
||||
|
||||
}
|
||||
|
||||
export interface Sketch {
|
||||
@@ -56,6 +71,7 @@ export interface Sketch {
|
||||
readonly mainFileUri: string; // `MainFile`
|
||||
readonly otherSketchFileUris: string[]; // `OtherSketchFiles`
|
||||
readonly additionalFileUris: string[]; // `AdditionalFiles`
|
||||
readonly rootFolderFileUris: string[]; // `RootFolderFiles` (does not include the main sketch file)
|
||||
}
|
||||
export namespace Sketch {
|
||||
export function is(arg: any): arg is Sketch {
|
||||
@@ -64,12 +80,17 @@ export namespace Sketch {
|
||||
export namespace Extensions {
|
||||
export const MAIN = ['.ino', '.pde'];
|
||||
export const SOURCE = ['.c', '.cpp', '.s'];
|
||||
export const ADDITIONAL = ['.h', '.c', '.hpp', '.hh', '.cpp', '.s'];
|
||||
export const ADDITIONAL = ['.h', '.c', '.hpp', '.hh', '.cpp', '.s', '.json'];
|
||||
export const ALL = Array.from(new Set([...MAIN, ...SOURCE, ...ADDITIONAL]));
|
||||
}
|
||||
export function isInSketch(uri: string | URI, sketch: Sketch): boolean {
|
||||
const { mainFileUri, otherSketchFileUris, additionalFileUris } = sketch;
|
||||
return [mainFileUri, ...otherSketchFileUris, ...additionalFileUris].indexOf(uri.toString()) !== -1;
|
||||
}
|
||||
export function isSketchFile(arg: string | URI): boolean {
|
||||
if (arg instanceof URI) {
|
||||
return isSketchFile(arg.toString());
|
||||
}
|
||||
return Extensions.MAIN.some(ext => arg.endsWith(ext));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,7 @@
|
||||
export type RecursiveRequired<T> = {
|
||||
[P in keyof T]-?: RecursiveRequired<T[P]>;
|
||||
};
|
||||
|
||||
export interface Index {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@@ -1,12 +1,18 @@
|
||||
import { injectable } from 'inversify'
|
||||
import { remote } from 'electron';
|
||||
import { Keybinding } from '@theia/core/lib/common/keybinding';
|
||||
import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
|
||||
import { ArduinoMenus } from '../../../browser/menu/arduino-menus';
|
||||
import { CompositeMenuNode } from '@theia/core/lib/common/menu';
|
||||
import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory, ElectronMenuOptions } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../../../browser/menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
|
||||
|
||||
createMenuBar(): Electron.Menu {
|
||||
this._toggledCommands.clear(); // https://github.com/eclipse-theia/theia/issues/8977
|
||||
return super.createMenuBar();
|
||||
}
|
||||
|
||||
protected acceleratorFor(keybinding: Keybinding): string {
|
||||
// TODO: https://github.com/eclipse-theia/theia/issues/8207
|
||||
return this.keybindingRegistry.resolveKeybinding(keybinding)
|
||||
@@ -18,7 +24,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
|
||||
|
||||
protected createOSXMenu(): Electron.MenuItemConstructorOptions {
|
||||
const { submenu } = super.createOSXMenu();
|
||||
const label = 'Arduino Pro IDE';
|
||||
const label = 'Arduino IDE';
|
||||
if (!!submenu && !(submenu instanceof remote.Menu)) {
|
||||
const [/* about */, /* settings */, ...rest] = submenu;
|
||||
const about = this.fillMenuTemplate([], this.menuProvider.getMenu(ArduinoMenus.HELP__ABOUT_GROUP));
|
||||
@@ -37,4 +43,15 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
|
||||
return { label, submenu };
|
||||
}
|
||||
|
||||
protected handleDefault(menuNode: CompositeMenuNode, args: any[] = [], options?: ElectronMenuOptions): Electron.MenuItemConstructorOptions[] {
|
||||
if (menuNode instanceof PlaceholderMenuNode) {
|
||||
return [{
|
||||
label: menuNode.label,
|
||||
enabled: false,
|
||||
visible: true
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,15 +1,20 @@
|
||||
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 { SplashService, splashServicePath } from '../electron-common/splash-service';
|
||||
import { SplashServiceImpl } from './splash/splash-service-impl';
|
||||
import { ElectronMainApplication } from './theia/electron-main-application';
|
||||
import { ElectronMainWindowServiceImpl } from './theia/electron-main-window-service';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ElectronMainApplication).toSelf().inSingletonScope();
|
||||
rebind(TheiaElectronMainApplication).toService(ElectronMainApplication);
|
||||
|
||||
bind(ElectronMainWindowServiceImpl).toSelf().inSingletonScope();
|
||||
rebind(ElectronMainWindowService).toService(ElectronMainWindowServiceImpl);
|
||||
|
||||
bind(SplashServiceImpl).toSelf().inSingletonScope();
|
||||
bind(SplashService).toService(SplashServiceImpl);
|
||||
bind(ElectronConnectionHandler).toDynamicValue(context =>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
@@ -13,7 +13,7 @@ import { SplashServiceImpl } from '../splash/splash-service-impl';
|
||||
@injectable()
|
||||
export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
|
||||
protected windows: BrowserWindow[] = [];
|
||||
protected _windows: BrowserWindow[] = [];
|
||||
|
||||
@inject(SplashServiceImpl)
|
||||
protected readonly splashService: SplashServiceImpl;
|
||||
@@ -34,7 +34,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
async createWindow(asyncOptions: MaybePromise<TheiaBrowserWindowOptions> = this.getDefaultBrowserWindowOptions()): Promise<BrowserWindow> {
|
||||
const options = await asyncOptions;
|
||||
let electronWindow: BrowserWindow | undefined;
|
||||
if (this.windows.length) {
|
||||
if (this._windows.length) {
|
||||
electronWindow = new BrowserWindow(options);
|
||||
} else {
|
||||
const { bounds } = screen.getDisplayNearestPoint(screen.getCursorScreenPoint());
|
||||
@@ -63,14 +63,14 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
splashScreenOpts
|
||||
}, this.splashService.onCloseRequested);
|
||||
}
|
||||
this.windows.push(electronWindow);
|
||||
this._windows.push(electronWindow);
|
||||
electronWindow.on('closed', () => {
|
||||
if (electronWindow) {
|
||||
const index = this.windows.indexOf(electronWindow);
|
||||
const index = this._windows.indexOf(electronWindow);
|
||||
if (index === -1) {
|
||||
console.warn(`Could not dispose browser window: '${electronWindow.title}'.`);
|
||||
} else {
|
||||
this.windows.splice(index, 1);
|
||||
this._windows.splice(index, 1);
|
||||
electronWindow = undefined;
|
||||
}
|
||||
}
|
||||
@@ -148,4 +148,8 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
}
|
||||
}
|
||||
|
||||
get windows(): BrowserWindow[] {
|
||||
return this._windows.slice();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,24 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { NewWindowOptions } from '@theia/core/lib/browser/window/window-service';
|
||||
import { ElectronMainWindowServiceImpl as TheiaElectronMainWindowService } from '@theia/core/lib/electron-main/electron-main-window-service-impl';
|
||||
import { ElectronMainApplication } from './electron-main-application';
|
||||
|
||||
@injectable()
|
||||
export class ElectronMainWindowServiceImpl extends TheiaElectronMainWindowService {
|
||||
|
||||
@inject(ElectronMainApplication)
|
||||
protected readonly app: ElectronMainApplication;
|
||||
|
||||
openNewWindow(url: string, { external }: NewWindowOptions): undefined {
|
||||
if (!external) {
|
||||
const existing = this.app.windows.find(window => window.webContents.getURL() === url);
|
||||
if (existing) {
|
||||
existing.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
return super.openNewWindow(url, { external });
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,6 +1,3 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { join } from 'path';
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { ArduinoDaemonImpl } from './arduino-daemon-impl';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
@@ -24,9 +21,6 @@ import { MonitorServiceImpl } from './monitor/monitor-service-impl';
|
||||
import { MonitorService, MonitorServicePath, MonitorServiceClient } from '../common/protocol/monitor-service';
|
||||
import { MonitorClientProvider } from './monitor/monitor-client-provider';
|
||||
import { ConfigServiceImpl } from './config-service-impl';
|
||||
import { HostedPluginReader } from './theia/plugin-ext/plugin-reader';
|
||||
import { HostedPluginReader as TheiaHostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader';
|
||||
import { ConfigFileValidator } from './config-file-validator';
|
||||
import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||
import { EnvVariablesServer } from './theia/env-variables/env-variables-server';
|
||||
import { NodeFileSystemExt } from './node-filesystem-ext';
|
||||
@@ -46,7 +40,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
rebind(TheiaBackendApplication).toService(BackendApplication);
|
||||
|
||||
// Shared config service
|
||||
bind(ConfigFileValidator).toSelf().inSingletonScope();
|
||||
bind(ConfigServiceImpl).toSelf().inSingletonScope();
|
||||
bind(ConfigService).toService(ConfigServiceImpl);
|
||||
// Note: The config service must start earlier than the daemon, hence the binding order of the BA contribution does matter.
|
||||
@@ -78,7 +71,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bindBackendService(LibraryServicePath, LibraryService);
|
||||
}));
|
||||
|
||||
// Shred sketches service
|
||||
// Shared sketches service
|
||||
bind(SketchesServiceImpl).toSelf().inSingletonScope();
|
||||
bind(SketchesService).toService(SketchesServiceImpl);
|
||||
bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(SketchesServicePath, () => context.container.get(SketchesService))).inSingletonScope();
|
||||
@@ -111,9 +104,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(EnvVariablesServer).toSelf().inSingletonScope();
|
||||
rebind(TheiaEnvVariablesServer).toService(EnvVariablesServer);
|
||||
|
||||
bind(HostedPluginReader).toSelf().inSingletonScope();
|
||||
rebind(TheiaHostedPluginReader).toService(HostedPluginReader);
|
||||
|
||||
// #endregion Theia customizations
|
||||
|
||||
// Monitor client provider per connected frontend.
|
||||
@@ -128,21 +118,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
});
|
||||
}));
|
||||
|
||||
// Set up cpp extension
|
||||
if (!process.env.CPP_CLANGD_COMMAND) {
|
||||
const segments = ['..', '..', 'build'];
|
||||
if (os.platform() === 'win32') {
|
||||
segments.push('clangd.exe');
|
||||
} else {
|
||||
segments.push('bin');
|
||||
segments.push('clangd');
|
||||
}
|
||||
const clangdCommand = join(__dirname, ...segments);
|
||||
if (fs.existsSync(clangdCommand)) {
|
||||
process.env.CPP_CLANGD_COMMAND = clangdCommand;
|
||||
}
|
||||
}
|
||||
|
||||
// File-system extension for mapping paths to URIs
|
||||
bind(NodeFileSystemExt).toSelf().inSingletonScope();
|
||||
bind(FileSystemExt).toService(NodeFileSystemExt);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { injectable, inject, named } from 'inversify';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { notEmpty } from '@theia/core/lib/common/objects';
|
||||
import {
|
||||
BoardsService,
|
||||
Installable,
|
||||
@@ -128,12 +129,22 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
platform: p.getPlatform()
|
||||
});
|
||||
|
||||
let VID = 'N/A';
|
||||
let PID = 'N/A';
|
||||
const usbId = detailsResp.getIdentificationPrefList().map(item => item.getUsbid()).find(notEmpty);
|
||||
if (usbId) {
|
||||
VID = usbId.getVid();
|
||||
PID = usbId.getPid();
|
||||
}
|
||||
|
||||
return {
|
||||
fqbn,
|
||||
requiredTools,
|
||||
configOptions,
|
||||
programmers,
|
||||
debuggingSupported
|
||||
debuggingSupported,
|
||||
VID,
|
||||
PID
|
||||
};
|
||||
}
|
||||
|
||||
@@ -260,7 +271,7 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
resp.on('data', (r: PlatformInstallResp) => {
|
||||
const prog = r.getProgress();
|
||||
if (prog && prog.getFile()) {
|
||||
this.outputService.append({ name: 'board download', chunk: `downloading ${prog.getFile()}\n` });
|
||||
this.outputService.append({ chunk: `downloading ${prog.getFile()}\n` });
|
||||
}
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
@@ -291,7 +302,7 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
const resp = client.platformUninstall(req);
|
||||
resp.on('data', (_: PlatformUninstallResp) => {
|
||||
if (!logged) {
|
||||
this.outputService.append({ name: 'board uninstall', chunk: `uninstalling ${item.id}\n` });
|
||||
this.outputService.append({ chunk: `uninstalling ${item.id}\n` });
|
||||
logged = true;
|
||||
}
|
||||
})
|
||||
|
@@ -1,9 +1,6 @@
|
||||
import { join } from 'path';
|
||||
import { RecursivePartial } from '@theia/core/lib/common/types';
|
||||
|
||||
export const CLI_CONFIG = 'arduino-cli.yaml';
|
||||
export const CLI_CONFIG_SCHEMA = 'arduino-cli.schema.json';
|
||||
export const CLI_CONFIG_SCHEMA_PATH = join(__dirname, '..', '..', 'data', 'cli', 'schema', CLI_CONFIG_SCHEMA);
|
||||
|
||||
export interface BoardManager {
|
||||
readonly additional_urls: Array<string>;
|
||||
@@ -95,11 +92,16 @@ export namespace Logging {
|
||||
|
||||
}
|
||||
|
||||
export interface Network {
|
||||
proxy?: string;
|
||||
}
|
||||
|
||||
// Arduino CLI config scheme
|
||||
export interface CliConfig {
|
||||
board_manager?: RecursivePartial<BoardManager>;
|
||||
directories?: RecursivePartial<Directories>;
|
||||
logging?: RecursivePartial<Logging>;
|
||||
network?: RecursivePartial<Network>;
|
||||
}
|
||||
|
||||
// Bare minimum required CLI config.
|
||||
|
@@ -1175,7 +1175,7 @@ libraryInstall: {
|
||||
responseSerialize: serialize_cc_arduino_cli_commands_LibraryInstallResp,
|
||||
responseDeserialize: deserialize_cc_arduino_cli_commands_LibraryInstallResp,
|
||||
},
|
||||
// Install a library from a Zip File
|
||||
// Install a library from a Zip File
|
||||
zipLibraryInstall: {
|
||||
path: '/cc.arduino.cli.commands.ArduinoCore/ZipLibraryInstall',
|
||||
requestStream: false,
|
||||
|
@@ -512,6 +512,11 @@ export class LoadSketchResp extends jspb.Message {
|
||||
setAdditionalFilesList(value: Array<string>): LoadSketchResp;
|
||||
addAdditionalFiles(value: string, index?: number): string;
|
||||
|
||||
clearRootFolderFilesList(): void;
|
||||
getRootFolderFilesList(): Array<string>;
|
||||
setRootFolderFilesList(value: Array<string>): LoadSketchResp;
|
||||
addRootFolderFiles(value: string, index?: number): string;
|
||||
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): LoadSketchResp.AsObject;
|
||||
@@ -529,6 +534,7 @@ export namespace LoadSketchResp {
|
||||
locationPath: string,
|
||||
otherSketchFilesList: Array<string>,
|
||||
additionalFilesList: Array<string>,
|
||||
rootFolderFilesList: Array<string>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3639,7 +3639,7 @@ proto.cc.arduino.cli.commands.LoadSketchReq.prototype.setSketchPath = function(v
|
||||
* @private {!Array<number>}
|
||||
* @const
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.LoadSketchResp.repeatedFields_ = [3,4];
|
||||
proto.cc.arduino.cli.commands.LoadSketchResp.repeatedFields_ = [3,4,5];
|
||||
|
||||
|
||||
|
||||
@@ -3675,7 +3675,8 @@ proto.cc.arduino.cli.commands.LoadSketchResp.toObject = function(includeInstance
|
||||
mainFile: jspb.Message.getFieldWithDefault(msg, 1, ""),
|
||||
locationPath: jspb.Message.getFieldWithDefault(msg, 2, ""),
|
||||
otherSketchFilesList: (f = jspb.Message.getRepeatedField(msg, 3)) == null ? undefined : f,
|
||||
additionalFilesList: (f = jspb.Message.getRepeatedField(msg, 4)) == null ? undefined : f
|
||||
additionalFilesList: (f = jspb.Message.getRepeatedField(msg, 4)) == null ? undefined : f,
|
||||
rootFolderFilesList: (f = jspb.Message.getRepeatedField(msg, 5)) == null ? undefined : f
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@@ -3728,6 +3729,10 @@ proto.cc.arduino.cli.commands.LoadSketchResp.deserializeBinaryFromReader = funct
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.addAdditionalFiles(value);
|
||||
break;
|
||||
case 5:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.addRootFolderFiles(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@@ -3785,6 +3790,13 @@ proto.cc.arduino.cli.commands.LoadSketchResp.serializeBinaryToWriter = function(
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getRootFolderFilesList();
|
||||
if (f.length > 0) {
|
||||
writer.writeRepeatedString(
|
||||
5,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3898,6 +3910,43 @@ proto.cc.arduino.cli.commands.LoadSketchResp.prototype.clearAdditionalFilesList
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* repeated string root_folder_files = 5;
|
||||
* @return {!Array<string>}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.LoadSketchResp.prototype.getRootFolderFilesList = function() {
|
||||
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 5));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Array<string>} value
|
||||
* @return {!proto.cc.arduino.cli.commands.LoadSketchResp} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.LoadSketchResp.prototype.setRootFolderFilesList = function(value) {
|
||||
return jspb.Message.setField(this, 5, value || []);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {number=} opt_index
|
||||
* @return {!proto.cc.arduino.cli.commands.LoadSketchResp} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.LoadSketchResp.prototype.addRootFolderFiles = function(value, opt_index) {
|
||||
return jspb.Message.addToRepeatedField(this, 5, value, opt_index);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the list making it empty but non-null.
|
||||
* @return {!proto.cc.arduino.cli.commands.LoadSketchResp} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.LoadSketchResp.prototype.clearRootFolderFilesList = function() {
|
||||
return this.setRootFolderFilesList([]);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -5,6 +5,7 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
import * as google_protobuf_wrappers_pb from "google-protobuf/google/protobuf/wrappers_pb";
|
||||
import * as commands_common_pb from "../commands/common_pb";
|
||||
import * as commands_lib_pb from "../commands/lib_pb";
|
||||
|
||||
@@ -67,8 +68,18 @@ export class CompileReq extends jspb.Message {
|
||||
getClean(): boolean;
|
||||
setClean(value: boolean): CompileReq;
|
||||
|
||||
getExportBinaries(): boolean;
|
||||
setExportBinaries(value: boolean): CompileReq;
|
||||
getCreateCompilationDatabaseOnly(): boolean;
|
||||
setCreateCompilationDatabaseOnly(value: boolean): CompileReq;
|
||||
|
||||
|
||||
getSourceOverrideMap(): jspb.Map<string, string>;
|
||||
clearSourceOverrideMap(): void;
|
||||
|
||||
|
||||
hasExportBinaries(): boolean;
|
||||
clearExportBinaries(): void;
|
||||
getExportBinaries(): google_protobuf_wrappers_pb.BoolValue | undefined;
|
||||
setExportBinaries(value?: google_protobuf_wrappers_pb.BoolValue): CompileReq;
|
||||
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
@@ -100,7 +111,10 @@ export namespace CompileReq {
|
||||
optimizefordebug: boolean,
|
||||
exportDir: string,
|
||||
clean: boolean,
|
||||
exportBinaries: boolean,
|
||||
createCompilationDatabaseOnly: boolean,
|
||||
|
||||
sourceOverrideMap: Array<[string, string]>,
|
||||
exportBinaries?: google_protobuf_wrappers_pb.BoolValue.AsObject,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,8 @@ var jspb = require('google-protobuf');
|
||||
var goog = jspb;
|
||||
var global = Function('return this')();
|
||||
|
||||
var google_protobuf_wrappers_pb = require('google-protobuf/google/protobuf/wrappers_pb.js');
|
||||
goog.object.extend(proto, google_protobuf_wrappers_pb);
|
||||
var commands_common_pb = require('../commands/common_pb.js');
|
||||
goog.object.extend(proto, commands_common_pb);
|
||||
var commands_lib_pb = require('../commands/lib_pb.js');
|
||||
@@ -140,7 +142,9 @@ proto.cc.arduino.cli.commands.CompileReq.toObject = function(includeInstance, ms
|
||||
optimizefordebug: jspb.Message.getBooleanFieldWithDefault(msg, 16, false),
|
||||
exportDir: jspb.Message.getFieldWithDefault(msg, 18, ""),
|
||||
clean: jspb.Message.getBooleanFieldWithDefault(msg, 19, false),
|
||||
exportBinaries: jspb.Message.getBooleanFieldWithDefault(msg, 20, false)
|
||||
createCompilationDatabaseOnly: jspb.Message.getBooleanFieldWithDefault(msg, 21, false),
|
||||
sourceOverrideMap: (f = msg.getSourceOverrideMap()) ? f.toObject(includeInstance, undefined) : [],
|
||||
exportBinaries: (f = msg.getExportBinaries()) && google_protobuf_wrappers_pb.BoolValue.toObject(includeInstance, f)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@@ -246,8 +250,19 @@ proto.cc.arduino.cli.commands.CompileReq.deserializeBinaryFromReader = function(
|
||||
var value = /** @type {boolean} */ (reader.readBool());
|
||||
msg.setClean(value);
|
||||
break;
|
||||
case 20:
|
||||
case 21:
|
||||
var value = /** @type {boolean} */ (reader.readBool());
|
||||
msg.setCreateCompilationDatabaseOnly(value);
|
||||
break;
|
||||
case 22:
|
||||
var value = msg.getSourceOverrideMap();
|
||||
reader.readMessage(value, function(message, reader) {
|
||||
jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readString, null, "", "");
|
||||
});
|
||||
break;
|
||||
case 23:
|
||||
var value = new google_protobuf_wrappers_pb.BoolValue;
|
||||
reader.readMessage(value,google_protobuf_wrappers_pb.BoolValue.deserializeBinaryFromReader);
|
||||
msg.setExportBinaries(value);
|
||||
break;
|
||||
default:
|
||||
@@ -399,13 +414,25 @@ proto.cc.arduino.cli.commands.CompileReq.serializeBinaryToWriter = function(mess
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getExportBinaries();
|
||||
f = message.getCreateCompilationDatabaseOnly();
|
||||
if (f) {
|
||||
writer.writeBool(
|
||||
20,
|
||||
21,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getSourceOverrideMap(true);
|
||||
if (f && f.getLength() > 0) {
|
||||
f.serializeBinary(22, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeString);
|
||||
}
|
||||
f = message.getExportBinaries();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
23,
|
||||
f,
|
||||
google_protobuf_wrappers_pb.BoolValue.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -773,11 +800,11 @@ proto.cc.arduino.cli.commands.CompileReq.prototype.setClean = function(value) {
|
||||
|
||||
|
||||
/**
|
||||
* optional bool export_binaries = 20;
|
||||
* optional bool create_compilation_database_only = 21;
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.CompileReq.prototype.getExportBinaries = function() {
|
||||
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 20, false));
|
||||
proto.cc.arduino.cli.commands.CompileReq.prototype.getCreateCompilationDatabaseOnly = function() {
|
||||
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 21, false));
|
||||
};
|
||||
|
||||
|
||||
@@ -785,8 +812,67 @@ proto.cc.arduino.cli.commands.CompileReq.prototype.getExportBinaries = function(
|
||||
* @param {boolean} value
|
||||
* @return {!proto.cc.arduino.cli.commands.CompileReq} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.CompileReq.prototype.setCreateCompilationDatabaseOnly = function(value) {
|
||||
return jspb.Message.setProto3BooleanField(this, 21, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* map<string, string> source_override = 22;
|
||||
* @param {boolean=} opt_noLazyCreate Do not create the map if
|
||||
* empty, instead returning `undefined`
|
||||
* @return {!jspb.Map<string,string>}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.CompileReq.prototype.getSourceOverrideMap = function(opt_noLazyCreate) {
|
||||
return /** @type {!jspb.Map<string,string>} */ (
|
||||
jspb.Message.getMapField(this, 22, opt_noLazyCreate,
|
||||
null));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears values from the map. The map will be non-null.
|
||||
* @return {!proto.cc.arduino.cli.commands.CompileReq} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.CompileReq.prototype.clearSourceOverrideMap = function() {
|
||||
this.getSourceOverrideMap().clear();
|
||||
return this;};
|
||||
|
||||
|
||||
/**
|
||||
* optional google.protobuf.BoolValue export_binaries = 23;
|
||||
* @return {?proto.google.protobuf.BoolValue}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.CompileReq.prototype.getExportBinaries = function() {
|
||||
return /** @type{?proto.google.protobuf.BoolValue} */ (
|
||||
jspb.Message.getWrapperField(this, google_protobuf_wrappers_pb.BoolValue, 23));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?proto.google.protobuf.BoolValue|undefined} value
|
||||
* @return {!proto.cc.arduino.cli.commands.CompileReq} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.CompileReq.prototype.setExportBinaries = function(value) {
|
||||
return jspb.Message.setProto3BooleanField(this, 20, value);
|
||||
return jspb.Message.setWrapperField(this, 23, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the message field making it undefined.
|
||||
* @return {!proto.cc.arduino.cli.commands.CompileReq} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.CompileReq.prototype.clearExportBinaries = function() {
|
||||
return this.setExportBinaries(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.CompileReq.prototype.hasExportBinaries = function() {
|
||||
return jspb.Message.getField(this, 23) != null;
|
||||
};
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user