mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-10-11 20:38:32 +00:00
Compare commits
88 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6e3fe08c4c | ||
![]() |
7f06b148f4 | ||
![]() |
bf303d1b2f | ||
![]() |
59ca91d805 | ||
![]() |
69bb0aa385 | ||
![]() |
565970e779 | ||
![]() |
fec3b1138b | ||
![]() |
dcc0c0aa5d | ||
![]() |
76673cb553 | ||
![]() |
8f95fd6ca6 | ||
![]() |
4907ef2a47 | ||
![]() |
9ae3402631 | ||
![]() |
d0dfc656e6 | ||
![]() |
df3a34eec6 | ||
![]() |
20cc34ca9d | ||
![]() |
1b7f86b231 | ||
![]() |
0d545bea0e | ||
![]() |
204d71b2dd | ||
![]() |
5cb9166c83 | ||
![]() |
7828cc11ac | ||
![]() |
34a7fdb733 | ||
![]() |
7c361cf2d1 | ||
![]() |
8beade0867 | ||
![]() |
3afc2d7e4b | ||
![]() |
d40401437a | ||
![]() |
10ac7fd50a | ||
![]() |
07962e81d4 | ||
![]() |
785775327b | ||
![]() |
80dfa5b7dd | ||
![]() |
40425d49e0 | ||
![]() |
0c87fa9877 | ||
![]() |
5b79320302 | ||
![]() |
1da2dfc349 | ||
![]() |
d7bbfc515d | ||
![]() |
0c22884729 | ||
![]() |
fc9107c084 | ||
![]() |
474d5e5975 | ||
![]() |
f7f644cf36 | ||
![]() |
b5f9aa0f15 | ||
![]() |
cc5cf3b165 | ||
![]() |
125bd64c91 | ||
![]() |
ca47e8a09a | ||
![]() |
52804a5b52 | ||
![]() |
3ec62642dd | ||
![]() |
1281ad1932 | ||
![]() |
de32bddc20 | ||
![]() |
79ea0fa9a6 | ||
![]() |
683219dc1c | ||
![]() |
d674ab9b73 | ||
![]() |
5be1f9d7fe | ||
![]() |
9e2b73a045 | ||
![]() |
75e00c2bae | ||
![]() |
989300f25d | ||
![]() |
5226636fed | ||
![]() |
8b3f3c69fc | ||
![]() |
a39ab47e70 | ||
![]() |
9cabd40429 | ||
![]() |
6e3681896c | ||
![]() |
8a1cabd2bc | ||
![]() |
7a3e6789d1 | ||
![]() |
92bc5ecf7b | ||
![]() |
aebec0f942 | ||
![]() |
54db9bbce8 | ||
![]() |
676eb2f588 | ||
![]() |
ce273adf77 | ||
![]() |
0b33b51700 | ||
![]() |
36ac47b975 | ||
![]() |
bf193b1cac | ||
![]() |
879aedeaa3 | ||
![]() |
d556ee95c0 | ||
![]() |
d93c9ba654 | ||
![]() |
8a0dc1be7e | ||
![]() |
564862e173 | ||
![]() |
d7f7010bb5 | ||
![]() |
e156dcc213 | ||
![]() |
27a2a6ca03 | ||
![]() |
581379f86f | ||
![]() |
b62f3dec84 | ||
![]() |
90d2950bdd | ||
![]() |
5b7d64c1c1 | ||
![]() |
55927ac3dd | ||
![]() |
40c93bc19a | ||
![]() |
59b8a2d6bb | ||
![]() |
124738d810 | ||
![]() |
19c0334a91 | ||
![]() |
f22be3c587 | ||
![]() |
9373a0bcaf | ||
![]() |
5087ff08f2 |
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -28,6 +28,8 @@ on:
|
|||||||
- cron: '0 3 * * *' # run every day at 3AM (https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule)
|
- cron: '0 3 * * *' # run every day at 3AM (https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule)
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||||
|
GO_VERSION: "1.17"
|
||||||
JOB_TRANSFER_ARTIFACT: build-artifacts
|
JOB_TRANSFER_ARTIFACT: build-artifacts
|
||||||
CHANGELOG_ARTIFACTS: changelog
|
CHANGELOG_ARTIFACTS: changelog
|
||||||
|
|
||||||
@@ -66,6 +68,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Taskfile
|
||||||
|
uses: arduino/setup-task@v1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
version: 3.x
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
|
15
.github/workflows/check-i18n-task.yml
vendored
15
.github/workflows/check-i18n-task.yml
vendored
@@ -1,5 +1,9 @@
|
|||||||
name: Check Internationalization
|
name: Check Internationalization
|
||||||
|
|
||||||
|
env:
|
||||||
|
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||||
|
GO_VERSION: "1.17"
|
||||||
|
|
||||||
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
|
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -31,6 +35,17 @@ jobs:
|
|||||||
node-version: '14.x'
|
node-version: '14.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Taskfile
|
||||||
|
uses: arduino/setup-task@v1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
version: 3.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
|
15
.github/workflows/i18n-nightly-push.yml
vendored
15
.github/workflows/i18n-nightly-push.yml
vendored
@@ -1,5 +1,9 @@
|
|||||||
name: i18n-nightly-push
|
name: i18n-nightly-push
|
||||||
|
|
||||||
|
env:
|
||||||
|
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||||
|
GO_VERSION: "1.17"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
# run every day at 1AM
|
# run every day at 1AM
|
||||||
@@ -18,6 +22,17 @@ jobs:
|
|||||||
node-version: '14.x'
|
node-version: '14.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Task
|
||||||
|
uses: arduino/setup-task@v1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
version: 3.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
|
15
.github/workflows/i18n-weekly-pull.yml
vendored
15
.github/workflows/i18n-weekly-pull.yml
vendored
@@ -1,5 +1,9 @@
|
|||||||
name: i18n-weekly-pull
|
name: i18n-weekly-pull
|
||||||
|
|
||||||
|
env:
|
||||||
|
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||||
|
GO_VERSION: "1.17"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
# run every monday at 2AM
|
# run every monday at 2AM
|
||||||
@@ -18,6 +22,17 @@ jobs:
|
|||||||
node-version: '14.x'
|
node-version: '14.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Task
|
||||||
|
uses: arduino/setup-task@v1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
version: 3.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
|
13
.github/workflows/themes-weekly-pull.yml
vendored
13
.github/workflows/themes-weekly-pull.yml
vendored
@@ -7,6 +7,8 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||||
|
GO_VERSION: "1.17"
|
||||||
NODE_VERSION: 14.x
|
NODE_VERSION: 14.x
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -22,6 +24,17 @@ jobs:
|
|||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Task
|
||||||
|
uses: arduino/setup-task@v1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
version: 3.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
|
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
@@ -20,7 +20,6 @@
|
|||||||
"--no-app-auto-install",
|
"--no-app-auto-install",
|
||||||
"--plugins=local-dir:../plugins",
|
"--plugins=local-dir:../plugins",
|
||||||
"--hosted-plugin-inspect=9339",
|
"--hosted-plugin-inspect=9339",
|
||||||
"--nosplash",
|
|
||||||
"--content-trace",
|
"--content-trace",
|
||||||
"--open-devtools"
|
"--open-devtools"
|
||||||
],
|
],
|
||||||
|
35
BUILDING.md
35
BUILDING.md
@@ -41,7 +41,7 @@ The _frontend_ is running as an Electron renderer process and can invoke service
|
|||||||
|
|
||||||
If you’re familiar with TypeScript, the [Theia IDE](https://theia-ide.org/), and if you want to contribute to the
|
If you’re familiar with TypeScript, the [Theia IDE](https://theia-ide.org/), and if you want to contribute to the
|
||||||
project, you should be able to build the Arduino IDE locally.
|
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.
|
Please refer to the [Theia IDE prerequisites](https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md#prerequisites) documentation for the setup instructions.
|
||||||
> **Note**: Node.js 14 must be used instead of the version 12 recommended at the link above.
|
> **Note**: Node.js 14 must be used instead of the version 12 recommended at the link above.
|
||||||
|
|
||||||
Once you have all the tools installed, you can build the editor following these steps
|
Once you have all the tools installed, you can build the editor following these steps
|
||||||
@@ -89,7 +89,6 @@ This project is built on [GitHub Actions](https://github.com/arduino/arduino-ide
|
|||||||
git push origin 1.2.3
|
git push origin 1.2.3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Notes for macOS contributors
|
## Notes for macOS contributors
|
||||||
Beginning in macOS 10.14.5, the software [must be notarized to run](https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution). The signing and notarization processes for the Arduino IDE are managed by our Continuous Integration (CI) workflows, implemented with GitHub Actions. On every push and pull request, the Arduino IDE is built and saved to a workflow artifact. These artifacts can be used by contributors and beta testers who don't want to set up a build system locally.
|
Beginning in macOS 10.14.5, the software [must be notarized to run](https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution). The signing and notarization processes for the Arduino IDE are managed by our Continuous Integration (CI) workflows, implemented with GitHub Actions. On every push and pull request, the Arduino IDE is built and saved to a workflow artifact. These artifacts can be used by contributors and beta testers who don't want to set up a build system locally.
|
||||||
For security reasons, signing and notarization are disabled for workflow runs for pull requests from forks of this repository. This means that macOS will block you from running those artifacts.
|
For security reasons, signing and notarization are disabled for workflow runs for pull requests from forks of this repository. This means that macOS will block you from running those artifacts.
|
||||||
@@ -106,38 +105,6 @@ Follow [the instructions above](#build-from-source) to create the build environm
|
|||||||
1. Download the unsigned artifact provided by the CI workflow run related to the Pull Request at each push.
|
1. Download the unsigned artifact provided by the CI workflow run related to the Pull Request at each push.
|
||||||
1. Re-enable Gatekeeper after tests are done, following the guide linked above.
|
1. Re-enable Gatekeeper after tests are done, following the guide linked above.
|
||||||
|
|
||||||
### Creating a release
|
|
||||||
|
|
||||||
You will not need to create a new release yourself as the Arduino team takes care of this on a regular basis, but we are documenting the process here. 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 [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
|
## FAQ
|
||||||
|
|
||||||
* *Can I manually change the version of the [`arduino-cli`](https://github.com/arduino/arduino-cli/) used by the IDE?*
|
* *Can I manually change the version of the [`arduino-cli`](https://github.com/arduino/arduino-cli/) used by the IDE?*
|
||||||
|
33
README.md
33
README.md
@@ -1,45 +1,18 @@
|
|||||||
<img src="https://content.arduino.cc/website/Arduino_logo_teal.svg" height="100" align="right" />
|
<img src="https://content.arduino.cc/website/Arduino_logo_teal.svg" height="100" align="right" />
|
||||||
|
|
||||||
# Arduino IDE 2.x (beta)
|
# Arduino IDE 2.x
|
||||||
|
|
||||||
[](https://github.com/arduino/arduino-ide/actions?query=workflow%3A%22Arduino+IDE%22)
|
[](https://github.com/arduino/arduino-ide/actions?query=workflow%3A%22Arduino+IDE%22)
|
||||||
|
|
||||||
This repository contains the source code of the Arduino IDE 2.x, which is currently in beta stage. If you're looking for the stable IDE, go to the repository of the 1.x version at https://github.com/arduino/Arduino.
|
This repository contains the source code of the Arduino IDE 2.x. If you're looking for the old IDE, go to the repository of the 1.x version at https://github.com/arduino/Arduino.
|
||||||
|
|
||||||
The Arduino IDE 2.x is a major rewrite, sharing no code with the IDE 1.x. It is based on the [Theia IDE](https://theia-ide.org/) framework and built with [Electron](https://www.electronjs.org/). The backend operations such as compilation and uploading are offloaded to an [arduino-cli](https://github.com/arduino/arduino-cli) instance running in daemon mode. This new IDE was developed with the goal of preserving the same interface and user experience of the previous major version in order to provide a frictionless upgrade.
|
The Arduino IDE 2.x is a major rewrite, sharing no code with the IDE 1.x. It is based on the [Theia IDE](https://theia-ide.org/) framework and built with [Electron](https://www.electronjs.org/). The backend operations such as compilation and uploading are offloaded to an [arduino-cli](https://github.com/arduino/arduino-cli) instance running in daemon mode. This new IDE was developed with the goal of preserving the same interface and user experience of the previous major version in order to provide a frictionless upgrade.
|
||||||
|
|
||||||
> ⚠️ This is **beta** software. Help us test it!
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
You can download the latest version from the [software download page on the Arduino website](https://www.arduino.cc/en/software#experimental-software).
|
You can download the latest release version and nightly builds from the [software download page on the Arduino website](https://www.arduino.cc/en/software).
|
||||||
|
|
||||||
### Nightly builds
|
|
||||||
|
|
||||||
These builds are generated every day at 03:00 GMT from the `main` branch and
|
|
||||||
should be considered unstable:
|
|
||||||
|
|
||||||
| Platform | 32 bit | 64 bit |
|
|
||||||
| --------- | ------------------------ | ------------------------------------------------------------------------------------------------------ |
|
|
||||||
| Linux | | [Nightly Linux AppImage 64 bit]<br />[Nightly Linux ZIP file 64 bit] |
|
|
||||||
| Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...] |
|
|
||||||
| Windows | | [Nightly Windows 64 bit installer]<br />[Nightly Windows 64 bit MSI]<br />[Nightly Windows 64 bit ZIP] |
|
|
||||||
| macOS | | [Nightly macOS 64 bit] |
|
|
||||||
|
|
||||||
[🚧 work in progress...]: https://github.com/arduino/arduino-ide/issues/107
|
|
||||||
[nightly linux appimage 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Linux_64bit.AppImage
|
|
||||||
[nightly linux zip file 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Linux_64bit.zip
|
|
||||||
[nightly windows 64 bit installer]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.exe
|
|
||||||
[nightly windows 64 bit msi]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.msi
|
|
||||||
[nightly windows 64 bit zip]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.zip
|
|
||||||
[nightly macos 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_macOS_64bit.dmg
|
|
||||||
|
|
||||||
> These links return an HTTP `302: Found` response, redirecting to latest
|
|
||||||
> generated builds by replacing `latest` with the latest available build
|
|
||||||
> date, using the format YYYYMMDD (i.e for 2019/Aug/06 `latest` is
|
|
||||||
> replaced with `20190806`)
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
@@ -62,6 +62,15 @@ The Config Service knows about your system, like for example the default sketch
|
|||||||
- Some CLI updates can bring changes to the gRPC interfaces, as the API might change. gRPC interfaces can be updated running the command
|
- Some CLI updates can bring changes to the gRPC interfaces, as the API might change. gRPC interfaces can be updated running the command
|
||||||
`yarn --cwd arduino-ide-extension generate-protocol`
|
`yarn --cwd arduino-ide-extension generate-protocol`
|
||||||
|
|
||||||
|
### Update **clangd** and **ClangFormat**
|
||||||
|
|
||||||
|
The [**clangd** C++ language server](https://clangd.llvm.org/) and the [**ClangFormat** code formatter](https://clang.llvm.org/docs/ClangFormat.html) tool dependencies are managed in parallel. Updating them to a different version is done by the following procedure:
|
||||||
|
|
||||||
|
1. If the target version is not already [available from the `arduino/clang-static-binaries` repository](https://github.com/arduino/clang-static-binaries/releases), submit [an issue there](https://github.com/arduino/clang-static-binaries/issues) requesting a build and wait for that to be completed.
|
||||||
|
1. Validate the **ClangFormat** configuration for the target version by following the instructions [**here**](https://github.com/arduino/tooling-project-assets/tree/main/other/clang-format-configuration#clangformat-version-updates)
|
||||||
|
1. Submit a pull request in the `arduino/arduino-ide` repository to update the version in the `arduino.clangd.version` key of [`package.json`](package.json).
|
||||||
|
1. Submit a pull request in [the `arduino/tooling-project-assets` repository](https://github.com/arduino/tooling-project-assets) to update the version in the `vars.DEFAULT_CLANG_FORMAT_VERSION` field of [`Taskfile.yml`](https://github.com/arduino/tooling-project-assets/blob/main/Taskfile.yml).
|
||||||
|
|
||||||
### Customize Icons
|
### Customize Icons
|
||||||
ArduinoIde uses a customized version of FontAwesome.
|
ArduinoIde uses a customized version of FontAwesome.
|
||||||
In order to update/replace icons follow the following steps:
|
In order to update/replace icons follow the following steps:
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "arduino-ide-extension",
|
"name": "arduino-ide-extension",
|
||||||
"version": "2.0.0-rc9",
|
"version": "2.0.0",
|
||||||
"description": "An extension for Theia building the Arduino IDE",
|
"description": "An extension for Theia building the Arduino IDE",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -150,13 +150,17 @@
|
|||||||
"frontend": "lib/browser/theia/core/browser-menu-module",
|
"frontend": "lib/browser/theia/core/browser-menu-module",
|
||||||
"frontendElectron": "lib/electron-browser/theia/core/electron-menu-module"
|
"frontendElectron": "lib/electron-browser/theia/core/electron-menu-module"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"frontend": "lib/browser/theia/core/browser-window-module",
|
||||||
|
"frontendElectron": "lib/electron-browser/theia/core/electron-window-module"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"electronMain": "lib/electron-main/arduino-electron-main-module"
|
"electronMain": "lib/electron-main/arduino-electron-main-module"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"arduino": {
|
"arduino": {
|
||||||
"cli": {
|
"cli": {
|
||||||
"version": "0.25.0"
|
"version": "0.27.1"
|
||||||
},
|
},
|
||||||
"fwuploader": {
|
"fwuploader": {
|
||||||
"version": "2.2.0"
|
"version": "2.2.0"
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const downloader = require('./downloader');
|
const downloader = require('./downloader');
|
||||||
const { goBuildFromGit } = require('./utils');
|
const { taskBuildFromGit } = require('./utils');
|
||||||
|
|
||||||
const version = (() => {
|
const version = (() => {
|
||||||
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
||||||
@@ -82,6 +82,6 @@
|
|||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
goBuildFromGit(version, destinationPath, 'CLI');
|
taskBuildFromGit(version, destinationPath, 'CLI');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@@ -1,3 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Clones something from GitHub and builds it with [`Task`](https://taskfile.dev/).
|
||||||
|
*
|
||||||
|
* @param version {object} the version object.
|
||||||
|
* @param destinationPath {string} the absolute path of the output binary. For example, `C:\\folder\\arduino-cli.exe` or `/path/to/arduino-language-server`
|
||||||
|
* @param taskName {string} for the CLI logging . Can be `'CLI'` or `'language-server'`, etc.
|
||||||
|
*/
|
||||||
|
exports.taskBuildFromGit = (version, destinationPath, taskName) => {
|
||||||
|
return buildFromGit('task', version, destinationPath, taskName);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clones something from GitHub and builds it with `Golang`.
|
* Clones something from GitHub and builds it with `Golang`.
|
||||||
*
|
*
|
||||||
@@ -6,6 +17,13 @@
|
|||||||
* @param taskName {string} for the CLI logging . Can be `'CLI'` or `'language-server'`, etc.
|
* @param taskName {string} for the CLI logging . Can be `'CLI'` or `'language-server'`, etc.
|
||||||
*/
|
*/
|
||||||
exports.goBuildFromGit = (version, destinationPath, taskName) => {
|
exports.goBuildFromGit = (version, destinationPath, taskName) => {
|
||||||
|
return buildFromGit('go', version, destinationPath, taskName);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `command` is either `go` or `task`.
|
||||||
|
*/
|
||||||
|
function buildFromGit(command, version, destinationPath, taskName) {
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const temp = require('temp');
|
const temp = require('temp');
|
||||||
@@ -62,7 +80,7 @@ exports.goBuildFromGit = (version, destinationPath, taskName) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shell.echo(`>>> Building the ${taskName}...`);
|
shell.echo(`>>> Building the ${taskName}...`);
|
||||||
if (shell.exec('go build', { cwd: tempRepoPath }).code !== 0) {
|
if (shell.exec(`${command} build`, { cwd: tempRepoPath }).code !== 0) {
|
||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
shell.echo(`<<< Done ${taskName} build.`);
|
shell.echo(`<<< Done ${taskName} build.`);
|
||||||
@@ -89,4 +107,4 @@ exports.goBuildFromGit = (version, destinationPath, taskName) => {
|
|||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
shell.echo(`>>> Verified ${taskName}.`);
|
shell.echo(`>>> Verified ${taskName}.`);
|
||||||
};
|
}
|
||||||
|
@@ -5,17 +5,14 @@ import {
|
|||||||
postConstruct,
|
postConstruct,
|
||||||
} from '@theia/core/shared/inversify';
|
} from '@theia/core/shared/inversify';
|
||||||
import * as React from '@theia/core/shared/react';
|
import * as React from '@theia/core/shared/react';
|
||||||
import { SketchesService } from '../common/protocol';
|
|
||||||
import {
|
import {
|
||||||
MAIN_MENU_BAR,
|
MAIN_MENU_BAR,
|
||||||
MenuContribution,
|
MenuContribution,
|
||||||
MenuModelRegistry,
|
MenuModelRegistry,
|
||||||
} from '@theia/core';
|
} from '@theia/core';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
|
||||||
FrontendApplication,
|
FrontendApplication,
|
||||||
FrontendApplicationContribution,
|
FrontendApplicationContribution,
|
||||||
OnWillStopAction,
|
|
||||||
} from '@theia/core/lib/browser';
|
} from '@theia/core/lib/browser';
|
||||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||||
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
||||||
@@ -34,14 +31,9 @@ import { EditorCommands, EditorMainMenu } from '@theia/editor/lib/browser';
|
|||||||
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
||||||
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
|
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||||
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
||||||
import {
|
|
||||||
CurrentSketch,
|
|
||||||
SketchesServiceClientImpl,
|
|
||||||
} from '../common/protocol/sketches-service-client-impl';
|
|
||||||
import { ArduinoPreferences } from './arduino-preferences';
|
import { ArduinoPreferences } from './arduino-preferences';
|
||||||
import { BoardsServiceProvider } from './boards/boards-service-provider';
|
import { BoardsServiceProvider } from './boards/boards-service-provider';
|
||||||
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
||||||
import { SaveAsSketch } from './contributions/save-as-sketch';
|
|
||||||
import { ArduinoMenus } from './menu/arduino-menus';
|
import { ArduinoMenus } from './menu/arduino-menus';
|
||||||
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
|
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
|
||||||
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
||||||
@@ -63,18 +55,12 @@ export class ArduinoFrontendContribution
|
|||||||
@inject(BoardsServiceProvider)
|
@inject(BoardsServiceProvider)
|
||||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
@inject(SketchesService)
|
|
||||||
private readonly sketchService: SketchesService;
|
|
||||||
|
|
||||||
@inject(CommandRegistry)
|
@inject(CommandRegistry)
|
||||||
private readonly commandRegistry: CommandRegistry;
|
private readonly commandRegistry: CommandRegistry;
|
||||||
|
|
||||||
@inject(ArduinoPreferences)
|
@inject(ArduinoPreferences)
|
||||||
private readonly arduinoPreferences: ArduinoPreferences;
|
private readonly arduinoPreferences: ArduinoPreferences;
|
||||||
|
|
||||||
@inject(SketchesServiceClientImpl)
|
|
||||||
private readonly sketchServiceClient: SketchesServiceClientImpl;
|
|
||||||
|
|
||||||
@inject(FrontendApplicationStateService)
|
@inject(FrontendApplicationStateService)
|
||||||
private readonly appStateService: FrontendApplicationStateService;
|
private readonly appStateService: FrontendApplicationStateService;
|
||||||
|
|
||||||
@@ -91,7 +77,7 @@ export class ArduinoFrontendContribution
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onStart(app: FrontendApplication): Promise<void> {
|
onStart(app: FrontendApplication): void {
|
||||||
this.arduinoPreferences.onPreferenceChanged((event) => {
|
this.arduinoPreferences.onPreferenceChanged((event) => {
|
||||||
if (event.newValue !== event.oldValue) {
|
if (event.newValue !== event.oldValue) {
|
||||||
switch (event.preferenceName) {
|
switch (event.preferenceName) {
|
||||||
@@ -104,7 +90,7 @@ export class ArduinoFrontendContribution
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.appStateService.reachedState('initialized_layout').then(() =>
|
this.appStateService.reachedState('ready').then(() =>
|
||||||
this.arduinoPreferences.ready.then(() => {
|
this.arduinoPreferences.ready.then(() => {
|
||||||
const webContents = remote.getCurrentWebContents();
|
const webContents = remote.getCurrentWebContents();
|
||||||
const zoomLevel = this.arduinoPreferences.get(
|
const zoomLevel = this.arduinoPreferences.get(
|
||||||
@@ -183,34 +169,6 @@ export class ArduinoFrontendContribution
|
|||||||
|
|
||||||
registerColors(colors: ColorRegistry): void {
|
registerColors(colors: ColorRegistry): void {
|
||||||
colors.register(
|
colors.register(
|
||||||
{
|
|
||||||
id: 'arduino.branding.primary',
|
|
||||||
defaults: {
|
|
||||||
dark: 'statusBar.background',
|
|
||||||
light: 'statusBar.background',
|
|
||||||
},
|
|
||||||
description:
|
|
||||||
'The primary branding color, such as dialog titles, library, and board manager list labels.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'arduino.branding.secondary',
|
|
||||||
defaults: {
|
|
||||||
dark: 'statusBar.background',
|
|
||||||
light: 'statusBar.background',
|
|
||||||
},
|
|
||||||
description:
|
|
||||||
'Secondary branding color for list selections, dropdowns, and widget borders.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'arduino.foreground',
|
|
||||||
defaults: {
|
|
||||||
dark: 'editorWidget.background',
|
|
||||||
light: 'editorWidget.background',
|
|
||||||
hc: 'editorWidget.background',
|
|
||||||
},
|
|
||||||
description:
|
|
||||||
'Color of the Arduino IDE foreground which is used for dialogs, such as the Select Board dialog.',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'arduino.toolbar.button.background',
|
id: 'arduino.toolbar.button.background',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -225,8 +183,8 @@ export class ArduinoFrontendContribution
|
|||||||
id: 'arduino.toolbar.button.hoverBackground',
|
id: 'arduino.toolbar.button.hoverBackground',
|
||||||
defaults: {
|
defaults: {
|
||||||
dark: 'button.hoverBackground',
|
dark: 'button.hoverBackground',
|
||||||
light: 'button.foreground',
|
light: 'button.hoverBackground',
|
||||||
hc: 'textLink.foreground',
|
hc: 'button.background',
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
'Background color of the toolbar items when hovering over them. Such as Upload, Verify, etc.',
|
'Background color of the toolbar items when hovering over them. Such as Upload, Verify, etc.',
|
||||||
@@ -261,24 +219,6 @@ export class ArduinoFrontendContribution
|
|||||||
description:
|
description:
|
||||||
'Toggle color of the toolbar items when they are currently toggled (the command is in progress)',
|
'Toggle color of the toolbar items when they are currently toggled (the command is in progress)',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'arduino.output.foreground',
|
|
||||||
defaults: {
|
|
||||||
dark: 'editor.foreground',
|
|
||||||
light: 'editor.foreground',
|
|
||||||
hc: 'editor.foreground',
|
|
||||||
},
|
|
||||||
description: 'Color of the text in the Output view.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'arduino.output.background',
|
|
||||||
defaults: {
|
|
||||||
dark: 'editor.background',
|
|
||||||
light: 'editor.background',
|
|
||||||
hc: 'editor.background',
|
|
||||||
},
|
|
||||||
description: 'Background color of the Output view.',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'arduino.toolbar.dropdown.border',
|
id: 'arduino.toolbar.dropdown.border',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -303,8 +243,8 @@ export class ArduinoFrontendContribution
|
|||||||
id: 'arduino.toolbar.dropdown.background',
|
id: 'arduino.toolbar.dropdown.background',
|
||||||
defaults: {
|
defaults: {
|
||||||
dark: 'tab.unfocusedActiveBackground',
|
dark: 'tab.unfocusedActiveBackground',
|
||||||
light: 'tab.unfocusedActiveBackground',
|
light: 'dropdown.background',
|
||||||
hc: 'tab.unfocusedActiveBackground',
|
hc: 'dropdown.background',
|
||||||
},
|
},
|
||||||
description: 'Background color of the Board Selector.',
|
description: 'Background color of the Board Selector.',
|
||||||
},
|
},
|
||||||
@@ -312,18 +252,18 @@ export class ArduinoFrontendContribution
|
|||||||
{
|
{
|
||||||
id: 'arduino.toolbar.dropdown.label',
|
id: 'arduino.toolbar.dropdown.label',
|
||||||
defaults: {
|
defaults: {
|
||||||
dark: 'foreground',
|
dark: 'dropdown.foreground',
|
||||||
light: 'foreground',
|
light: 'dropdown.foreground',
|
||||||
hc: 'foreground',
|
hc: 'dropdown.foreground',
|
||||||
},
|
},
|
||||||
description: 'Font color of the Board Selector.',
|
description: 'Font color of the Board Selector.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'arduino.toolbar.dropdown.iconSelected',
|
id: 'arduino.toolbar.dropdown.iconSelected',
|
||||||
defaults: {
|
defaults: {
|
||||||
dark: 'statusBar.background',
|
dark: 'list.activeSelectionIconForeground',
|
||||||
light: 'statusBar.background',
|
light: 'list.activeSelectionIconForeground',
|
||||||
hc: 'statusBar.background',
|
hc: 'list.activeSelectionIconForeground',
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
'Color of the selected protocol icon in the Board Selector.',
|
'Color of the selected protocol icon in the Board Selector.',
|
||||||
@@ -331,76 +271,22 @@ export class ArduinoFrontendContribution
|
|||||||
{
|
{
|
||||||
id: 'arduino.toolbar.dropdown.option.backgroundHover',
|
id: 'arduino.toolbar.dropdown.option.backgroundHover',
|
||||||
defaults: {
|
defaults: {
|
||||||
dark: 'editor.background',
|
dark: 'list.hoverBackground',
|
||||||
light: 'editor.background',
|
light: 'list.hoverBackground',
|
||||||
hc: 'editor.background',
|
hc: 'list.hoverBackground',
|
||||||
},
|
},
|
||||||
description: 'Background color on hover of the Board Selector options.',
|
description: 'Background color on hover of the Board Selector options.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'arduino.toolbar.dropdown.option.backgroundSelected',
|
id: 'arduino.toolbar.dropdown.option.backgroundSelected',
|
||||||
defaults: {
|
defaults: {
|
||||||
dark: 'editor.background',
|
dark: 'list.activeSelectionBackground',
|
||||||
light: 'editor.background',
|
light: 'list.activeSelectionBackground',
|
||||||
hc: 'editor.background',
|
hc: 'list.activeSelectionBackground',
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
'Background color of the selected board in the Board Selector.',
|
'Background color of the selected board in the Board Selector.',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: should be handled by `Close` contribution. https://github.com/arduino/arduino-ide/issues/1016
|
|
||||||
onWillStop(): OnWillStopAction {
|
|
||||||
return {
|
|
||||||
reason: 'temp-sketch',
|
|
||||||
action: () => {
|
|
||||||
return this.showTempSketchDialog();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async showTempSketchDialog(): Promise<boolean> {
|
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
|
||||||
if (!CurrentSketch.isValid(sketch)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const isTemp = await this.sketchService.isTemp(sketch);
|
|
||||||
if (!isTemp) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const messageBoxResult = await remote.dialog.showMessageBox(
|
|
||||||
remote.getCurrentWindow(),
|
|
||||||
{
|
|
||||||
message: nls.localize(
|
|
||||||
'arduino/sketch/saveTempSketch',
|
|
||||||
'Save your sketch to open it again later.'
|
|
||||||
),
|
|
||||||
title: nls.localize(
|
|
||||||
'theia/core/quitTitle',
|
|
||||||
'Are you sure you want to quit?'
|
|
||||||
),
|
|
||||||
type: 'question',
|
|
||||||
buttons: [
|
|
||||||
Dialog.CANCEL,
|
|
||||||
nls.localizeByDefault('Save As...'),
|
|
||||||
nls.localizeByDefault("Don't Save"),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const result = messageBoxResult.response;
|
|
||||||
if (result === 2) {
|
|
||||||
return true;
|
|
||||||
} else if (result === 1) {
|
|
||||||
return !!(await this.commandRegistry.executeCommand(
|
|
||||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
|
||||||
{
|
|
||||||
execOnlyIfTemp: false,
|
|
||||||
openAfterMove: false,
|
|
||||||
wipeOriginal: true,
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -50,13 +50,17 @@ import {
|
|||||||
ApplicationShell as TheiaApplicationShell,
|
ApplicationShell as TheiaApplicationShell,
|
||||||
ShellLayoutRestorer as TheiaShellLayoutRestorer,
|
ShellLayoutRestorer as TheiaShellLayoutRestorer,
|
||||||
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
||||||
|
DockPanelRenderer as TheiaDockPanelRenderer,
|
||||||
TabBarRendererFactory,
|
TabBarRendererFactory,
|
||||||
ContextMenuRenderer,
|
ContextMenuRenderer,
|
||||||
createTreeContainer,
|
createTreeContainer,
|
||||||
TreeWidget,
|
TreeWidget,
|
||||||
} from '@theia/core/lib/browser';
|
} from '@theia/core/lib/browser';
|
||||||
import { MenuContribution } from '@theia/core/lib/common/menu';
|
import { MenuContribution } from '@theia/core/lib/common/menu';
|
||||||
import { ApplicationShell } from './theia/core/application-shell';
|
import {
|
||||||
|
ApplicationShell,
|
||||||
|
DockPanelRenderer,
|
||||||
|
} from './theia/core/application-shell';
|
||||||
import { FrontendApplication } from './theia/core/frontend-application';
|
import { FrontendApplication } from './theia/core/frontend-application';
|
||||||
import {
|
import {
|
||||||
BoardsConfigDialog,
|
BoardsConfigDialog,
|
||||||
@@ -82,7 +86,10 @@ import { BoardsAutoInstaller } from './boards/boards-auto-installer';
|
|||||||
import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer';
|
import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer';
|
||||||
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
|
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
|
||||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||||
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
import {
|
||||||
|
MonacoThemeJson,
|
||||||
|
MonacoThemingService,
|
||||||
|
} from '@theia/monaco/lib/browser/monaco-theming-service';
|
||||||
import {
|
import {
|
||||||
ArduinoDaemonPath,
|
ArduinoDaemonPath,
|
||||||
ArduinoDaemon,
|
ArduinoDaemon,
|
||||||
@@ -134,8 +141,6 @@ import { WorkspaceDeleteHandler } from './theia/workspace/workspace-delete-handl
|
|||||||
import { TabBarToolbar } from './theia/core/tab-bar-toolbar';
|
import { TabBarToolbar } from './theia/core/tab-bar-toolbar';
|
||||||
import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
|
import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
|
||||||
import { EditorWidgetFactory } from './theia/editor/editor-widget-factory';
|
import { EditorWidgetFactory } from './theia/editor/editor-widget-factory';
|
||||||
import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/output-widget';
|
|
||||||
import { OutputWidget } from './theia/output/output-widget';
|
|
||||||
import { BurnBootloader } from './contributions/burn-bootloader';
|
import { BurnBootloader } from './contributions/burn-bootloader';
|
||||||
import {
|
import {
|
||||||
ExamplesServicePath,
|
ExamplesServicePath,
|
||||||
@@ -208,7 +213,10 @@ import { SearchInWorkspaceFactory } from './theia/search-in-workspace/search-in-
|
|||||||
import { SearchInWorkspaceResultTreeWidget as TheiaSearchInWorkspaceResultTreeWidget } from '@theia/search-in-workspace/lib/browser/search-in-workspace-result-tree-widget';
|
import { SearchInWorkspaceResultTreeWidget as TheiaSearchInWorkspaceResultTreeWidget } from '@theia/search-in-workspace/lib/browser/search-in-workspace-result-tree-widget';
|
||||||
import { SearchInWorkspaceResultTreeWidget } from './theia/search-in-workspace/search-in-workspace-result-tree-widget';
|
import { SearchInWorkspaceResultTreeWidget } from './theia/search-in-workspace/search-in-workspace-result-tree-widget';
|
||||||
import { MonacoEditorProvider } from './theia/monaco/monaco-editor-provider';
|
import { MonacoEditorProvider } from './theia/monaco/monaco-editor-provider';
|
||||||
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
import {
|
||||||
|
MonacoEditorFactory,
|
||||||
|
MonacoEditorProvider as TheiaMonacoEditorProvider,
|
||||||
|
} from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||||
import { StorageWrapper } from './storage-wrapper';
|
import { StorageWrapper } from './storage-wrapper';
|
||||||
import { NotificationManager } from './theia/messages/notifications-manager';
|
import { NotificationManager } from './theia/messages/notifications-manager';
|
||||||
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||||
@@ -307,23 +315,51 @@ import { FirstStartupInstaller } from './contributions/first-startup-installer';
|
|||||||
import { OpenSketchFiles } from './contributions/open-sketch-files';
|
import { OpenSketchFiles } from './contributions/open-sketch-files';
|
||||||
import { InoLanguage } from './contributions/ino-language';
|
import { InoLanguage } from './contributions/ino-language';
|
||||||
import { SelectedBoard } from './contributions/selected-board';
|
import { SelectedBoard } from './contributions/selected-board';
|
||||||
import { CheckForUpdates } from './contributions/check-for-updates';
|
import { CheckForIDEUpdates } from './contributions/check-for-ide-updates';
|
||||||
import { OpenBoardsConfig } from './contributions/open-boards-config';
|
import { OpenBoardsConfig } from './contributions/open-boards-config';
|
||||||
import { SketchFilesTracker } from './contributions/sketch-files-tracker';
|
import { SketchFilesTracker } from './contributions/sketch-files-tracker';
|
||||||
|
import { MonacoThemeServiceIsReady } from './utils/window';
|
||||||
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||||
|
import { StatusBarImpl } from './theia/core/status-bar';
|
||||||
|
import { StatusBarImpl as TheiaStatusBarImpl } from '@theia/core/lib/browser';
|
||||||
|
import { EditorMenuContribution } from './theia/editor/editor-file';
|
||||||
|
import { EditorMenuContribution as TheiaEditorMenuContribution } from '@theia/editor/lib/browser/editor-menu';
|
||||||
|
import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/preferences/lib/browser/views/preference-editor-widget';
|
||||||
|
import { PreferencesEditorWidget } from './theia/preferences/preference-editor-widget';
|
||||||
|
import { PreferencesWidget } from '@theia/preferences/lib/browser/views/preference-widget';
|
||||||
|
import { createPreferencesWidgetContainer } from '@theia/preferences/lib/browser/views/preference-widget-bindings';
|
||||||
|
import {
|
||||||
|
BoardsFilterRenderer,
|
||||||
|
LibraryFilterRenderer,
|
||||||
|
} from './widgets/component-list/filter-renderer';
|
||||||
|
import { CheckForUpdates } from './contributions/check-for-updates';
|
||||||
|
import { OutputEditorFactory } from './theia/output/output-editor-factory';
|
||||||
|
|
||||||
MonacoThemingService.register({
|
const registerArduinoThemes = () => {
|
||||||
|
const themes: MonacoThemeJson[] = [
|
||||||
|
{
|
||||||
id: 'arduino-theme',
|
id: 'arduino-theme',
|
||||||
label: 'Light (Arduino)',
|
label: 'Light (Arduino)',
|
||||||
uiTheme: 'vs',
|
uiTheme: 'vs',
|
||||||
json: require('../../src/browser/data/default.color-theme.json'),
|
json: require('../../src/browser/data/default.color-theme.json'),
|
||||||
});
|
},
|
||||||
|
{
|
||||||
MonacoThemingService.register({
|
|
||||||
id: 'arduino-theme-dark',
|
id: 'arduino-theme-dark',
|
||||||
label: 'Dark (Arduino)',
|
label: 'Dark (Arduino)',
|
||||||
uiTheme: 'vs-dark',
|
uiTheme: 'vs-dark',
|
||||||
json: require('../../src/browser/data/dark.color-theme.json'),
|
json: require('../../src/browser/data/dark.color-theme.json'),
|
||||||
});
|
},
|
||||||
|
];
|
||||||
|
themes.forEach((theme) => MonacoThemingService.register(theme));
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const global = window as any;
|
||||||
|
const ready = global[MonacoThemeServiceIsReady] as Deferred;
|
||||||
|
if (ready) {
|
||||||
|
ready.promise.then(registerArduinoThemes);
|
||||||
|
} else {
|
||||||
|
registerArduinoThemes();
|
||||||
|
}
|
||||||
|
|
||||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||||
// Commands and toolbar items
|
// Commands and toolbar items
|
||||||
@@ -339,6 +375,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
|
|
||||||
// Renderer for both the library and the core widgets.
|
// Renderer for both the library and the core widgets.
|
||||||
bind(ListItemRenderer).toSelf().inSingletonScope();
|
bind(ListItemRenderer).toSelf().inSingletonScope();
|
||||||
|
bind(LibraryFilterRenderer).toSelf().inSingletonScope();
|
||||||
|
bind(BoardsFilterRenderer).toSelf().inSingletonScope();
|
||||||
|
|
||||||
// Library service
|
// Library service
|
||||||
bind(LibraryService)
|
bind(LibraryService)
|
||||||
@@ -430,7 +468,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();
|
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();
|
||||||
bind(BoardsConfigDialog).toSelf().inSingletonScope();
|
bind(BoardsConfigDialog).toSelf().inSingletonScope();
|
||||||
bind(BoardsConfigDialogProps).toConstantValue({
|
bind(BoardsConfigDialogProps).toConstantValue({
|
||||||
title: nls.localize('arduino/common/selectBoard', 'Select Board'),
|
title: nls.localize(
|
||||||
|
'arduino/board/boardConfigDialogTitle',
|
||||||
|
'Select Other Board and Port'
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Core service
|
// Core service
|
||||||
@@ -548,8 +589,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
return container.get(TabBarToolbar);
|
return container.get(TabBarToolbar);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
bind(OutputWidget).toSelf().inSingletonScope();
|
|
||||||
rebind(TheiaOutputWidget).toService(OutputWidget);
|
|
||||||
bind(OutputChannelManager).toSelf().inSingletonScope();
|
bind(OutputChannelManager).toSelf().inSingletonScope();
|
||||||
rebind(TheiaOutputChannelManager).toService(OutputChannelManager);
|
rebind(TheiaOutputChannelManager).toService(OutputChannelManager);
|
||||||
bind(OutputChannelRegistryMainImpl).toSelf().inTransientScope();
|
bind(OutputChannelRegistryMainImpl).toSelf().inTransientScope();
|
||||||
@@ -566,7 +605,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
|
|
||||||
// Disabled reference counter in the editor manager to avoid opening the same editor (with different opener options) multiple times.
|
// Disabled reference counter in the editor manager to avoid opening the same editor (with different opener options) multiple times.
|
||||||
bind(EditorManager).toSelf().inSingletonScope();
|
bind(EditorManager).toSelf().inSingletonScope();
|
||||||
rebind(TheiaEditorManager).to(EditorManager);
|
rebind(TheiaEditorManager).toService(EditorManager);
|
||||||
|
|
||||||
// replace search icon
|
// replace search icon
|
||||||
rebind(TheiaSearchInWorkspaceFactory)
|
rebind(TheiaSearchInWorkspaceFactory)
|
||||||
@@ -614,6 +653,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
bind(WindowContribution).toSelf().inSingletonScope();
|
bind(WindowContribution).toSelf().inSingletonScope();
|
||||||
rebind(TheiaWindowContribution).toService(WindowContribution);
|
rebind(TheiaWindowContribution).toService(WindowContribution);
|
||||||
|
|
||||||
|
// To remove `File` > `Close Editor`.
|
||||||
|
bind(EditorMenuContribution).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaEditorMenuContribution).toService(EditorMenuContribution);
|
||||||
|
|
||||||
|
// To disable the highlighting of non-unicode characters in the _Output_ view
|
||||||
|
bind(OutputEditorFactory).toSelf().inSingletonScope();
|
||||||
|
// Rebind to `TheiaOutputEditorFactory` when https://github.com/eclipse-theia/theia/pull/11615 is available.
|
||||||
|
rebind(MonacoEditorFactory).toService(OutputEditorFactory);
|
||||||
|
|
||||||
bind(ArduinoDaemon)
|
bind(ArduinoDaemon)
|
||||||
.toDynamicValue((context) =>
|
.toDynamicValue((context) =>
|
||||||
WebSocketConnectionProvider.createProxy(
|
WebSocketConnectionProvider.createProxy(
|
||||||
@@ -705,9 +753,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
Contribution.configure(bind, OpenSketchFiles);
|
Contribution.configure(bind, OpenSketchFiles);
|
||||||
Contribution.configure(bind, InoLanguage);
|
Contribution.configure(bind, InoLanguage);
|
||||||
Contribution.configure(bind, SelectedBoard);
|
Contribution.configure(bind, SelectedBoard);
|
||||||
Contribution.configure(bind, CheckForUpdates);
|
Contribution.configure(bind, CheckForIDEUpdates);
|
||||||
Contribution.configure(bind, OpenBoardsConfig);
|
Contribution.configure(bind, OpenBoardsConfig);
|
||||||
Contribution.configure(bind, SketchFilesTracker);
|
Contribution.configure(bind, SketchFilesTracker);
|
||||||
|
Contribution.configure(bind, CheckForUpdates);
|
||||||
|
|
||||||
// Disabled the quick-pick customization from Theia when multiple formatters are available.
|
// Disabled the quick-pick customization from Theia when multiple formatters are available.
|
||||||
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
|
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
|
||||||
@@ -805,6 +854,26 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
bind(WidgetManager).toSelf().inSingletonScope();
|
bind(WidgetManager).toSelf().inSingletonScope();
|
||||||
rebind(TheiaWidgetManager).toService(WidgetManager);
|
rebind(TheiaWidgetManager).toService(WidgetManager);
|
||||||
|
|
||||||
|
// To avoid running a status bar update on every single `keypress` event from the editor.
|
||||||
|
bind(StatusBarImpl).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaStatusBarImpl).toService(StatusBarImpl);
|
||||||
|
|
||||||
|
// Debounced update for the tab-bar toolbar when typing in the editor.
|
||||||
|
bind(DockPanelRenderer).toSelf();
|
||||||
|
rebind(TheiaDockPanelRenderer).toService(DockPanelRenderer);
|
||||||
|
|
||||||
|
// Avoid running the "reset scroll" interval tasks until the preference editor opens.
|
||||||
|
rebind(PreferencesWidget)
|
||||||
|
.toDynamicValue(({ container }) => {
|
||||||
|
const child = createPreferencesWidgetContainer(container);
|
||||||
|
child.bind(PreferencesEditorWidget).toSelf().inSingletonScope();
|
||||||
|
child
|
||||||
|
.rebind(TheiaPreferencesEditorWidget)
|
||||||
|
.toService(PreferencesEditorWidget);
|
||||||
|
return child.get(PreferencesWidget);
|
||||||
|
})
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
bindArduinoPreferences(bind);
|
bindArduinoPreferences(bind);
|
||||||
|
|
||||||
|
@@ -241,6 +241,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
|
|||||||
),
|
),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
'arduino.checkForUpdates': {
|
||||||
|
type: 'boolean',
|
||||||
|
description: nls.localize(
|
||||||
|
'arduino/preferences/checkForUpdate',
|
||||||
|
"Receive notifications of available updates for the IDE, boards, and libraries. Requires an IDE restart after change. It's true by default."
|
||||||
|
),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -270,6 +278,7 @@ export interface ArduinoConfiguration {
|
|||||||
'arduino.auth.registerUri': string;
|
'arduino.auth.registerUri': string;
|
||||||
'arduino.survey.notification': boolean;
|
'arduino.survey.notification': boolean;
|
||||||
'arduino.cli.daemon.debug': boolean;
|
'arduino.cli.daemon.debug': boolean;
|
||||||
|
'arduino.checkForUpdates': boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ArduinoPreferences = Symbol('ArduinoPreferences');
|
export const ArduinoPreferences = Symbol('ArduinoPreferences');
|
||||||
|
@@ -12,6 +12,7 @@ import { Installable, ResponseServiceClient } from '../../common/protocol';
|
|||||||
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
import { NotificationCenter } from '../notification-center';
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
import { InstallManually } from '../../common/nls';
|
||||||
|
|
||||||
interface AutoInstallPromptAction {
|
interface AutoInstallPromptAction {
|
||||||
// isAcceptance, whether or not the action indicates acceptance of auto-install proposal
|
// isAcceptance, whether or not the action indicates acceptance of auto-install proposal
|
||||||
@@ -231,12 +232,21 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
|||||||
candidate: BoardsPackage
|
candidate: BoardsPackage
|
||||||
): AutoInstallPromptActions {
|
): AutoInstallPromptActions {
|
||||||
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
|
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
|
||||||
const manualInstall = nls.localize(
|
|
||||||
'arduino/board/installManually',
|
|
||||||
'Install Manually'
|
|
||||||
);
|
|
||||||
|
|
||||||
const actions: AutoInstallPromptActions = [
|
const actions: AutoInstallPromptActions = [
|
||||||
|
{
|
||||||
|
key: InstallManually,
|
||||||
|
handler: () => {
|
||||||
|
this.boardsManagerFrontendContribution
|
||||||
|
.openView({ reveal: true })
|
||||||
|
.then((widget) =>
|
||||||
|
widget.refresh({
|
||||||
|
query: candidate.name.toLocaleLowerCase(),
|
||||||
|
type: 'All',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
isAcceptance: true,
|
isAcceptance: true,
|
||||||
key: yes,
|
key: yes,
|
||||||
@@ -250,16 +260,6 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: manualInstall,
|
|
||||||
handler: () => {
|
|
||||||
this.boardsManagerFrontendContribution
|
|
||||||
.openView({ reveal: true })
|
|
||||||
.then((widget) =>
|
|
||||||
widget.refresh(candidate.name.toLocaleLowerCase())
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
import {
|
||||||
|
injectable,
|
||||||
|
inject,
|
||||||
|
postConstruct,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||||
import { DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
|
import { DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
|
||||||
import { AbstractDialog } from '../theia/dialogs/dialogs';
|
import { AbstractDialog } from '../theia/dialogs/dialogs';
|
||||||
@@ -28,7 +32,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
|||||||
@inject(BoardsConfigDialogProps)
|
@inject(BoardsConfigDialogProps)
|
||||||
protected override readonly props: BoardsConfigDialogProps
|
protected override readonly props: BoardsConfigDialogProps
|
||||||
) {
|
) {
|
||||||
super(props);
|
super({ ...props, maxWidth: 500 });
|
||||||
|
|
||||||
this.contentNode.classList.add('select-board-dialog');
|
this.contentNode.classList.add('select-board-dialog');
|
||||||
this.contentNode.appendChild(this.createDescription());
|
this.contentNode.appendChild(this.createDescription());
|
||||||
@@ -65,14 +69,6 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
|||||||
const head = document.createElement('div');
|
const head = document.createElement('div');
|
||||||
head.classList.add('head');
|
head.classList.add('head');
|
||||||
|
|
||||||
const title = document.createElement('div');
|
|
||||||
title.textContent = nls.localize(
|
|
||||||
'arduino/board/configDialogTitle',
|
|
||||||
'Select Other Board & Port'
|
|
||||||
);
|
|
||||||
title.classList.add('title');
|
|
||||||
head.appendChild(title);
|
|
||||||
|
|
||||||
const text = document.createElement('div');
|
const text = document.createElement('div');
|
||||||
text.classList.add('text');
|
text.classList.add('text');
|
||||||
head.appendChild(text);
|
head.appendChild(text);
|
||||||
|
@@ -258,14 +258,14 @@ export class BoardsConfig extends React.Component<
|
|||||||
|
|
||||||
override render(): React.ReactNode {
|
override render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="body">
|
<>
|
||||||
{this.renderContainer('boards', this.renderBoards.bind(this))}
|
{this.renderContainer('boards', this.renderBoards.bind(this))}
|
||||||
{this.renderContainer(
|
{this.renderContainer(
|
||||||
'ports',
|
'ports',
|
||||||
this.renderPorts.bind(this),
|
this.renderPorts.bind(this),
|
||||||
this.renderPortsFooter.bind(this)
|
this.renderPortsFooter.bind(this)
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +306,10 @@ export class BoardsConfig extends React.Component<
|
|||||||
type="search"
|
type="search"
|
||||||
value={query}
|
value={query}
|
||||||
className="theia-input"
|
className="theia-input"
|
||||||
placeholder="SEARCH BOARD"
|
placeholder={nls.localize(
|
||||||
|
'arduino/board/searchBoard',
|
||||||
|
'Search board'
|
||||||
|
)}
|
||||||
onChange={this.updateBoards}
|
onChange={this.updateBoards}
|
||||||
ref={this.focusNodeSet}
|
ref={this.focusNodeSet}
|
||||||
/>
|
/>
|
||||||
@@ -334,27 +337,19 @@ export class BoardsConfig extends React.Component<
|
|||||||
if (this.state.showAllPorts) {
|
if (this.state.showAllPorts) {
|
||||||
ports = this.state.knownPorts;
|
ports = this.state.knownPorts;
|
||||||
} else {
|
} else {
|
||||||
ports = this.state.knownPorts.filter((port) => {
|
ports = this.state.knownPorts.filter(
|
||||||
if (port.protocol === 'serial') {
|
Port.visiblePorts(this.availableBoards)
|
||||||
return true;
|
);
|
||||||
}
|
|
||||||
// All other ports with different protocol are
|
|
||||||
// only shown if there is a recognized board
|
|
||||||
// connected
|
|
||||||
for (const board of this.availableBoards) {
|
|
||||||
if (board.port?.address === port.address) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return !ports.length ? (
|
return !ports.length ? (
|
||||||
<div className="loading noselect">No ports discovered</div>
|
<div className="loading noselect">
|
||||||
|
{nls.localize('arduino/board/noPortsDiscovered', 'No ports discovered')}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="ports list">
|
<div className="ports list">
|
||||||
{ports.map((port) => (
|
{ports.map((port) => (
|
||||||
<Item<Port>
|
<Item<Port>
|
||||||
key={`${port.id}`}
|
key={`${Port.keyOf(port)}`}
|
||||||
item={port}
|
item={port}
|
||||||
label={Port.toString(port)}
|
label={Port.toString(port)}
|
||||||
selected={Port.sameAs(this.state.selectedPort, port)}
|
selected={Port.sameAs(this.state.selectedPort, port)}
|
||||||
|
@@ -4,22 +4,24 @@ import {
|
|||||||
postConstruct,
|
postConstruct,
|
||||||
} from '@theia/core/shared/inversify';
|
} from '@theia/core/shared/inversify';
|
||||||
import {
|
import {
|
||||||
|
BoardSearch,
|
||||||
BoardsPackage,
|
BoardsPackage,
|
||||||
BoardsService,
|
BoardsService,
|
||||||
} from '../../common/protocol/boards-service';
|
} from '../../common/protocol/boards-service';
|
||||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
import { BoardsFilterRenderer } from '../widgets/component-list/filter-renderer';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
export class BoardsListWidget extends ListWidget<BoardsPackage, BoardSearch> {
|
||||||
static WIDGET_ID = 'boards-list-widget';
|
static WIDGET_ID = 'boards-list-widget';
|
||||||
static WIDGET_LABEL = nls.localize('arduino/boardsManager', 'Boards Manager');
|
static WIDGET_LABEL = nls.localize('arduino/boardsManager', 'Boards Manager');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(BoardsService) protected service: BoardsService,
|
@inject(BoardsService) service: BoardsService,
|
||||||
@inject(ListItemRenderer)
|
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<BoardsPackage>,
|
||||||
protected itemRenderer: ListItemRenderer<BoardsPackage>
|
@inject(BoardsFilterRenderer) filterRenderer: BoardsFilterRenderer
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
id: BoardsListWidget.WIDGET_ID,
|
id: BoardsListWidget.WIDGET_ID,
|
||||||
@@ -30,6 +32,8 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
|||||||
itemLabel: (item: BoardsPackage) => item.name,
|
itemLabel: (item: BoardsPackage) => item.name,
|
||||||
itemDeprecated: (item: BoardsPackage) => item.deprecated,
|
itemDeprecated: (item: BoardsPackage) => item.deprecated,
|
||||||
itemRenderer,
|
itemRenderer,
|
||||||
|
filterRenderer,
|
||||||
|
defaultSearchOptions: { query: '', type: 'All' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@ import {
|
|||||||
AttachedBoardsChangeEvent,
|
AttachedBoardsChangeEvent,
|
||||||
BoardWithPackage,
|
BoardWithPackage,
|
||||||
BoardUserField,
|
BoardUserField,
|
||||||
|
AvailablePorts,
|
||||||
} from '../../common/protocol';
|
} from '../../common/protocol';
|
||||||
import { BoardsConfig } from './boards-config';
|
import { BoardsConfig } from './boards-config';
|
||||||
import { naturalCompare } from '../../common/utils';
|
import { naturalCompare } from '../../common/utils';
|
||||||
@@ -21,6 +22,7 @@ import { StorageWrapper } from '../storage-wrapper';
|
|||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
|
import { Unknown } from '../../common/nls';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsServiceProvider implements FrontendApplicationContribution {
|
export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||||
@@ -65,11 +67,16 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
protected _availablePorts: Port[] = [];
|
protected _availablePorts: Port[] = [];
|
||||||
protected _availableBoards: AvailableBoard[] = [];
|
protected _availableBoards: AvailableBoard[] = [];
|
||||||
|
|
||||||
|
private lastBoardsConfigOnUpload: BoardsConfig.Config | undefined;
|
||||||
|
private lastAvailablePortsOnUpload: Port[] | undefined;
|
||||||
|
private boardConfigToAutoSelect: BoardsConfig.Config | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlike `onAttachedBoardsChanged` this even fires when the user modifies the selected board in the IDE.\
|
* Unlike `onAttachedBoardsChanged` this event fires when the user modifies the selected board in the IDE.\
|
||||||
* This even also fires, when the boards package was not available for the currently selected board,
|
* This event also fires, when the boards package was not available for the currently selected board,
|
||||||
* and the user installs the board package. Note: installing a board package will set the `fqbn` of the
|
* and the user installs the board package. Note: installing a board package will set the `fqbn` of the
|
||||||
* currently selected board.\
|
* currently selected board.
|
||||||
|
*
|
||||||
* This event is also emitted when the board package for the currently selected board was uninstalled.
|
* This event is also emitted when the board package for the currently selected board was uninstalled.
|
||||||
*/
|
*/
|
||||||
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
|
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
|
||||||
@@ -91,11 +98,12 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.appStateService.reachedState('ready').then(async () => {
|
this.appStateService.reachedState('ready').then(async () => {
|
||||||
const [attachedBoards, availablePorts] = await Promise.all([
|
const [state] = await Promise.all([
|
||||||
this.boardsService.getAttachedBoards(),
|
this.boardsService.getState(),
|
||||||
this.boardsService.getAvailablePorts(),
|
|
||||||
this.loadState(),
|
this.loadState(),
|
||||||
]);
|
]);
|
||||||
|
const { boards: attachedBoards, ports: availablePorts } =
|
||||||
|
AvailablePorts.split(state);
|
||||||
this._attachedBoards = attachedBoards;
|
this._attachedBoards = attachedBoards;
|
||||||
this._availablePorts = availablePorts;
|
this._availablePorts = availablePorts;
|
||||||
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
|
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
|
||||||
@@ -111,6 +119,84 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
return this._reconciled.promise;
|
return this._reconciled.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
snapshotBoardDiscoveryOnUpload(): void {
|
||||||
|
this.lastBoardsConfigOnUpload = this._boardsConfig;
|
||||||
|
this.lastAvailablePortsOnUpload = this._availablePorts;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearBoardDiscoverySnapshot(): void {
|
||||||
|
this.lastBoardsConfigOnUpload = undefined;
|
||||||
|
this.lastAvailablePortsOnUpload = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private portToAutoSelectCanBeDerived(): boolean {
|
||||||
|
return Boolean(
|
||||||
|
this.lastBoardsConfigOnUpload && this.lastAvailablePortsOnUpload
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
attemptPostUploadAutoSelect(): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.portToAutoSelectCanBeDerived()) {
|
||||||
|
this.attemptAutoSelect({
|
||||||
|
ports: this._availablePorts,
|
||||||
|
boards: this._availableBoards,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 2000); // 2 second delay same as IDE 1.8
|
||||||
|
}
|
||||||
|
|
||||||
|
private attemptAutoSelect(
|
||||||
|
newState: AttachedBoardsChangeEvent['newState']
|
||||||
|
): void {
|
||||||
|
this.deriveBoardConfigToAutoSelect(newState);
|
||||||
|
this.tryReconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private deriveBoardConfigToAutoSelect(
|
||||||
|
newState: AttachedBoardsChangeEvent['newState']
|
||||||
|
): void {
|
||||||
|
if (!this.portToAutoSelectCanBeDerived()) {
|
||||||
|
this.boardConfigToAutoSelect = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldPorts = this.lastAvailablePortsOnUpload!;
|
||||||
|
const { ports: newPorts, boards: newBoards } = newState;
|
||||||
|
|
||||||
|
const appearedPorts =
|
||||||
|
oldPorts.length > 0
|
||||||
|
? newPorts.filter((newPort: Port) =>
|
||||||
|
oldPorts.every((oldPort: Port) => !Port.sameAs(newPort, oldPort))
|
||||||
|
)
|
||||||
|
: newPorts;
|
||||||
|
|
||||||
|
for (const port of appearedPorts) {
|
||||||
|
const boardOnAppearedPort = newBoards.find((board: Board) =>
|
||||||
|
Port.sameAs(board.port, port)
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastBoardsConfigOnUpload = this.lastBoardsConfigOnUpload!;
|
||||||
|
|
||||||
|
if (
|
||||||
|
boardOnAppearedPort &&
|
||||||
|
lastBoardsConfigOnUpload.selectedBoard &&
|
||||||
|
Board.sameAs(
|
||||||
|
boardOnAppearedPort,
|
||||||
|
lastBoardsConfigOnUpload.selectedBoard
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.clearBoardDiscoverySnapshot();
|
||||||
|
|
||||||
|
this.boardConfigToAutoSelect = {
|
||||||
|
selectedBoard: boardOnAppearedPort,
|
||||||
|
selectedPort: port,
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected notifyAttachedBoardsChanged(
|
protected notifyAttachedBoardsChanged(
|
||||||
event: AttachedBoardsChangeEvent
|
event: AttachedBoardsChangeEvent
|
||||||
): void {
|
): void {
|
||||||
@@ -119,10 +205,18 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
this.logger.info(AttachedBoardsChangeEvent.toString(event));
|
this.logger.info(AttachedBoardsChangeEvent.toString(event));
|
||||||
this.logger.info('------------------------------------------');
|
this.logger.info('------------------------------------------');
|
||||||
}
|
}
|
||||||
|
|
||||||
this._attachedBoards = event.newState.boards;
|
this._attachedBoards = event.newState.boards;
|
||||||
this._availablePorts = event.newState.ports;
|
this._availablePorts = event.newState.ports;
|
||||||
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
|
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
|
||||||
this.reconcileAvailableBoards().then(() => this.tryReconnect());
|
this.reconcileAvailableBoards().then(() => {
|
||||||
|
const { uploadInProgress } = event;
|
||||||
|
// avoid attempting "auto-selection" while an
|
||||||
|
// upload is in progress
|
||||||
|
if (!uploadInProgress) {
|
||||||
|
this.attemptAutoSelect(event.newState);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected notifyPlatformInstalled(event: { item: BoardsPackage }): void {
|
protected notifyPlatformInstalled(event: { item: BoardsPackage }): void {
|
||||||
@@ -238,25 +332,13 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we could not find an exact match, we compare the board FQBN-name pairs and ignore the port, as it might have changed.
|
|
||||||
// See documentation on `latestValidBoardsConfig`.
|
if (!this.boardConfigToAutoSelect) return false;
|
||||||
for (const board of this.availableBoards.filter(
|
|
||||||
({ state }) => state !== AvailableBoard.State.incomplete
|
this.boardsConfig = this.boardConfigToAutoSelect;
|
||||||
)) {
|
this.boardConfigToAutoSelect = undefined;
|
||||||
if (
|
|
||||||
this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn &&
|
|
||||||
this.latestValidBoardsConfig.selectedBoard.name === board.name &&
|
|
||||||
this.latestValidBoardsConfig.selectedPort.protocol ===
|
|
||||||
board.port?.protocol
|
|
||||||
) {
|
|
||||||
this.boardsConfig = {
|
|
||||||
...this.latestValidBoardsConfig,
|
|
||||||
selectedPort: board.port,
|
|
||||||
};
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,6 +462,16 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
return this._availableBoards;
|
return this._availableBoards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Do not use this API, it will be removed. This is a hack to be able to set the missing port `properties` before an upload.
|
||||||
|
*
|
||||||
|
* See: https://github.com/arduino/arduino-ide/pull/1335#issuecomment-1224355236.
|
||||||
|
*/
|
||||||
|
// TODO: remove this API and fix the selected board config store/restore correctly.
|
||||||
|
get availablePorts(): Port[] {
|
||||||
|
return this._availablePorts.slice();
|
||||||
|
}
|
||||||
|
|
||||||
async waitUntilAvailable(
|
async waitUntilAvailable(
|
||||||
what: Board & { port: Port },
|
what: Board & { port: Port },
|
||||||
timeout?: number
|
timeout?: number
|
||||||
@@ -436,28 +528,19 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
const currentAvailableBoards = this._availableBoards;
|
const currentAvailableBoards = this._availableBoards;
|
||||||
const availableBoards: AvailableBoard[] = [];
|
const availableBoards: AvailableBoard[] = [];
|
||||||
const attachedBoards = this._attachedBoards.filter(({ port }) => !!port);
|
const attachedBoards = this._attachedBoards.filter(({ port }) => !!port);
|
||||||
const availableBoardPorts = availablePorts.filter((port) => {
|
const availableBoardPorts = availablePorts.filter(
|
||||||
if (port.protocol === 'serial') {
|
Port.visiblePorts(attachedBoards)
|
||||||
// We always show all serial ports, even if there
|
);
|
||||||
// is no recognized board connected to it
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other ports with different protocol are
|
|
||||||
// only shown if there is a recognized board
|
|
||||||
// connected
|
|
||||||
for (const board of attachedBoards) {
|
|
||||||
if (board.port?.address === port.address) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const boardPort of availableBoardPorts) {
|
for (const boardPort of availableBoardPorts) {
|
||||||
const board = attachedBoards.find(({ port }) =>
|
const board = attachedBoards.find(({ port }) =>
|
||||||
Port.sameAs(boardPort, port)
|
Port.sameAs(boardPort, port)
|
||||||
);
|
);
|
||||||
|
// "board" will always be falsey for
|
||||||
|
// port that was originally mapped
|
||||||
|
// to unknown board and then selected
|
||||||
|
// manually by user
|
||||||
|
|
||||||
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
|
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
|
||||||
boardPort
|
boardPort
|
||||||
);
|
);
|
||||||
@@ -476,12 +559,14 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
availableBoard = {
|
availableBoard = {
|
||||||
...lastSelectedBoard,
|
...lastSelectedBoard,
|
||||||
state: AvailableBoard.State.guessed,
|
state: AvailableBoard.State.guessed,
|
||||||
selected: BoardsConfig.Config.sameAs(boardsConfig, lastSelectedBoard),
|
selected:
|
||||||
|
BoardsConfig.Config.sameAs(boardsConfig, lastSelectedBoard) &&
|
||||||
|
Port.sameAs(boardPort, boardsConfig.selectedPort), // to avoid double selection
|
||||||
port: boardPort,
|
port: boardPort,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
availableBoard = {
|
availableBoard = {
|
||||||
name: nls.localize('arduino/common/unknown', 'Unknown'),
|
name: Unknown,
|
||||||
port: boardPort,
|
port: boardPort,
|
||||||
state: AvailableBoard.State.incomplete,
|
state: AvailableBoard.State.incomplete,
|
||||||
};
|
};
|
||||||
@@ -491,7 +576,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
boardsConfig.selectedBoard &&
|
boardsConfig.selectedBoard &&
|
||||||
!availableBoards.some(({ selected }) => selected)
|
availableBoards.every(({ selected }) => !selected)
|
||||||
) {
|
) {
|
||||||
// If the selected board has the same port of an unknown board
|
// If the selected board has the same port of an unknown board
|
||||||
// that is already in availableBoards we might get a duplicate port.
|
// that is already in availableBoards we might get a duplicate port.
|
||||||
|
@@ -130,12 +130,15 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
|
|||||||
protocolIcon
|
protocolIcon
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="arduino-boards-dropdown-item--label">
|
<div
|
||||||
<div className="arduino-boards-dropdown-item--board-label">
|
className="arduino-boards-dropdown-item--label"
|
||||||
|
title={`${boardLabel}\n${port.address}`}
|
||||||
|
>
|
||||||
|
<div className="arduino-boards-dropdown-item--board-label noWrapInfo noselect">
|
||||||
{boardLabel}
|
{boardLabel}
|
||||||
</div>
|
</div>
|
||||||
<div className="arduino-boards-dropdown-item--port-label">
|
<div className="arduino-boards-dropdown-item--port-label noWrapInfo noselect">
|
||||||
{port.address}
|
{port.addressLabel}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{selected ? <div className="fa fa-check" /> : ''}
|
{selected ? <div className="fa fa-check" /> : ''}
|
||||||
@@ -229,7 +232,8 @@ export class BoardsToolBarItem extends React.Component<
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'arduino-boards-toolbar-item--label',
|
'arduino-boards-toolbar-item--label',
|
||||||
'noWrapInfo noselect',
|
'noWrapInfo',
|
||||||
|
'noselect',
|
||||||
{ 'arduino-boards-toolbar-item--label-connected': isConnected }
|
{ 'arduino-boards-toolbar-item--label-connected': isConnected }
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@@ -1,10 +1,16 @@
|
|||||||
import { injectable } from '@theia/core/shared/inversify';
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
import { BoardsListWidget } from './boards-list-widget';
|
import { BoardsListWidget } from './boards-list-widget';
|
||||||
import { BoardsPackage } from '../../common/protocol/boards-service';
|
import type {
|
||||||
|
BoardSearch,
|
||||||
|
BoardsPackage,
|
||||||
|
} from '../../common/protocol/boards-service';
|
||||||
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
|
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
|
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<
|
||||||
|
BoardsPackage,
|
||||||
|
BoardSearch
|
||||||
|
> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
widgetId: BoardsListWidget.WIDGET_ID,
|
widgetId: BoardsListWidget.WIDGET_ID,
|
||||||
|
@@ -4,11 +4,8 @@ import URI from '@theia/core/lib/common/uri';
|
|||||||
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
||||||
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import {
|
import { LibraryService, ResponseServiceClient } from '../../common/protocol';
|
||||||
Installable,
|
import { ExecuteWithProgress } from '../../common/protocol/progressible';
|
||||||
LibraryService,
|
|
||||||
ResponseServiceClient,
|
|
||||||
} from '../../common/protocol';
|
|
||||||
import {
|
import {
|
||||||
SketchContribution,
|
SketchContribution,
|
||||||
Command,
|
Command,
|
||||||
@@ -88,7 +85,7 @@ export class AddZipLibrary extends SketchContribution {
|
|||||||
|
|
||||||
private async doInstall(zipUri: string, overwrite?: boolean): Promise<void> {
|
private async doInstall(zipUri: string, overwrite?: boolean): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await Installable.doWithProgress({
|
await ExecuteWithProgress.doWithProgress({
|
||||||
messageService: this.messageService,
|
messageService: this.messageService,
|
||||||
progressText:
|
progressText:
|
||||||
nls.localize('arduino/common/processing', 'Processing') +
|
nls.localize('arduino/common/processing', 'Processing') +
|
||||||
|
@@ -288,7 +288,7 @@ PID: ${PID}`;
|
|||||||
for (let i = 0; i < sortedIDs.length; i++) {
|
for (let i = 0; i < sortedIDs.length; i++) {
|
||||||
const portID = sortedIDs[i];
|
const portID = sortedIDs[i];
|
||||||
const [port, boards] = ports[portID];
|
const [port, boards] = ports[portID];
|
||||||
let label = `${port.address}`;
|
let label = `${port.addressLabel}`;
|
||||||
if (boards.length) {
|
if (boards.length) {
|
||||||
const boardsList = boards.map((board) => board.name).join(', ');
|
const boardsList = boards.map((board) => board.name).join(', ');
|
||||||
label = `${label} (${boardsList})`;
|
label = `${label} (${boardsList})`;
|
||||||
@@ -331,7 +331,7 @@ PID: ${PID}`;
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const grouped = AvailablePorts.byProtocol(availablePorts);
|
const grouped = AvailablePorts.groupByProtocol(availablePorts);
|
||||||
let protocolOrder = 100;
|
let protocolOrder = 100;
|
||||||
// We first show serial and network ports, then all the rest
|
// We first show serial and network ports, then all the rest
|
||||||
['serial', 'network'].forEach((protocol) => {
|
['serial', 'network'].forEach((protocol) => {
|
||||||
|
@@ -1,23 +1,16 @@
|
|||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { CoreService } from '../../common/protocol';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
|
||||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
|
||||||
import {
|
import {
|
||||||
CoreServiceContribution,
|
|
||||||
Command,
|
Command,
|
||||||
CommandRegistry,
|
CommandRegistry,
|
||||||
|
CoreServiceContribution,
|
||||||
MenuModelRegistry,
|
MenuModelRegistry,
|
||||||
} from './contribution';
|
} from './contribution';
|
||||||
import { nls } from '@theia/core/lib/common';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BurnBootloader extends CoreServiceContribution {
|
export class BurnBootloader extends CoreServiceContribution {
|
||||||
@inject(BoardsDataStore)
|
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
|
||||||
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
|
||||||
|
|
||||||
override registerCommands(registry: CommandRegistry): void {
|
override registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, {
|
registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, {
|
||||||
execute: () => this.burnBootloader(),
|
execute: () => this.burnBootloader(),
|
||||||
@@ -35,32 +28,20 @@ export class BurnBootloader extends CoreServiceContribution {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async burnBootloader(): Promise<void> {
|
private async burnBootloader(): Promise<void> {
|
||||||
|
this.clearVisibleNotification();
|
||||||
|
const options = await this.options();
|
||||||
try {
|
try {
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
await this.doWithProgress({
|
||||||
const port = boardsConfig.selectedPort;
|
progressText: nls.localize(
|
||||||
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
|
'arduino/bootloader/burningBootloader',
|
||||||
await Promise.all([
|
'Burning bootloader...'
|
||||||
this.boardsDataStore.appendConfigToFqbn(
|
|
||||||
boardsConfig.selectedBoard?.fqbn
|
|
||||||
),
|
),
|
||||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
task: (progressId, coreService) =>
|
||||||
this.preferences.get('arduino.upload.verify'),
|
coreService.burnBootloader({
|
||||||
this.preferences.get('arduino.upload.verbose'),
|
...options,
|
||||||
]);
|
progressId,
|
||||||
|
}),
|
||||||
const board = {
|
|
||||||
...boardsConfig.selectedBoard,
|
|
||||||
name: boardsConfig.selectedBoard?.name || '',
|
|
||||||
fqbn,
|
|
||||||
};
|
|
||||||
this.outputChannelManager.getChannel('Arduino').clear();
|
|
||||||
await this.coreService.burnBootloader({
|
|
||||||
board,
|
|
||||||
programmer,
|
|
||||||
port,
|
|
||||||
verify,
|
|
||||||
verbose,
|
|
||||||
});
|
});
|
||||||
this.messageService.info(
|
this.messageService.info(
|
||||||
nls.localize(
|
nls.localize(
|
||||||
@@ -75,6 +56,27 @@ export class BurnBootloader extends CoreServiceContribution {
|
|||||||
this.handleError(e);
|
this.handleError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async options(): Promise<CoreService.Options.Bootloader> {
|
||||||
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
|
const port = boardsConfig.selectedPort;
|
||||||
|
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
|
||||||
|
await Promise.all([
|
||||||
|
this.boardsDataStore.appendConfigToFqbn(
|
||||||
|
boardsConfig.selectedBoard?.fqbn
|
||||||
|
),
|
||||||
|
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||||
|
this.preferences.get('arduino.upload.verify'),
|
||||||
|
this.preferences.get('arduino.upload.verbose'),
|
||||||
|
]);
|
||||||
|
return {
|
||||||
|
fqbn,
|
||||||
|
programmer,
|
||||||
|
port,
|
||||||
|
verify,
|
||||||
|
verbose,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace BurnBootloader {
|
export namespace BurnBootloader {
|
||||||
|
@@ -0,0 +1,68 @@
|
|||||||
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
|
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
||||||
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import {
|
||||||
|
IDEUpdater,
|
||||||
|
SKIP_IDE_VERSION,
|
||||||
|
} from '../../common/protocol/ide-updater';
|
||||||
|
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
|
||||||
|
import { Contribution } from './contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class CheckForIDEUpdates extends Contribution {
|
||||||
|
@inject(IDEUpdater)
|
||||||
|
private readonly updater: IDEUpdater;
|
||||||
|
|
||||||
|
@inject(IDEUpdaterDialog)
|
||||||
|
private readonly updaterDialog: IDEUpdaterDialog;
|
||||||
|
|
||||||
|
@inject(LocalStorageService)
|
||||||
|
private readonly localStorage: LocalStorageService;
|
||||||
|
|
||||||
|
override onStart(): void {
|
||||||
|
this.preferences.onPreferenceChanged(
|
||||||
|
({ preferenceName, newValue, oldValue }) => {
|
||||||
|
if (newValue !== oldValue) {
|
||||||
|
switch (preferenceName) {
|
||||||
|
case 'arduino.ide.updateChannel':
|
||||||
|
case 'arduino.ide.updateBaseUrl':
|
||||||
|
this.updater.init(
|
||||||
|
this.preferences.get('arduino.ide.updateChannel'),
|
||||||
|
this.preferences.get('arduino.ide.updateBaseUrl')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override onReady(): void {
|
||||||
|
const checkForUpdates = this.preferences['arduino.checkForUpdates'];
|
||||||
|
if (!checkForUpdates) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updater
|
||||||
|
.init(
|
||||||
|
this.preferences.get('arduino.ide.updateChannel'),
|
||||||
|
this.preferences.get('arduino.ide.updateBaseUrl')
|
||||||
|
)
|
||||||
|
.then(() => this.updater.checkForUpdates(true))
|
||||||
|
.then(async (updateInfo) => {
|
||||||
|
if (!updateInfo) return;
|
||||||
|
const versionToSkip = await this.localStorage.getData<string>(
|
||||||
|
SKIP_IDE_VERSION
|
||||||
|
);
|
||||||
|
if (versionToSkip === updateInfo.version) return;
|
||||||
|
this.updaterDialog.open(updateInfo);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.messageService.error(
|
||||||
|
nls.localize(
|
||||||
|
'arduino/ide-updater/errorCheckingForUpdates',
|
||||||
|
'Error while checking for Arduino IDE updates.\n{0}',
|
||||||
|
e.message
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -1,64 +1,221 @@
|
|||||||
|
import type { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||||
import { nls } from '@theia/core/lib/common/nls';
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
|
||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { InstallManually, Later } from '../../common/nls';
|
||||||
import {
|
import {
|
||||||
IDEUpdater,
|
ArduinoComponent,
|
||||||
SKIP_IDE_VERSION,
|
BoardsPackage,
|
||||||
} from '../../common/protocol/ide-updater';
|
BoardsService,
|
||||||
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
|
LibraryPackage,
|
||||||
import { Contribution } from './contribution';
|
LibraryService,
|
||||||
|
ResponseServiceClient,
|
||||||
|
Searchable,
|
||||||
|
} from '../../common/protocol';
|
||||||
|
import { Installable } from '../../common/protocol/installable';
|
||||||
|
import { ExecuteWithProgress } from '../../common/protocol/progressible';
|
||||||
|
import { BoardsListWidgetFrontendContribution } from '../boards/boards-widget-frontend-contribution';
|
||||||
|
import { LibraryListWidgetFrontendContribution } from '../library/library-widget-frontend-contribution';
|
||||||
|
import { WindowServiceExt } from '../theia/core/window-service-ext';
|
||||||
|
import type { ListWidget } from '../widgets/component-list/list-widget';
|
||||||
|
import { Command, CommandRegistry, Contribution } from './contribution';
|
||||||
|
|
||||||
|
const NoUpdates = nls.localize(
|
||||||
|
'arduino/checkForUpdates/noUpdates',
|
||||||
|
'There are no recent updates available.'
|
||||||
|
);
|
||||||
|
const PromptUpdateBoards = nls.localize(
|
||||||
|
'arduino/checkForUpdates/promptUpdateBoards',
|
||||||
|
'Updates are available for some of your boards.'
|
||||||
|
);
|
||||||
|
const PromptUpdateLibraries = nls.localize(
|
||||||
|
'arduino/checkForUpdates/promptUpdateLibraries',
|
||||||
|
'Updates are available for some of your libraries.'
|
||||||
|
);
|
||||||
|
const UpdatingBoards = nls.localize(
|
||||||
|
'arduino/checkForUpdates/updatingBoards',
|
||||||
|
'Updating boards...'
|
||||||
|
);
|
||||||
|
const UpdatingLibraries = nls.localize(
|
||||||
|
'arduino/checkForUpdates/updatingLibraries',
|
||||||
|
'Updating libraries...'
|
||||||
|
);
|
||||||
|
const InstallAll = nls.localize(
|
||||||
|
'arduino/checkForUpdates/installAll',
|
||||||
|
'Install All'
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Task<T extends ArduinoComponent> {
|
||||||
|
readonly run: () => Promise<void>;
|
||||||
|
readonly item: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Updatable = { type: 'Updatable' } as const;
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CheckForUpdates extends Contribution {
|
export class CheckForUpdates extends Contribution {
|
||||||
@inject(IDEUpdater)
|
@inject(WindowServiceExt)
|
||||||
private readonly updater: IDEUpdater;
|
private readonly windowService: WindowServiceExt;
|
||||||
|
@inject(ResponseServiceClient)
|
||||||
|
private readonly responseService: ResponseServiceClient;
|
||||||
|
@inject(BoardsService)
|
||||||
|
private readonly boardsService: BoardsService;
|
||||||
|
@inject(LibraryService)
|
||||||
|
private readonly libraryService: LibraryService;
|
||||||
|
@inject(BoardsListWidgetFrontendContribution)
|
||||||
|
private readonly boardsContribution: BoardsListWidgetFrontendContribution;
|
||||||
|
@inject(LibraryListWidgetFrontendContribution)
|
||||||
|
private readonly librariesContribution: LibraryListWidgetFrontendContribution;
|
||||||
|
|
||||||
@inject(IDEUpdaterDialog)
|
override registerCommands(register: CommandRegistry): void {
|
||||||
private readonly updaterDialog: IDEUpdaterDialog;
|
register.registerCommand(CheckForUpdates.Commands.CHECK_FOR_UPDATES, {
|
||||||
|
execute: () => this.checkForUpdates(false),
|
||||||
@inject(LocalStorageService)
|
|
||||||
private readonly localStorage: LocalStorageService;
|
|
||||||
|
|
||||||
override onStart(): void {
|
|
||||||
this.preferences.onPreferenceChanged(
|
|
||||||
({ preferenceName, newValue, oldValue }) => {
|
|
||||||
if (newValue !== oldValue) {
|
|
||||||
switch (preferenceName) {
|
|
||||||
case 'arduino.ide.updateChannel':
|
|
||||||
case 'arduino.ide.updateBaseUrl':
|
|
||||||
this.updater.init(
|
|
||||||
this.preferences.get('arduino.ide.updateChannel'),
|
|
||||||
this.preferences.get('arduino.ide.updateBaseUrl')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
override onReady(): void {
|
|
||||||
this.updater
|
|
||||||
.init(
|
|
||||||
this.preferences.get('arduino.ide.updateChannel'),
|
|
||||||
this.preferences.get('arduino.ide.updateBaseUrl')
|
|
||||||
)
|
|
||||||
.then(() => this.updater.checkForUpdates(true))
|
|
||||||
.then(async (updateInfo) => {
|
|
||||||
if (!updateInfo) return;
|
|
||||||
const versionToSkip = await this.localStorage.getData<string>(
|
|
||||||
SKIP_IDE_VERSION
|
|
||||||
);
|
|
||||||
if (versionToSkip === updateInfo.version) return;
|
|
||||||
this.updaterDialog.open(updateInfo);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
this.messageService.error(
|
|
||||||
nls.localize(
|
|
||||||
'arduino/ide-updater/errorCheckingForUpdates',
|
|
||||||
'Error while checking for Arduino IDE updates.\n{0}',
|
|
||||||
e.message
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async onReady(): Promise<void> {
|
||||||
|
const checkForUpdates = this.preferences['arduino.checkForUpdates'];
|
||||||
|
if (checkForUpdates) {
|
||||||
|
this.windowService.isFirstWindow().then((firstWindow) => {
|
||||||
|
if (firstWindow) {
|
||||||
|
this.checkForUpdates();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkForUpdates(silent = true) {
|
||||||
|
const [boardsPackages, libraryPackages] = await Promise.all([
|
||||||
|
this.boardsService.search(Updatable),
|
||||||
|
this.libraryService.search(Updatable),
|
||||||
|
]);
|
||||||
|
this.promptUpdateBoards(boardsPackages);
|
||||||
|
this.promptUpdateLibraries(libraryPackages);
|
||||||
|
if (!libraryPackages.length && !boardsPackages.length && !silent) {
|
||||||
|
this.messageService.info(NoUpdates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private promptUpdateBoards(items: BoardsPackage[]): void {
|
||||||
|
this.prompt({
|
||||||
|
items,
|
||||||
|
installable: this.boardsService,
|
||||||
|
viewContribution: this.boardsContribution,
|
||||||
|
viewSearchOptions: { query: '', ...Updatable },
|
||||||
|
promptMessage: PromptUpdateBoards,
|
||||||
|
updatingMessage: UpdatingBoards,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private promptUpdateLibraries(items: LibraryPackage[]): void {
|
||||||
|
this.prompt({
|
||||||
|
items,
|
||||||
|
installable: this.libraryService,
|
||||||
|
viewContribution: this.librariesContribution,
|
||||||
|
viewSearchOptions: { query: '', topic: 'All', ...Updatable },
|
||||||
|
promptMessage: PromptUpdateLibraries,
|
||||||
|
updatingMessage: UpdatingLibraries,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private prompt<
|
||||||
|
T extends ArduinoComponent,
|
||||||
|
S extends Searchable.Options
|
||||||
|
>(options: {
|
||||||
|
items: T[];
|
||||||
|
installable: Installable<T>;
|
||||||
|
viewContribution: AbstractViewContribution<ListWidget<T, S>>;
|
||||||
|
viewSearchOptions: S;
|
||||||
|
promptMessage: string;
|
||||||
|
updatingMessage: string;
|
||||||
|
}): void {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
installable,
|
||||||
|
viewContribution,
|
||||||
|
promptMessage: message,
|
||||||
|
viewSearchOptions,
|
||||||
|
updatingMessage,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.messageService
|
||||||
|
.info(message, Later, InstallManually, InstallAll)
|
||||||
|
.then((answer) => {
|
||||||
|
if (answer === InstallAll) {
|
||||||
|
const tasks = items.map((item) =>
|
||||||
|
this.createInstallTask(item, installable)
|
||||||
|
);
|
||||||
|
this.executeTasks(updatingMessage, tasks);
|
||||||
|
} else if (answer === InstallManually) {
|
||||||
|
viewContribution
|
||||||
|
.openView({ reveal: true })
|
||||||
|
.then((widget) => widget.refresh(viewSearchOptions));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async executeTasks(
|
||||||
|
message: string,
|
||||||
|
tasks: Task<ArduinoComponent>[]
|
||||||
|
): Promise<void> {
|
||||||
|
if (tasks.length) {
|
||||||
|
return ExecuteWithProgress.withProgress(
|
||||||
|
message,
|
||||||
|
this.messageService,
|
||||||
|
async (progress) => {
|
||||||
|
try {
|
||||||
|
const total = tasks.length;
|
||||||
|
let count = 0;
|
||||||
|
for (const { run, item } of tasks) {
|
||||||
|
try {
|
||||||
|
await run(); // runs update sequentially. // TODO: is parallel update desired?
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
this.messageService.error(
|
||||||
|
`Failed to update ${item.name}. ${err}`
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
progress.report({ work: { total, done: ++count } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
progress.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createInstallTask<T extends ArduinoComponent>(
|
||||||
|
item: T,
|
||||||
|
installable: Installable<T>
|
||||||
|
): Task<T> {
|
||||||
|
const latestVersion = item.availableVersions[0];
|
||||||
|
return {
|
||||||
|
item,
|
||||||
|
run: () =>
|
||||||
|
Installable.installWithProgress({
|
||||||
|
installable,
|
||||||
|
item,
|
||||||
|
version: latestVersion,
|
||||||
|
messageService: this.messageService,
|
||||||
|
responseService: this.responseService,
|
||||||
|
keepOutput: true,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export namespace CheckForUpdates {
|
||||||
|
export namespace Commands {
|
||||||
|
export const CHECK_FOR_UPDATES: Command = Command.toLocalizedCommand(
|
||||||
|
{
|
||||||
|
id: 'arduino-check-for-updates',
|
||||||
|
label: 'Check for Arduino Updates',
|
||||||
|
category: 'Arduino',
|
||||||
|
},
|
||||||
|
'arduino/checkForUpdates/checkForUpdates'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,14 @@
|
|||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { toArray } from '@theia/core/shared/@phosphor/algorithm';
|
||||||
import * as remote from '@theia/core/electron-shared/@electron/remote';
|
import * as remote from '@theia/core/electron-shared/@electron/remote';
|
||||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||||
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
import type { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
|
import type {
|
||||||
|
FrontendApplication,
|
||||||
|
OnWillStopAction,
|
||||||
|
} from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
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 { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import {
|
import {
|
||||||
SketchContribution,
|
SketchContribution,
|
||||||
@@ -11,27 +16,48 @@ import {
|
|||||||
CommandRegistry,
|
CommandRegistry,
|
||||||
MenuModelRegistry,
|
MenuModelRegistry,
|
||||||
KeybindingRegistry,
|
KeybindingRegistry,
|
||||||
|
Sketch,
|
||||||
URI,
|
URI,
|
||||||
} from './contribution';
|
} from './contribution';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { Dialog } from '@theia/core/lib/browser/dialogs';
|
||||||
|
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||||
|
import { SaveAsSketch } from './save-as-sketch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window.
|
* Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window.
|
||||||
*/
|
*/
|
||||||
@injectable()
|
@injectable()
|
||||||
export class Close extends SketchContribution {
|
export class Close extends SketchContribution {
|
||||||
@inject(EditorManager)
|
private shell: ApplicationShell | undefined;
|
||||||
protected override readonly editorManager: EditorManager;
|
|
||||||
|
|
||||||
protected shell: ApplicationShell;
|
override onStart(app: FrontendApplication): MaybePromise<void> {
|
||||||
|
|
||||||
override onStart(app: FrontendApplication): void {
|
|
||||||
this.shell = app.shell;
|
this.shell = app.shell;
|
||||||
}
|
}
|
||||||
|
|
||||||
override registerCommands(registry: CommandRegistry): void {
|
override registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(Close.Commands.CLOSE, {
|
registry.registerCommand(Close.Commands.CLOSE, {
|
||||||
execute: () => remote.getCurrentWindow().close()
|
execute: () => {
|
||||||
|
// Close current editor if closeable.
|
||||||
|
const { currentEditor } = this.editorManager;
|
||||||
|
if (currentEditor && currentEditor.title.closable) {
|
||||||
|
currentEditor.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shell) {
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remote.getCurrentWindow().close();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +76,123 @@ export class Close extends SketchContribution {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `FrontendApplicationContribution#onWillStop`
|
||||||
|
onWillStop(): OnWillStopAction {
|
||||||
|
return {
|
||||||
|
reason: 'save-sketch',
|
||||||
|
action: () => {
|
||||||
|
return this.showSaveSketchDialog();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If returns with `true`, IDE2 will close. Otherwise, it won't.
|
||||||
|
*/
|
||||||
|
private async showSaveSketchDialog(): Promise<boolean> {
|
||||||
|
const sketch = await this.isCurrentSketchTemp();
|
||||||
|
if (!sketch) {
|
||||||
|
// Normal close workflow: if there are dirty editors prompt the user.
|
||||||
|
if (!this.shell) {
|
||||||
|
console.error(
|
||||||
|
`Could not get the application shell. Something went wrong.`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.shell.canSaveAll()) {
|
||||||
|
const prompt = await this.prompt(false);
|
||||||
|
switch (prompt) {
|
||||||
|
case Prompt.DoNotSave:
|
||||||
|
return true;
|
||||||
|
case Prompt.Cancel:
|
||||||
|
return false;
|
||||||
|
case Prompt.Save: {
|
||||||
|
await this.shell.saveAll();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected prompt: ${prompt}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If non of the sketch files were ever touched, do not prompt the save dialog. (#1274)
|
||||||
|
const wereTouched = await Promise.all(
|
||||||
|
Sketch.uris(sketch).map((uri) => this.wasTouched(uri))
|
||||||
|
);
|
||||||
|
if (wereTouched.every((wasTouched) => !Boolean(wasTouched))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompt = await this.prompt(true);
|
||||||
|
switch (prompt) {
|
||||||
|
case Prompt.DoNotSave:
|
||||||
|
return true;
|
||||||
|
case Prompt.Cancel:
|
||||||
|
return false;
|
||||||
|
case Prompt.Save: {
|
||||||
|
// If `save as` was canceled by user, the result will be `undefined`, otherwise the new URI.
|
||||||
|
const result = await this.commandService.executeCommand(
|
||||||
|
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||||
|
{
|
||||||
|
execOnlyIfTemp: false,
|
||||||
|
openAfterMove: false,
|
||||||
|
wipeOriginal: true,
|
||||||
|
markAsRecentlyOpened: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return !!result;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected prompt: ${prompt}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async prompt(isTemp: boolean): Promise<Prompt> {
|
||||||
|
const { response } = await remote.dialog.showMessageBox(
|
||||||
|
remote.getCurrentWindow(),
|
||||||
|
{
|
||||||
|
message: nls.localize(
|
||||||
|
'arduino/sketch/saveSketch',
|
||||||
|
'Save your sketch to open it again later.'
|
||||||
|
),
|
||||||
|
title: nls.localize(
|
||||||
|
'theia/core/quitTitle',
|
||||||
|
'Are you sure you want to quit?'
|
||||||
|
),
|
||||||
|
type: 'question',
|
||||||
|
buttons: [
|
||||||
|
nls.localizeByDefault("Don't Save"),
|
||||||
|
Dialog.CANCEL,
|
||||||
|
nls.localizeByDefault(isTemp ? 'Save As...' : 'Save'),
|
||||||
|
],
|
||||||
|
defaultId: 2, // `Save`/`Save As...` button index is the default.
|
||||||
|
}
|
||||||
|
);
|
||||||
|
switch (response) {
|
||||||
|
case 0:
|
||||||
|
return Prompt.DoNotSave;
|
||||||
|
case 1:
|
||||||
|
return Prompt.Cancel;
|
||||||
|
case 2:
|
||||||
|
return Prompt.Save;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected response: ${response}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async isCurrentSketchTemp(): Promise<false | Sketch> {
|
||||||
|
const currentSketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (CurrentSketch.isValid(currentSketch)) {
|
||||||
|
const isTemp = await this.sketchService.isTemp(currentSketch);
|
||||||
|
if (isTemp) {
|
||||||
|
return currentSketch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the file was ever touched/modified. We get this based on the `version` of the monaco model.
|
* If the file was ever touched/modified. We get this based on the `version` of the monaco model.
|
||||||
*/
|
*/
|
||||||
@@ -59,13 +202,23 @@ export class Close extends SketchContribution {
|
|||||||
const { editor } = editorWidget;
|
const { editor } = editorWidget;
|
||||||
if (editor instanceof MonacoEditor) {
|
if (editor instanceof MonacoEditor) {
|
||||||
const versionId = editor.getControl().getModel()?.getVersionId();
|
const versionId = editor.getControl().getModel()?.getVersionId();
|
||||||
if (Number.isInteger(versionId) && versionId! > 1) {
|
if (this.isInteger(versionId) && versionId > 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isInteger(arg: unknown): arg is number {
|
||||||
|
return Number.isInteger(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Prompt {
|
||||||
|
Save,
|
||||||
|
DoNotSave,
|
||||||
|
Cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Close {
|
export namespace Close {
|
||||||
|
@@ -4,11 +4,13 @@ import {
|
|||||||
Disposable,
|
Disposable,
|
||||||
DisposableCollection,
|
DisposableCollection,
|
||||||
Emitter,
|
Emitter,
|
||||||
|
MaybeArray,
|
||||||
MaybePromise,
|
MaybePromise,
|
||||||
nls,
|
nls,
|
||||||
notEmpty,
|
notEmpty,
|
||||||
} from '@theia/core';
|
} from '@theia/core';
|
||||||
import { ApplicationShell, FrontendApplication } from '@theia/core/lib/browser';
|
import { ApplicationShell, FrontendApplication } from '@theia/core/lib/browser';
|
||||||
|
import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
import {
|
import {
|
||||||
@@ -28,14 +30,15 @@ import * as monaco from '@theia/monaco-editor-core';
|
|||||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||||
import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
|
import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
|
||||||
import { ProtocolToMonacoConverter } from '@theia/monaco/lib/browser/protocol-to-monaco-converter';
|
import { ProtocolToMonacoConverter } from '@theia/monaco/lib/browser/protocol-to-monaco-converter';
|
||||||
|
import { OutputUri } from '@theia/output/lib/common/output-uri';
|
||||||
import { CoreError } from '../../common/protocol/core-service';
|
import { CoreError } from '../../common/protocol/core-service';
|
||||||
import { ErrorRevealStrategy } from '../arduino-preferences';
|
import { ErrorRevealStrategy } from '../arduino-preferences';
|
||||||
import { InoSelector } from '../ino-selectors';
|
import { ArduinoOutputSelector, InoSelector } from '../selectors';
|
||||||
import { fullRange } from '../utils/monaco';
|
|
||||||
import { Contribution } from './contribution';
|
import { Contribution } from './contribution';
|
||||||
import { CoreErrorHandler } from './core-error-handler';
|
import { CoreErrorHandler } from './core-error-handler';
|
||||||
|
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
|
||||||
|
|
||||||
interface ErrorDecoration {
|
interface ErrorDecorationRef {
|
||||||
/**
|
/**
|
||||||
* This is the unique ID of the decoration given by `monaco`.
|
* This is the unique ID of the decoration given by `monaco`.
|
||||||
*/
|
*/
|
||||||
@@ -45,72 +48,89 @@ interface ErrorDecoration {
|
|||||||
*/
|
*/
|
||||||
readonly uri: string;
|
readonly uri: string;
|
||||||
}
|
}
|
||||||
namespace ErrorDecoration {
|
export namespace ErrorDecorationRef {
|
||||||
export function rangeOf(
|
export function is(arg: unknown): arg is ErrorDecorationRef {
|
||||||
{ id, uri }: ErrorDecoration,
|
if (typeof arg === 'object') {
|
||||||
editorProvider: (uri: string) => Promise<MonacoEditor | undefined>
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
): Promise<monaco.Range | undefined>;
|
const object = arg as any;
|
||||||
export function rangeOf(
|
return (
|
||||||
{ id, uri }: ErrorDecoration,
|
'uri' in object &&
|
||||||
editorProvider: MonacoEditor
|
typeof object['uri'] === 'string' &&
|
||||||
): monaco.Range | undefined;
|
'id' in object &&
|
||||||
export function rangeOf(
|
typeof object['id'] === 'string'
|
||||||
{ id, uri }: ErrorDecoration,
|
);
|
||||||
editorProvider:
|
|
||||||
| ((uri: string) => Promise<MonacoEditor | undefined>)
|
|
||||||
| MonacoEditor
|
|
||||||
): MaybePromise<monaco.Range | undefined> {
|
|
||||||
if (editorProvider instanceof MonacoEditor) {
|
|
||||||
const control = editorProvider.getControl();
|
|
||||||
const model = control.getModel();
|
|
||||||
if (model) {
|
|
||||||
return control
|
|
||||||
.getDecorationsInRange(fullRange(model))
|
|
||||||
?.find(({ id: candidateId }) => id === candidateId)?.range;
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return false;
|
||||||
}
|
}
|
||||||
return editorProvider(uri).then((editor) => {
|
|
||||||
if (editor) {
|
|
||||||
return rangeOf({ id, uri }, editor);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// export async function rangeOf(
|
|
||||||
// { id, uri }: ErrorDecoration,
|
|
||||||
// editorProvider:
|
|
||||||
// | ((uri: string) => Promise<MonacoEditor | undefined>)
|
|
||||||
// | MonacoEditor
|
|
||||||
// ): Promise<monaco.Range | undefined> {
|
|
||||||
// const editor =
|
|
||||||
// editorProvider instanceof MonacoEditor
|
|
||||||
// ? editorProvider
|
|
||||||
// : await editorProvider(uri);
|
|
||||||
// if (editor) {
|
|
||||||
// const control = editor.getControl();
|
|
||||||
// const model = control.getModel();
|
|
||||||
// if (model) {
|
|
||||||
// return control
|
|
||||||
// .getDecorationsInRange(fullRange(model))
|
|
||||||
// ?.find(({ id: candidateId }) => id === candidateId)?.range;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return undefined;
|
|
||||||
// }
|
|
||||||
export function sameAs(
|
export function sameAs(
|
||||||
left: ErrorDecoration,
|
left: ErrorDecorationRef,
|
||||||
right: ErrorDecoration
|
right: ErrorDecorationRef
|
||||||
): boolean {
|
): boolean {
|
||||||
return left.id === right.id && left.uri === right.uri;
|
return left.id === right.id && left.uri === right.uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ErrorDecoration extends ErrorDecorationRef {
|
||||||
|
/**
|
||||||
|
* The range of the error location the error in the compiler output from the CLI.
|
||||||
|
*/
|
||||||
|
readonly rangesInOutput: monaco.Range[];
|
||||||
|
}
|
||||||
|
namespace ErrorDecoration {
|
||||||
|
export function rangeOf(
|
||||||
|
editorOrModel: MonacoEditor | ITextModel | undefined,
|
||||||
|
decorations: ErrorDecoration
|
||||||
|
): monaco.Range | undefined;
|
||||||
|
export function rangeOf(
|
||||||
|
editorOrModel: MonacoEditor | ITextModel | undefined,
|
||||||
|
decorations: ErrorDecoration[]
|
||||||
|
): (monaco.Range | undefined)[];
|
||||||
|
export function rangeOf(
|
||||||
|
editorOrModel: MonacoEditor | ITextModel | undefined,
|
||||||
|
decorations: ErrorDecoration | ErrorDecoration[]
|
||||||
|
): MaybePromise<MaybeArray<monaco.Range | undefined>> {
|
||||||
|
if (editorOrModel) {
|
||||||
|
const allDecorations = getAllDecorations(editorOrModel);
|
||||||
|
if (allDecorations) {
|
||||||
|
if (Array.isArray(decorations)) {
|
||||||
|
return decorations.map(({ id: decorationId }) =>
|
||||||
|
findRangeOf(decorationId, allDecorations)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return findRangeOf(decorations.id, allDecorations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.isArray(decorations)
|
||||||
|
? decorations.map(() => undefined)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
function findRangeOf(
|
||||||
|
decorationId: string,
|
||||||
|
allDecorations: { id: string; range?: monaco.Range }[]
|
||||||
|
): monaco.Range | undefined {
|
||||||
|
return allDecorations.find(
|
||||||
|
({ id: candidateId }) => candidateId === decorationId
|
||||||
|
)?.range;
|
||||||
|
}
|
||||||
|
function getAllDecorations(
|
||||||
|
editorOrModel: MonacoEditor | ITextModel
|
||||||
|
): { id: string; range?: monaco.Range }[] {
|
||||||
|
if (editorOrModel instanceof MonacoEditor) {
|
||||||
|
const model = editorOrModel.getControl().getModel();
|
||||||
|
if (!model) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return model.getAllDecorations();
|
||||||
|
}
|
||||||
|
return editorOrModel.getAllDecorations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CompilerErrors
|
export class CompilerErrors
|
||||||
extends Contribution
|
extends Contribution
|
||||||
implements monaco.languages.CodeLensProvider
|
implements monaco.languages.CodeLensProvider, monaco.languages.LinkProvider
|
||||||
{
|
{
|
||||||
@inject(EditorManager)
|
@inject(EditorManager)
|
||||||
private readonly editorManager: EditorManager;
|
private readonly editorManager: EditorManager;
|
||||||
@@ -119,11 +139,14 @@ export class CompilerErrors
|
|||||||
private readonly p2m: ProtocolToMonacoConverter;
|
private readonly p2m: ProtocolToMonacoConverter;
|
||||||
|
|
||||||
@inject(MonacoToProtocolConverter)
|
@inject(MonacoToProtocolConverter)
|
||||||
private readonly mp2: MonacoToProtocolConverter;
|
private readonly m2p: MonacoToProtocolConverter;
|
||||||
|
|
||||||
@inject(CoreErrorHandler)
|
@inject(CoreErrorHandler)
|
||||||
private readonly coreErrorHandler: CoreErrorHandler;
|
private readonly coreErrorHandler: CoreErrorHandler;
|
||||||
|
|
||||||
|
private revealStrategy = ErrorRevealStrategy.Default;
|
||||||
|
private experimental = false;
|
||||||
|
|
||||||
private readonly errors: ErrorDecoration[] = [];
|
private readonly errors: ErrorDecoration[] = [];
|
||||||
private readonly onDidChangeEmitter = new monaco.Emitter<this>();
|
private readonly onDidChangeEmitter = new monaco.Emitter<this>();
|
||||||
private readonly currentErrorDidChangEmitter = new Emitter<ErrorDecoration>();
|
private readonly currentErrorDidChangEmitter = new Emitter<ErrorDecoration>();
|
||||||
@@ -131,8 +154,8 @@ export class CompilerErrors
|
|||||||
this.currentErrorDidChangEmitter.event;
|
this.currentErrorDidChangEmitter.event;
|
||||||
private readonly toDisposeOnCompilerErrorDidChange =
|
private readonly toDisposeOnCompilerErrorDidChange =
|
||||||
new DisposableCollection();
|
new DisposableCollection();
|
||||||
|
|
||||||
private shell: ApplicationShell | undefined;
|
private shell: ApplicationShell | undefined;
|
||||||
private revealStrategy = ErrorRevealStrategy.Default;
|
|
||||||
private currentError: ErrorDecoration | undefined;
|
private currentError: ErrorDecoration | undefined;
|
||||||
private get currentErrorIndex(): number {
|
private get currentErrorIndex(): number {
|
||||||
const current = this.currentError;
|
const current = this.currentError;
|
||||||
@@ -140,46 +163,75 @@ export class CompilerErrors
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return this.errors.findIndex((error) =>
|
return this.errors.findIndex((error) =>
|
||||||
ErrorDecoration.sameAs(error, current)
|
ErrorDecorationRef.sameAs(error, current)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override onStart(app: FrontendApplication): void {
|
override onStart(app: FrontendApplication): void {
|
||||||
this.shell = app.shell;
|
this.shell = app.shell;
|
||||||
monaco.languages.registerCodeLensProvider(InoSelector, this);
|
monaco.languages.registerCodeLensProvider(InoSelector, this);
|
||||||
|
monaco.languages.registerLinkProvider(ArduinoOutputSelector, this);
|
||||||
this.coreErrorHandler.onCompilerErrorsDidChange((errors) =>
|
this.coreErrorHandler.onCompilerErrorsDidChange((errors) =>
|
||||||
this.filter(errors).then(this.handleCompilerErrorsDidChange.bind(this))
|
this.handleCompilerErrorsDidChange(errors)
|
||||||
);
|
);
|
||||||
this.onCurrentErrorDidChange(async (error) => {
|
this.onCurrentErrorDidChange(async (error) => {
|
||||||
const range = await ErrorDecoration.rangeOf(error, (uri) =>
|
const monacoEditor = await this.monacoEditor(error.uri);
|
||||||
this.monacoEditor(uri)
|
const monacoRange = ErrorDecoration.rangeOf(monacoEditor, error);
|
||||||
);
|
if (!monacoRange) {
|
||||||
if (!range) {
|
|
||||||
console.warn(
|
console.warn(
|
||||||
'compiler-errors',
|
'compiler-errors',
|
||||||
`Could not find range of decoration: ${error.id}`
|
`Could not find range of decoration: ${error.id}`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const range = this.m2p.asRange(monacoRange);
|
||||||
const editor = await this.revealLocationInEditor({
|
const editor = await this.revealLocationInEditor({
|
||||||
uri: error.uri,
|
uri: error.uri,
|
||||||
range: this.mp2.asRange(range),
|
range,
|
||||||
});
|
});
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'compiler-errors',
|
'compiler-errors',
|
||||||
`Failed to mark error ${error.id} as the current one.`
|
`Failed to mark error ${error.id} as the current one.`
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
const monacoEditor = this.monacoEditor(editor);
|
||||||
|
if (monacoEditor) {
|
||||||
|
monacoEditor.cursor = range.start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override onReady(): MaybePromise<void> {
|
||||||
this.preferences.ready.then(() => {
|
this.preferences.ready.then(() => {
|
||||||
this.preferences.onPreferenceChanged(({ preferenceName, newValue }) => {
|
this.experimental = Boolean(
|
||||||
if (preferenceName === 'arduino.compile.revealRange') {
|
this.preferences['arduino.compile.experimental']
|
||||||
|
);
|
||||||
|
const strategy = this.preferences['arduino.compile.revealRange'];
|
||||||
|
this.revealStrategy = ErrorRevealStrategy.is(strategy)
|
||||||
|
? strategy
|
||||||
|
: ErrorRevealStrategy.Default;
|
||||||
|
this.preferences.onPreferenceChanged(
|
||||||
|
({ preferenceName, newValue, oldValue }) => {
|
||||||
|
if (newValue === oldValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (preferenceName) {
|
||||||
|
case 'arduino.compile.revealRange': {
|
||||||
this.revealStrategy = ErrorRevealStrategy.is(newValue)
|
this.revealStrategy = ErrorRevealStrategy.is(newValue)
|
||||||
? newValue
|
? newValue
|
||||||
: ErrorRevealStrategy.Default;
|
: ErrorRevealStrategy.Default;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
case 'arduino.compile.experimental': {
|
||||||
|
this.experimental = Boolean(newValue);
|
||||||
|
this.onDidChangeEmitter.fire(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,9 +248,13 @@ export class CompilerErrors
|
|||||||
}
|
}
|
||||||
const nextError =
|
const nextError =
|
||||||
this.errors[index === this.errors.length - 1 ? 0 : index + 1];
|
this.errors[index === this.errors.length - 1 ? 0 : index + 1];
|
||||||
this.markAsCurrentError(nextError);
|
return this.markAsCurrentError(nextError, {
|
||||||
|
forceReselect: true,
|
||||||
|
reveal: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
isEnabled: () => !!this.currentError && this.errors.length > 1,
|
isEnabled: () =>
|
||||||
|
this.experimental && !!this.currentError && this.errors.length > 1,
|
||||||
});
|
});
|
||||||
registry.registerCommand(CompilerErrors.Commands.PREVIOUS_ERROR, {
|
registry.registerCommand(CompilerErrors.Commands.PREVIOUS_ERROR, {
|
||||||
execute: () => {
|
execute: () => {
|
||||||
@@ -212,9 +268,24 @@ export class CompilerErrors
|
|||||||
}
|
}
|
||||||
const previousError =
|
const previousError =
|
||||||
this.errors[index === 0 ? this.errors.length - 1 : index - 1];
|
this.errors[index === 0 ? this.errors.length - 1 : index - 1];
|
||||||
this.markAsCurrentError(previousError);
|
return this.markAsCurrentError(previousError, {
|
||||||
|
forceReselect: true,
|
||||||
|
reveal: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
isEnabled: () => !!this.currentError && this.errors.length > 1,
|
isEnabled: () =>
|
||||||
|
this.experimental && !!this.currentError && this.errors.length > 1,
|
||||||
|
});
|
||||||
|
registry.registerCommand(CompilerErrors.Commands.MARK_AS_CURRENT, {
|
||||||
|
execute: (arg: unknown) => {
|
||||||
|
if (ErrorDecorationRef.is(arg)) {
|
||||||
|
return this.markAsCurrentError(
|
||||||
|
{ id: arg.id, uri: new URI(arg.uri).toString() }, // Make sure the URI fragments are encoded. On Windows, `C:` is encoded as `C%3A`.
|
||||||
|
{ forceReselect: true, reveal: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isEnabled: () => !!this.errors.length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,13 +300,13 @@ export class CompilerErrors
|
|||||||
): Promise<monaco.languages.CodeLensList> {
|
): Promise<monaco.languages.CodeLensList> {
|
||||||
const lenses: monaco.languages.CodeLens[] = [];
|
const lenses: monaco.languages.CodeLens[] = [];
|
||||||
if (
|
if (
|
||||||
|
this.experimental &&
|
||||||
this.currentError &&
|
this.currentError &&
|
||||||
this.currentError.uri === model.uri.toString() &&
|
this.currentError.uri === model.uri.toString() &&
|
||||||
this.errors.length > 1
|
this.errors.length > 1
|
||||||
) {
|
) {
|
||||||
const range = await ErrorDecoration.rangeOf(this.currentError, (uri) =>
|
const monacoEditor = await this.monacoEditor(model.uri);
|
||||||
this.monacoEditor(uri)
|
const range = ErrorDecoration.rangeOf(monacoEditor, this.currentError);
|
||||||
);
|
|
||||||
if (range) {
|
if (range) {
|
||||||
lenses.push(
|
lenses.push(
|
||||||
{
|
{
|
||||||
@@ -268,14 +339,81 @@ export class CompilerErrors
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async provideLinks(
|
||||||
|
model: monaco.editor.ITextModel,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
_token: monaco.CancellationToken
|
||||||
|
): Promise<monaco.languages.ILinksList> {
|
||||||
|
const links: monaco.languages.ILink[] = [];
|
||||||
|
if (
|
||||||
|
model.uri.scheme === OutputUri.SCHEME &&
|
||||||
|
model.uri.path === '/Arduino'
|
||||||
|
) {
|
||||||
|
links.push(
|
||||||
|
...this.errors
|
||||||
|
.filter((decoration) => !!decoration.rangesInOutput.length)
|
||||||
|
.map(({ rangesInOutput, id, uri }) =>
|
||||||
|
rangesInOutput.map(
|
||||||
|
(range) =>
|
||||||
|
<monaco.languages.ILink>{
|
||||||
|
range,
|
||||||
|
url: monaco.Uri.parse(`command://`).with({
|
||||||
|
query: JSON.stringify({ id, uri }),
|
||||||
|
path: CompilerErrors.Commands.MARK_AS_CURRENT.id,
|
||||||
|
}),
|
||||||
|
tooltip: nls.localize(
|
||||||
|
'arduino/editor/revealError',
|
||||||
|
'Reveal Error'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.reduce((acc, curr) => acc.concat(curr), [])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.warn('unexpected URI: ' + model.uri.toString());
|
||||||
|
}
|
||||||
|
return { links };
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolveLink(
|
||||||
|
link: monaco.languages.ILink,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
_token: monaco.CancellationToken
|
||||||
|
): Promise<monaco.languages.ILink | undefined> {
|
||||||
|
if (!this.experimental) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const { url } = link;
|
||||||
|
if (url) {
|
||||||
|
const candidateUri = new URI(
|
||||||
|
typeof url === 'string' ? url : url.toString()
|
||||||
|
);
|
||||||
|
const candidateId = candidateUri.path.toString();
|
||||||
|
const error = this.errors.find((error) => error.id === candidateId);
|
||||||
|
if (error) {
|
||||||
|
const monacoEditor = await this.monacoEditor(error.uri);
|
||||||
|
const range = ErrorDecoration.rangeOf(monacoEditor, error);
|
||||||
|
if (range) {
|
||||||
|
return {
|
||||||
|
range,
|
||||||
|
url: monaco.Uri.parse(error.uri),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private async handleCompilerErrorsDidChange(
|
private async handleCompilerErrorsDidChange(
|
||||||
errors: CoreError.ErrorLocation[]
|
errors: CoreError.ErrorLocation[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.toDisposeOnCompilerErrorDidChange.dispose();
|
this.toDisposeOnCompilerErrorDidChange.dispose();
|
||||||
const compilerErrorsPerResource = this.groupByResource(
|
const groupedErrors = this.groupBy(
|
||||||
await this.filter(errors)
|
errors,
|
||||||
|
(error: CoreError.ErrorLocation) => error.location.uri
|
||||||
);
|
);
|
||||||
const decorations = await this.decorateEditors(compilerErrorsPerResource);
|
const decorations = await this.decorateEditors(groupedErrors);
|
||||||
this.errors.push(...decorations.errors);
|
this.errors.push(...decorations.errors);
|
||||||
this.toDisposeOnCompilerErrorDidChange.pushAll([
|
this.toDisposeOnCompilerErrorDidChange.pushAll([
|
||||||
Disposable.create(() => (this.errors.length = 0)),
|
Disposable.create(() => (this.errors.length = 0)),
|
||||||
@@ -283,17 +421,17 @@ export class CompilerErrors
|
|||||||
...(await Promise.all([
|
...(await Promise.all([
|
||||||
decorations.dispose,
|
decorations.dispose,
|
||||||
this.trackEditors(
|
this.trackEditors(
|
||||||
compilerErrorsPerResource,
|
groupedErrors,
|
||||||
(editor) =>
|
(editor) =>
|
||||||
editor.editor.onSelectionChanged((selection) =>
|
editor.onSelectionChanged((selection) =>
|
||||||
this.handleSelectionChange(editor, selection)
|
this.handleSelectionChange(editor, selection)
|
||||||
),
|
),
|
||||||
(editor) =>
|
(editor) =>
|
||||||
editor.onDidDispose(() =>
|
editor.onDispose(() =>
|
||||||
this.handleEditorDidDispose(editor.editor.uri.toString())
|
this.handleEditorDidDispose(editor.uri.toString())
|
||||||
),
|
),
|
||||||
(editor) =>
|
(editor) =>
|
||||||
editor.editor.onDocumentContentChanged((event) =>
|
editor.onDocumentContentChanged((event) =>
|
||||||
this.handleDocumentContentChange(editor, event)
|
this.handleDocumentContentChange(editor, event)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -301,24 +439,13 @@ export class CompilerErrors
|
|||||||
]);
|
]);
|
||||||
const currentError = this.errors[0];
|
const currentError = this.errors[0];
|
||||||
if (currentError) {
|
if (currentError) {
|
||||||
await this.markAsCurrentError(currentError);
|
await this.markAsCurrentError(currentError, {
|
||||||
|
forceReselect: true,
|
||||||
|
reveal: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async filter(
|
|
||||||
errors: CoreError.ErrorLocation[]
|
|
||||||
): Promise<CoreError.ErrorLocation[]> {
|
|
||||||
if (!errors.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
await this.preferences.ready;
|
|
||||||
if (this.preferences['arduino.compile.experimental']) {
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
// Always shows maximum one error; hence the code lens navigation is unavailable.
|
|
||||||
return [errors[0]];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async decorateEditors(
|
private async decorateEditors(
|
||||||
errors: Map<string, CoreError.ErrorLocation[]>
|
errors: Map<string, CoreError.ErrorLocation[]>
|
||||||
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
||||||
@@ -342,11 +469,11 @@ export class CompilerErrors
|
|||||||
uri: string,
|
uri: string,
|
||||||
errors: CoreError.ErrorLocation[]
|
errors: CoreError.ErrorLocation[]
|
||||||
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
||||||
const editor = await this.editorManager.getByUri(new URI(uri));
|
const editor = await this.monacoEditor(uri);
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return { dispose: Disposable.NULL, errors: [] };
|
return { dispose: Disposable.NULL, errors: [] };
|
||||||
}
|
}
|
||||||
const oldDecorations = editor.editor.deltaDecorations({
|
const oldDecorations = editor.deltaDecorations({
|
||||||
oldDecorations: [],
|
oldDecorations: [],
|
||||||
newDecorations: errors.map((error) =>
|
newDecorations: errors.map((error) =>
|
||||||
this.compilerErrorDecoration(error.location.range)
|
this.compilerErrorDecoration(error.location.range)
|
||||||
@@ -355,13 +482,19 @@ export class CompilerErrors
|
|||||||
return {
|
return {
|
||||||
dispose: Disposable.create(() => {
|
dispose: Disposable.create(() => {
|
||||||
if (editor) {
|
if (editor) {
|
||||||
editor.editor.deltaDecorations({
|
editor.deltaDecorations({
|
||||||
oldDecorations,
|
oldDecorations,
|
||||||
newDecorations: [],
|
newDecorations: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
errors: oldDecorations.map((id) => ({ id, uri })),
|
errors: oldDecorations.map((id, index) => ({
|
||||||
|
id,
|
||||||
|
uri,
|
||||||
|
rangesInOutput: errors[index].rangesInOutput.map((range) =>
|
||||||
|
this.p2m.asRange(range)
|
||||||
|
),
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +504,7 @@ export class CompilerErrors
|
|||||||
options: {
|
options: {
|
||||||
isWholeLine: true,
|
isWholeLine: true,
|
||||||
className: 'compiler-error',
|
className: 'compiler-error',
|
||||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -379,11 +512,10 @@ export class CompilerErrors
|
|||||||
/**
|
/**
|
||||||
* Tracks the selection in all editors that have an error. If the editor selection overlaps one of the compiler error's range, mark as current error.
|
* Tracks the selection in all editors that have an error. If the editor selection overlaps one of the compiler error's range, mark as current error.
|
||||||
*/
|
*/
|
||||||
private handleSelectionChange(editor: EditorWidget, selection: Range): void {
|
private handleSelectionChange(
|
||||||
const monacoEditor = this.monacoEditor(editor);
|
monacoEditor: MonacoEditor,
|
||||||
if (!monacoEditor) {
|
selection: Range
|
||||||
return;
|
): void {
|
||||||
}
|
|
||||||
const uri = monacoEditor.uri.toString();
|
const uri = monacoEditor.uri.toString();
|
||||||
const monacoSelection = this.p2m.asRange(selection);
|
const monacoSelection = this.p2m.asRange(selection);
|
||||||
console.log(
|
console.log(
|
||||||
@@ -418,12 +550,13 @@ export class CompilerErrors
|
|||||||
console.trace('No match');
|
console.trace('No match');
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
const error = this.errors
|
const errorsPerResource = this.errors.filter((error) => error.uri === uri);
|
||||||
.filter((error) => error.uri === uri)
|
const rangesPerResource = ErrorDecoration.rangeOf(
|
||||||
.map((error) => ({
|
monacoEditor,
|
||||||
error,
|
errorsPerResource
|
||||||
range: ErrorDecoration.rangeOf(error, monacoEditor),
|
);
|
||||||
}))
|
const error = rangesPerResource
|
||||||
|
.map((range, index) => ({ error: errorsPerResource[index], range }))
|
||||||
.map(({ error, range }) => {
|
.map(({ error, range }) => {
|
||||||
if (range) {
|
if (range) {
|
||||||
const priority = calculatePriority(range, monacoSelection);
|
const priority = calculatePriority(range, monacoSelection);
|
||||||
@@ -464,66 +597,77 @@ export class CompilerErrors
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a document change "destroys" the range of the decoration, the decoration must be removed.
|
* If the text document changes in the line where compiler errors are, the compiler errors will be removed.
|
||||||
*/
|
*/
|
||||||
private handleDocumentContentChange(
|
private handleDocumentContentChange(
|
||||||
editor: EditorWidget,
|
monacoEditor: MonacoEditor,
|
||||||
event: TextDocumentChangeEvent
|
event: TextDocumentChangeEvent
|
||||||
): void {
|
): void {
|
||||||
const monacoEditor = this.monacoEditor(editor);
|
const errorsPerResource = this.errors.filter(
|
||||||
if (!monacoEditor) {
|
(error) => error.uri === event.document.uri
|
||||||
return;
|
|
||||||
}
|
|
||||||
// A decoration location can be "destroyed", hence should be deleted when:
|
|
||||||
// - deleting range (start != end AND text is empty)
|
|
||||||
// - inserting text into range (start != end AND text is not empty)
|
|
||||||
// Filter unrelated delta changes to spare the CPU.
|
|
||||||
const relevantChanges = event.contentChanges.filter(
|
|
||||||
({ range: { start, end } }) =>
|
|
||||||
start.line !== end.line || start.character !== end.character
|
|
||||||
);
|
);
|
||||||
if (!relevantChanges.length) {
|
let editorOrModel: MonacoEditor | ITextModel = monacoEditor;
|
||||||
return;
|
const doc = event.document;
|
||||||
|
if (doc instanceof MonacoEditorModel) {
|
||||||
|
editorOrModel = doc.textEditorModel;
|
||||||
}
|
}
|
||||||
|
const rangesPerResource = ErrorDecoration.rangeOf(
|
||||||
const resolvedMarkers = this.errors
|
editorOrModel,
|
||||||
.filter((error) => error.uri === event.document.uri)
|
errorsPerResource
|
||||||
.map((error, index) => {
|
);
|
||||||
const range = ErrorDecoration.rangeOf(error, monacoEditor);
|
const resolvedDecorations = rangesPerResource.map((range, index) => ({
|
||||||
if (range) {
|
error: errorsPerResource[index],
|
||||||
return { error, range, index };
|
range,
|
||||||
}
|
}));
|
||||||
return undefined;
|
const decoratorsToRemove = event.contentChanges
|
||||||
})
|
|
||||||
.filter(notEmpty);
|
|
||||||
|
|
||||||
const decorationIdsToRemove = relevantChanges
|
|
||||||
.map(({ range }) => this.p2m.asRange(range))
|
.map(({ range }) => this.p2m.asRange(range))
|
||||||
.map((changeRange) =>
|
.map((changedRange) =>
|
||||||
resolvedMarkers.filter(({ range: decorationRange }) =>
|
resolvedDecorations
|
||||||
changeRange.containsRange(decorationRange)
|
.filter(({ range: decorationRange }) => {
|
||||||
)
|
if (!decorationRange) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const affects =
|
||||||
|
changedRange.startLineNumber <= decorationRange.startLineNumber &&
|
||||||
|
changedRange.endLineNumber >= decorationRange.endLineNumber;
|
||||||
|
console.log(
|
||||||
|
'compiler-errors',
|
||||||
|
`decoration range: ${decorationRange.toString()}, change range: ${changedRange.toString()}, affects: ${affects}`
|
||||||
|
);
|
||||||
|
return affects;
|
||||||
|
})
|
||||||
|
.map(({ error }) => {
|
||||||
|
const index = this.errors.findIndex((candidate) =>
|
||||||
|
ErrorDecorationRef.sameAs(candidate, error)
|
||||||
|
);
|
||||||
|
return index !== -1 ? { error, index } : undefined;
|
||||||
|
})
|
||||||
|
.filter(notEmpty)
|
||||||
)
|
)
|
||||||
.reduce((acc, curr) => acc.concat(curr), [])
|
.reduce((acc, curr) => acc.concat(curr), [])
|
||||||
.map(({ error, index }) => {
|
.sort((left, right) => left.index - right.index); // highest index last
|
||||||
this.errors.splice(index, 1);
|
|
||||||
return error.id;
|
if (decoratorsToRemove.length) {
|
||||||
});
|
let i = decoratorsToRemove.length;
|
||||||
if (!decorationIdsToRemove.length) {
|
while (i--) {
|
||||||
return;
|
this.errors.splice(decoratorsToRemove[i].index, 1);
|
||||||
}
|
}
|
||||||
monacoEditor.getControl().deltaDecorations(decorationIdsToRemove, []);
|
monacoEditor.getControl().deltaDecorations(
|
||||||
|
decoratorsToRemove.map(({ error }) => error.id),
|
||||||
|
[]
|
||||||
|
);
|
||||||
this.onDidChangeEmitter.fire(this);
|
this.onDidChangeEmitter.fire(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async trackEditors(
|
private async trackEditors(
|
||||||
errors: Map<string, CoreError.ErrorLocation[]>,
|
errors: Map<string, CoreError.ErrorLocation[]>,
|
||||||
...track: ((editor: EditorWidget) => Disposable)[]
|
...track: ((editor: MonacoEditor) => Disposable)[]
|
||||||
): Promise<Disposable> {
|
): Promise<Disposable> {
|
||||||
return new DisposableCollection(
|
return new DisposableCollection(
|
||||||
...(await Promise.all(
|
...(await Promise.all(
|
||||||
Array.from(errors.keys()).map(async (uri) => {
|
Array.from(errors.keys()).map(async (uri) => {
|
||||||
const editor = await this.editorManager.getByUri(new URI(uri));
|
const editor = await this.monacoEditor(uri);
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return Disposable.NULL;
|
return Disposable.NULL;
|
||||||
}
|
}
|
||||||
@@ -533,15 +677,18 @@ export class CompilerErrors
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async markAsCurrentError(error: ErrorDecoration): Promise<void> {
|
private async markAsCurrentError(
|
||||||
|
ref: ErrorDecorationRef,
|
||||||
|
options?: { forceReselect?: boolean; reveal?: boolean }
|
||||||
|
): Promise<void> {
|
||||||
const index = this.errors.findIndex((candidate) =>
|
const index = this.errors.findIndex((candidate) =>
|
||||||
ErrorDecoration.sameAs(candidate, error)
|
ErrorDecorationRef.sameAs(candidate, ref)
|
||||||
);
|
);
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'compiler-errors',
|
'compiler-errors',
|
||||||
`Failed to mark error ${
|
`Failed to mark error ${
|
||||||
error.id
|
ref.id
|
||||||
} as the current one. Error is unknown. Known errors are: ${this.errors.map(
|
} as the current one. Error is unknown. Known errors are: ${this.errors.map(
|
||||||
({ id }) => id
|
({ id }) => id
|
||||||
)}`
|
)}`
|
||||||
@@ -550,15 +697,18 @@ export class CompilerErrors
|
|||||||
}
|
}
|
||||||
const newError = this.errors[index];
|
const newError = this.errors[index];
|
||||||
if (
|
if (
|
||||||
|
options?.forceReselect ||
|
||||||
!this.currentError ||
|
!this.currentError ||
|
||||||
!ErrorDecoration.sameAs(this.currentError, newError)
|
!ErrorDecorationRef.sameAs(this.currentError, newError)
|
||||||
) {
|
) {
|
||||||
this.currentError = this.errors[index];
|
this.currentError = this.errors[index];
|
||||||
console.log(
|
console.log(
|
||||||
'compiler-errors',
|
'compiler-errors',
|
||||||
`Current error changed to ${this.currentError.id}`
|
`Current error changed to ${this.currentError.id}`
|
||||||
);
|
);
|
||||||
|
if (options?.reveal) {
|
||||||
this.currentErrorDidChangEmitter.fire(this.currentError);
|
this.currentErrorDidChangEmitter.fire(this.currentError);
|
||||||
|
}
|
||||||
this.onDidChangeEmitter.fire(this);
|
this.onDidChangeEmitter.fire(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -593,32 +743,33 @@ export class CompilerErrors
|
|||||||
}
|
}
|
||||||
console.warn(
|
console.warn(
|
||||||
'compiler-errors',
|
'compiler-errors',
|
||||||
`could not found editor widget for URI: ${uri}`
|
`could not find editor widget for URI: ${uri}`
|
||||||
);
|
);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private groupByResource(
|
private groupBy<K, V>(
|
||||||
errors: CoreError.ErrorLocation[]
|
elements: V[],
|
||||||
): Map<string, CoreError.ErrorLocation[]> {
|
extractKey: (element: V) => K
|
||||||
return errors.reduce((acc, curr) => {
|
): Map<K, V[]> {
|
||||||
const {
|
return elements.reduce((acc, curr) => {
|
||||||
location: { uri },
|
const key = extractKey(curr);
|
||||||
} = curr;
|
let values = acc.get(key);
|
||||||
let errors = acc.get(uri);
|
if (!values) {
|
||||||
if (!errors) {
|
values = [];
|
||||||
errors = [];
|
acc.set(key, values);
|
||||||
acc.set(uri, errors);
|
|
||||||
}
|
}
|
||||||
errors.push(curr);
|
values.push(curr);
|
||||||
return acc;
|
return acc;
|
||||||
}, new Map<string, CoreError.ErrorLocation[]>());
|
}, new Map<K, V[]>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private monacoEditor(widget: EditorWidget): MonacoEditor | undefined;
|
private monacoEditor(widget: EditorWidget): MonacoEditor | undefined;
|
||||||
private monacoEditor(uri: string): Promise<MonacoEditor | undefined>;
|
|
||||||
private monacoEditor(
|
private monacoEditor(
|
||||||
uriOrWidget: string | EditorWidget
|
uri: string | monaco.Uri
|
||||||
|
): Promise<MonacoEditor | undefined>;
|
||||||
|
private monacoEditor(
|
||||||
|
uriOrWidget: string | monaco.Uri | EditorWidget
|
||||||
): MaybePromise<MonacoEditor | undefined> {
|
): MaybePromise<MonacoEditor | undefined> {
|
||||||
if (uriOrWidget instanceof EditorWidget) {
|
if (uriOrWidget instanceof EditorWidget) {
|
||||||
const editor = uriOrWidget.editor;
|
const editor = uriOrWidget.editor;
|
||||||
@@ -646,5 +797,8 @@ export namespace CompilerErrors {
|
|||||||
export const PREVIOUS_ERROR: Command = {
|
export const PREVIOUS_ERROR: Command = {
|
||||||
id: 'arduino-editor-previous-error',
|
id: 'arduino-editor-previous-error',
|
||||||
};
|
};
|
||||||
|
export const MARK_AS_CURRENT: Command = {
|
||||||
|
id: 'arduino-editor-mark-as-current-error',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -49,13 +49,18 @@ import {
|
|||||||
Sketch,
|
Sketch,
|
||||||
CoreService,
|
CoreService,
|
||||||
CoreError,
|
CoreError,
|
||||||
|
ResponseServiceClient,
|
||||||
} from '../../common/protocol';
|
} from '../../common/protocol';
|
||||||
import { ArduinoPreferences } from '../arduino-preferences';
|
import { ArduinoPreferences } from '../arduino-preferences';
|
||||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
import { CoreErrorHandler } from './core-error-handler';
|
|
||||||
import { nls } from '@theia/core';
|
import { nls } from '@theia/core';
|
||||||
import { OutputChannelManager } from '../theia/output/output-channel';
|
import { OutputChannelManager } from '../theia/output/output-channel';
|
||||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||||
|
import { ExecuteWithProgress } from '../../common/protocol/progressible';
|
||||||
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||||
|
import { NotificationManager } from '../theia/messages/notifications-manager';
|
||||||
|
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Command,
|
Command,
|
||||||
@@ -167,18 +172,39 @@ export abstract class SketchContribution extends Contribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CoreServiceContribution extends SketchContribution {
|
export abstract class CoreServiceContribution extends SketchContribution {
|
||||||
@inject(CoreService)
|
@inject(BoardsDataStore)
|
||||||
protected readonly coreService: CoreService;
|
protected readonly boardsDataStore: BoardsDataStore;
|
||||||
|
|
||||||
@inject(CoreErrorHandler)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly coreErrorHandler: CoreErrorHandler;
|
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
|
@inject(CoreService)
|
||||||
|
private readonly coreService: CoreService;
|
||||||
|
|
||||||
@inject(ClipboardService)
|
@inject(ClipboardService)
|
||||||
private readonly clipboardService: ClipboardService;
|
private readonly clipboardService: ClipboardService;
|
||||||
|
|
||||||
|
@inject(ResponseServiceClient)
|
||||||
|
private readonly responseService: ResponseServiceClient;
|
||||||
|
|
||||||
|
@inject(NotificationManager)
|
||||||
|
private readonly notificationManager: NotificationManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the internal (Theia) ID of the notification that is currently visible.
|
||||||
|
* It's stored here as a field to be able to close it before executing any new core command (such as verify, upload, etc.)
|
||||||
|
*/
|
||||||
|
private visibleNotificationId: string | undefined;
|
||||||
|
|
||||||
|
protected clearVisibleNotification(): void {
|
||||||
|
if (this.visibleNotificationId) {
|
||||||
|
this.notificationManager.clear(this.visibleNotificationId);
|
||||||
|
this.visibleNotificationId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected handleError(error: unknown): void {
|
protected handleError(error: unknown): void {
|
||||||
this.coreErrorHandler.tryHandle(error);
|
|
||||||
this.tryToastErrorMessage(error);
|
this.tryToastErrorMessage(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,10 +222,17 @@ export class CoreServiceContribution extends SketchContribution {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
if (message) {
|
if (message) {
|
||||||
|
if (message.includes('Missing FQBN (Fully Qualified Board Name)')) {
|
||||||
|
message = nls.localize(
|
||||||
|
'arduino/coreContribution/noBoardSelected',
|
||||||
|
'No board selected. Please select your Arduino board from the Tools > Board menu.'
|
||||||
|
);
|
||||||
|
}
|
||||||
const copyAction = nls.localize(
|
const copyAction = nls.localize(
|
||||||
'arduino/coreContribution/copyError',
|
'arduino/coreContribution/copyError',
|
||||||
'Copy error messages'
|
'Copy error messages'
|
||||||
);
|
);
|
||||||
|
this.visibleNotificationId = this.notificationId(message, copyAction);
|
||||||
this.messageService.error(message, copyAction).then(async (action) => {
|
this.messageService.error(message, copyAction).then(async (action) => {
|
||||||
if (action === copyAction) {
|
if (action === copyAction) {
|
||||||
const content = await this.outputChannelManager.contentOfChannel(
|
const content = await this.outputChannelManager.contentOfChannel(
|
||||||
@@ -214,6 +247,33 @@ export class CoreServiceContribution extends SketchContribution {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async doWithProgress<T>(options: {
|
||||||
|
progressText: string;
|
||||||
|
keepOutput?: boolean;
|
||||||
|
task: (progressId: string, coreService: CoreService) => Promise<T>;
|
||||||
|
}): Promise<T> {
|
||||||
|
const { progressText, keepOutput, task } = options;
|
||||||
|
this.outputChannelManager
|
||||||
|
.getChannel('Arduino')
|
||||||
|
.show({ preserveFocus: true });
|
||||||
|
const result = await ExecuteWithProgress.doWithProgress({
|
||||||
|
messageService: this.messageService,
|
||||||
|
responseService: this.responseService,
|
||||||
|
progressText,
|
||||||
|
run: ({ progressId }) => task(progressId, this.coreService),
|
||||||
|
keepOutput,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private notificationId(message: string, ...actions: string[]): string {
|
||||||
|
return this.notificationManager.getMessageId({
|
||||||
|
text: message,
|
||||||
|
actions,
|
||||||
|
type: MessageType.Error,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Contribution {
|
export namespace Contribution {
|
||||||
|
@@ -141,6 +141,11 @@ ${value}
|
|||||||
label: nls.localize('arduino/editor/decreaseIndent', 'Decrease Indent'),
|
label: nls.localize('arduino/editor/decreaseIndent', 'Decrease Indent'),
|
||||||
order: '2',
|
order: '2',
|
||||||
});
|
});
|
||||||
|
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
|
||||||
|
commandId: EditContributions.Commands.AUTO_FORMAT.id,
|
||||||
|
label: nls.localize('arduino/editor/autoFormat', 'Auto Format'),
|
||||||
|
order: '3',
|
||||||
|
});
|
||||||
|
|
||||||
registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, {
|
registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, {
|
||||||
commandId: EditContributions.Commands.INCREASE_FONT_SIZE.id,
|
commandId: EditContributions.Commands.INCREASE_FONT_SIZE.id,
|
||||||
@@ -248,10 +253,13 @@ ${value}
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async current(): Promise<ICodeEditor | StandaloneCodeEditor | undefined> {
|
protected async current(): Promise<
|
||||||
|
ICodeEditor | StandaloneCodeEditor | undefined
|
||||||
|
> {
|
||||||
return (
|
return (
|
||||||
this.codeEditorService.getFocusedCodeEditor() ||
|
this.codeEditorService.getFocusedCodeEditor() ||
|
||||||
this.codeEditorService.getActiveCodeEditor() || undefined
|
this.codeEditorService.getActiveCodeEditor() ||
|
||||||
|
undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
import { LocalStorageService } from '@theia/core/lib/browser';
|
import { LocalStorageService } from '@theia/core/lib/browser';
|
||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
import { BoardsService, LibraryService } from '../../common/protocol';
|
import {
|
||||||
|
BoardsService,
|
||||||
|
LibraryLocation,
|
||||||
|
LibraryService,
|
||||||
|
} from '../../common/protocol';
|
||||||
import { Contribution } from './contribution';
|
import { Contribution } from './contribution';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@@ -43,7 +47,7 @@ export class FirstStartupInstaller extends Contribution {
|
|||||||
// If arduino:avr installation fails because it's already installed we don't want to retry on next start-up
|
// If arduino:avr installation fails because it's already installed we don't want to retry on next start-up
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} else {
|
} else {
|
||||||
// But if there is any other error (e.g.: no interntet cconnection), we want to retry next time
|
// But if there is any other error (e.g.: no Internet connection), we want to retry next time
|
||||||
avrPackageError = e;
|
avrPackageError = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,6 +61,7 @@ export class FirstStartupInstaller extends Contribution {
|
|||||||
item: builtInLibrary,
|
item: builtInLibrary,
|
||||||
installDependencies: true,
|
installDependencies: true,
|
||||||
noOverwrite: true, // We don't want to automatically replace custom libraries the user might already have in place
|
noOverwrite: true, // We don't want to automatically replace custom libraries the user might already have in place
|
||||||
|
installLocation: LibraryLocation.BUILTIN,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// There's no error code, I need to parse the error message: https://github.com/arduino/arduino-cli/commit/2ea3608453b17b1157f8a1dc892af2e13e40f4f0#diff-1de7569144d4e260f8dde0e0d00a4e2a218c57966d583da1687a70d518986649R95
|
// There's no error code, I need to parse the error message: https://github.com/arduino/arduino-cli/commit/2ea3608453b17b1157f8a1dc892af2e13e40f4f0#diff-1de7569144d4e260f8dde0e0d00a4e2a218c57966d583da1687a70d518986649R95
|
||||||
@@ -64,7 +69,7 @@ export class FirstStartupInstaller extends Contribution {
|
|||||||
// If Arduino_BuiltIn installation fails because it's already installed we don't want to retry on next start-up
|
// If Arduino_BuiltIn installation fails because it's already installed we don't want to retry on next start-up
|
||||||
console.log('error installing core', e);
|
console.log('error installing core', e);
|
||||||
} else {
|
} else {
|
||||||
// But if there is any other error (e.g.: no interntet cconnection), we want to retry next time
|
// But if there is any other error (e.g.: no Internet connection), we want to retry next time
|
||||||
builtInLibraryError = e;
|
builtInLibraryError = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,8 +2,7 @@ import { MaybePromise } from '@theia/core';
|
|||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
import * as monaco from '@theia/monaco-editor-core';
|
import * as monaco from '@theia/monaco-editor-core';
|
||||||
import { Formatter } from '../../common/protocol/formatter';
|
import { Formatter } from '../../common/protocol/formatter';
|
||||||
import { InoSelector } from '../ino-selectors';
|
import { InoSelector } from '../selectors';
|
||||||
import { fullRange } from '../utils/monaco';
|
|
||||||
import { Contribution, URI } from './contribution';
|
import { Contribution, URI } from './contribution';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@@ -40,7 +39,7 @@ export class Format
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
_token: monaco.CancellationToken
|
_token: monaco.CancellationToken
|
||||||
): Promise<monaco.languages.TextEdit[]> {
|
): Promise<monaco.languages.TextEdit[]> {
|
||||||
const range = fullRange(model);
|
const range = model.getFullModelRange();
|
||||||
const text = await this.format(model, range, options);
|
const text = await this.format(model, range, options);
|
||||||
return [{ range, text }];
|
return [{ range, text }];
|
||||||
}
|
}
|
||||||
|
@@ -41,7 +41,9 @@ export class Help extends Contribution {
|
|||||||
);
|
);
|
||||||
registry.registerCommand(
|
registry.registerCommand(
|
||||||
Help.Commands.ENVIRONMENT,
|
Help.Commands.ENVIRONMENT,
|
||||||
createOpenHandler('https://www.arduino.cc/en/Guide/Environment')
|
createOpenHandler(
|
||||||
|
'https://docs.arduino.cc/software/ide-v2/tutorials/getting-started-ide-v2'
|
||||||
|
)
|
||||||
);
|
);
|
||||||
registry.registerCommand(
|
registry.registerCommand(
|
||||||
Help.Commands.TROUBLESHOOTING,
|
Help.Commands.TROUBLESHOOTING,
|
||||||
|
@@ -145,6 +145,7 @@ export class InoLanguage extends SketchContribution {
|
|||||||
name: name ? `"${name}"` : undefined,
|
name: name ? `"${name}"` : undefined,
|
||||||
},
|
},
|
||||||
realTimeDiagnostics,
|
realTimeDiagnostics,
|
||||||
|
silentOutput: true,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { nls } from '@theia/core/lib/common/nls';
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
import { injectable } from '@theia/core/shared/inversify';
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
import type { EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
|
import type { EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
|
||||||
|
import { Later } from '../../common/nls';
|
||||||
import { SketchesError } from '../../common/protocol';
|
import { SketchesError } from '../../common/protocol';
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
@@ -41,9 +42,7 @@ export class OpenSketchFiles extends SketchContribution {
|
|||||||
sketch.name
|
sketch.name
|
||||||
);
|
);
|
||||||
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
|
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
|
||||||
this.messageService
|
this.messageService.info(message, Later, yes).then((answer) => {
|
||||||
.info(message, nls.localize('arduino/common/later', 'Later'), yes)
|
|
||||||
.then(async (answer) => {
|
|
||||||
if (answer === yes) {
|
if (answer === yes) {
|
||||||
this.commandService.executeCommand(
|
this.commandService.executeCommand(
|
||||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||||
|
@@ -57,6 +57,7 @@ export class SaveAsSketch extends SketchContribution {
|
|||||||
execOnlyIfTemp,
|
execOnlyIfTemp,
|
||||||
openAfterMove,
|
openAfterMove,
|
||||||
wipeOriginal,
|
wipeOriginal,
|
||||||
|
markAsRecentlyOpened,
|
||||||
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
|
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
@@ -77,15 +78,11 @@ export class SaveAsSketch extends SketchContribution {
|
|||||||
const exists = await this.fileService.exists(
|
const exists = await this.fileService.exists(
|
||||||
sketchDirUri.resolve(sketch.name)
|
sketchDirUri.resolve(sketch.name)
|
||||||
);
|
);
|
||||||
const defaultUri = exists
|
const defaultUri = sketchDirUri.resolve(
|
||||||
? sketchDirUri.resolve(
|
exists
|
||||||
sketchDirUri
|
? `${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}`
|
||||||
.resolve(
|
: sketch.name
|
||||||
`${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}`
|
);
|
||||||
)
|
|
||||||
.toString()
|
|
||||||
)
|
|
||||||
: sketchDirUri.resolve(sketch.name);
|
|
||||||
const defaultPath = await this.fileService.fsPath(defaultUri);
|
const defaultPath = await this.fileService.fsPath(defaultUri);
|
||||||
const { filePath, canceled } = await remote.dialog.showSaveDialog({
|
const { filePath, canceled } = await remote.dialog.showSaveDialog({
|
||||||
title: nls.localize(
|
title: nls.localize(
|
||||||
@@ -106,18 +103,22 @@ export class SaveAsSketch extends SketchContribution {
|
|||||||
});
|
});
|
||||||
if (workspaceUri) {
|
if (workspaceUri) {
|
||||||
await this.saveOntoCopiedSketch(sketch.mainFileUri, sketch.uri, workspaceUri);
|
await this.saveOntoCopiedSketch(sketch.mainFileUri, sketch.uri, workspaceUri);
|
||||||
|
if (markAsRecentlyOpened) {
|
||||||
|
this.sketchService.markAsRecentlyOpened(workspaceUri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (workspaceUri && openAfterMove) {
|
if (workspaceUri && openAfterMove) {
|
||||||
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.windowService.setSafeToShutDown();
|
this.windowService.setSafeToShutDown();
|
||||||
|
if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) {
|
||||||
|
// This window will navigate away.
|
||||||
|
// Explicitly stop the contribution to dispose the file watcher before deleting the temp sketch.
|
||||||
|
// Otherwise, users might see irrelevant _Unable to watch for file changes in this large workspace._ notification.
|
||||||
|
// https://github.com/arduino/arduino-ide/issues/39.
|
||||||
|
this.sketchServiceClient.onStop();
|
||||||
|
// TODO: consider implementing the temp sketch deletion the following way:
|
||||||
|
// Open the other sketch with a `delete the temp sketch` startup-task.
|
||||||
|
this.sketchService.notifyDeleteSketch(sketch); // This is a notification and will execute on the backend.
|
||||||
|
}
|
||||||
this.workspaceService.open(new URI(workspaceUri), {
|
this.workspaceService.open(new URI(workspaceUri), {
|
||||||
preserveWindow: true,
|
preserveWindow: true,
|
||||||
});
|
});
|
||||||
@@ -174,12 +175,14 @@ export namespace SaveAsSketch {
|
|||||||
* Ignored if `openAfterMove` is `false`.
|
* Ignored if `openAfterMove` is `false`.
|
||||||
*/
|
*/
|
||||||
readonly wipeOriginal?: boolean;
|
readonly wipeOriginal?: boolean;
|
||||||
|
readonly markAsRecentlyOpened?: boolean;
|
||||||
}
|
}
|
||||||
export namespace Options {
|
export namespace Options {
|
||||||
export const DEFAULT: Options = {
|
export const DEFAULT: Options = {
|
||||||
execOnlyIfTemp: false,
|
execOnlyIfTemp: false,
|
||||||
openAfterMove: true,
|
openAfterMove: true,
|
||||||
wipeOriginal: false,
|
wipeOriginal: false,
|
||||||
|
markAsRecentlyOpened: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,10 +30,7 @@ export class SketchFilesTracker extends SketchContribution {
|
|||||||
|
|
||||||
override onReady(): void {
|
override onReady(): void {
|
||||||
this.sketchServiceClient.currentSketch().then(async (sketch) => {
|
this.sketchServiceClient.currentSketch().then(async (sketch) => {
|
||||||
if (
|
if (CurrentSketch.isValid(sketch)) {
|
||||||
CurrentSketch.isValid(sketch) &&
|
|
||||||
!(await this.sketchService.isTemp(sketch))
|
|
||||||
) {
|
|
||||||
this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
|
this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
|
||||||
this.toDisposeOnStop.push(
|
this.toDisposeOnStop.push(
|
||||||
this.fileService.onDidFilesChange(async (event) => {
|
this.fileService.onDidFilesChange(async (event) => {
|
||||||
|
@@ -1,58 +1,49 @@
|
|||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
import { Emitter } from '@theia/core/lib/common/event';
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
import { BoardUserField, CoreService } from '../../common/protocol';
|
import { BoardUserField, CoreService, Port } from '../../common/protocol';
|
||||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
|
||||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
|
||||||
import {
|
import {
|
||||||
CoreServiceContribution,
|
|
||||||
Command,
|
Command,
|
||||||
CommandRegistry,
|
CommandRegistry,
|
||||||
MenuModelRegistry,
|
MenuModelRegistry,
|
||||||
KeybindingRegistry,
|
KeybindingRegistry,
|
||||||
TabBarToolbarRegistry,
|
TabBarToolbarRegistry,
|
||||||
|
CoreServiceContribution,
|
||||||
} from './contribution';
|
} from './contribution';
|
||||||
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
|
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
|
||||||
import { DisposableCollection, nls } from '@theia/core/lib/common';
|
import { deepClone, DisposableCollection, nls } from '@theia/core/lib/common';
|
||||||
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||||
|
import type { VerifySketchParams } from './verify-sketch';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UploadSketch extends CoreServiceContribution {
|
export class UploadSketch extends CoreServiceContribution {
|
||||||
@inject(MenuModelRegistry)
|
@inject(MenuModelRegistry)
|
||||||
protected readonly menuRegistry: MenuModelRegistry;
|
private readonly menuRegistry: MenuModelRegistry;
|
||||||
|
|
||||||
@inject(BoardsDataStore)
|
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
|
||||||
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
|
||||||
|
|
||||||
@inject(UserFieldsDialog)
|
@inject(UserFieldsDialog)
|
||||||
protected readonly userFieldsDialog: UserFieldsDialog;
|
private readonly userFieldsDialog: UserFieldsDialog;
|
||||||
|
|
||||||
protected cachedUserFields: Map<string, BoardUserField[]> = new Map();
|
private boardRequiresUserFields = false;
|
||||||
|
private readonly cachedUserFields: Map<string, BoardUserField[]> = new Map();
|
||||||
|
private readonly menuActionsDisposables = new DisposableCollection();
|
||||||
|
|
||||||
protected readonly onDidChangeEmitter = new Emitter<Readonly<void>>();
|
private readonly onDidChangeEmitter = new Emitter<void>();
|
||||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
private readonly onDidChange = this.onDidChangeEmitter.event;
|
||||||
|
private uploadInProgress = false;
|
||||||
protected uploadInProgress = false;
|
|
||||||
protected boardRequiresUserFields = false;
|
|
||||||
|
|
||||||
protected readonly menuActionsDisposables = new DisposableCollection();
|
|
||||||
|
|
||||||
protected override init(): void {
|
protected override init(): void {
|
||||||
super.init();
|
super.init();
|
||||||
this.boardsServiceClientImpl.onBoardsConfigChanged(async () => {
|
this.boardsServiceProvider.onBoardsConfigChanged(async () => {
|
||||||
const userFields =
|
const userFields =
|
||||||
await this.boardsServiceClientImpl.selectedBoardUserFields();
|
await this.boardsServiceProvider.selectedBoardUserFields();
|
||||||
this.boardRequiresUserFields = userFields.length > 0;
|
this.boardRequiresUserFields = userFields.length > 0;
|
||||||
this.registerMenus(this.menuRegistry);
|
this.registerMenus(this.menuRegistry);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectedFqbnAddress(): string {
|
private selectedFqbnAddress(): string {
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
const fqbn = boardsConfig.selectedBoard?.fqbn;
|
const fqbn = boardsConfig.selectedBoard?.fqbn;
|
||||||
if (!fqbn) {
|
if (!fqbn) {
|
||||||
return '';
|
return '';
|
||||||
@@ -70,13 +61,14 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, {
|
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, {
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
const key = this.selectedFqbnAddress();
|
const key = this.selectedFqbnAddress();
|
||||||
if (!key) {
|
if (
|
||||||
return;
|
this.boardRequiresUserFields &&
|
||||||
}
|
key &&
|
||||||
if (this.boardRequiresUserFields && !this.cachedUserFields.has(key)) {
|
!this.cachedUserFields.has(key)
|
||||||
|
) {
|
||||||
// Deep clone the array of board fields to avoid editing the cached ones
|
// Deep clone the array of board fields to avoid editing the cached ones
|
||||||
this.userFieldsDialog.value = (
|
this.userFieldsDialog.value = (
|
||||||
await this.boardsServiceClientImpl.selectedBoardUserFields()
|
await this.boardsServiceProvider.selectedBoardUserFields()
|
||||||
).map((f) => ({ ...f }));
|
).map((f) => ({ ...f }));
|
||||||
const result = await this.userFieldsDialog.open();
|
const result = await this.userFieldsDialog.open();
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@@ -98,8 +90,7 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
const cached = this.cachedUserFields.get(key);
|
const cached = this.cachedUserFields.get(key);
|
||||||
// Deep clone the array of board fields to avoid editing the cached ones
|
// Deep clone the array of board fields to avoid editing the cached ones
|
||||||
this.userFieldsDialog.value = (
|
this.userFieldsDialog.value = (
|
||||||
cached ??
|
cached ?? (await this.boardsServiceProvider.selectedBoardUserFields())
|
||||||
(await this.boardsServiceClientImpl.selectedBoardUserFields())
|
|
||||||
).map((f) => ({ ...f }));
|
).map((f) => ({ ...f }));
|
||||||
|
|
||||||
const result = await this.userFieldsDialog.open();
|
const result = await this.userFieldsDialog.open();
|
||||||
@@ -130,7 +121,6 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
|
|
||||||
override registerMenus(registry: MenuModelRegistry): void {
|
override registerMenus(registry: MenuModelRegistry): void {
|
||||||
this.menuActionsDisposables.dispose();
|
this.menuActionsDisposables.dispose();
|
||||||
|
|
||||||
this.menuActionsDisposables.push(
|
this.menuActionsDisposables.push(
|
||||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||||
@@ -153,7 +143,7 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
new PlaceholderMenuNode(
|
new PlaceholderMenuNode(
|
||||||
ArduinoMenus.SKETCH__MAIN_GROUP,
|
ArduinoMenus.SKETCH__MAIN_GROUP,
|
||||||
// commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
|
// commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
|
||||||
UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label!,
|
UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label,
|
||||||
{ order: '2' }
|
{ order: '2' }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -193,54 +183,44 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async uploadSketch(usingProgrammer = false): Promise<void> {
|
async uploadSketch(usingProgrammer = false): Promise<void> {
|
||||||
// even with buttons disabled, better to double check if an upload is already in progress
|
|
||||||
if (this.uploadInProgress) {
|
if (this.uploadInProgress) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
|
||||||
if (!CurrentSketch.isValid(sketch)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// toggle the toolbar button and menu item state.
|
// toggle the toolbar button and menu item state.
|
||||||
// uploadInProgress will be set to false whether the upload fails or not
|
// uploadInProgress will be set to false whether the upload fails or not
|
||||||
this.uploadInProgress = true;
|
this.uploadInProgress = true;
|
||||||
this.coreErrorHandler.reset();
|
this.boardsServiceProvider.snapshotBoardDiscoveryOnUpload();
|
||||||
this.onDidChangeEmitter.fire();
|
this.onDidChangeEmitter.fire();
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
this.clearVisibleNotification();
|
||||||
const [
|
|
||||||
fqbn,
|
|
||||||
{ selectedProgrammer },
|
|
||||||
verify,
|
|
||||||
verbose,
|
|
||||||
sourceOverride,
|
|
||||||
optimizeForDebug,
|
|
||||||
] = await Promise.all([
|
|
||||||
this.boardsDataStore.appendConfigToFqbn(
|
|
||||||
boardsConfig.selectedBoard?.fqbn
|
|
||||||
),
|
|
||||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
|
||||||
this.preferences.get('arduino.upload.verify'),
|
|
||||||
this.preferences.get('arduino.upload.verbose'),
|
|
||||||
this.sourceOverride(),
|
|
||||||
this.commandService.executeCommand<boolean>(
|
|
||||||
'arduino-is-optimize-for-debug'
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const board = {
|
const verifyOptions =
|
||||||
...boardsConfig.selectedBoard,
|
await this.commandService.executeCommand<CoreService.Options.Compile>(
|
||||||
name: boardsConfig.selectedBoard?.name || '',
|
'arduino-verify-sketch',
|
||||||
fqbn,
|
<VerifySketchParams>{
|
||||||
};
|
exportBinaries: false,
|
||||||
let options: CoreService.Upload.Options | undefined = undefined;
|
silent: true,
|
||||||
const { selectedPort } = boardsConfig;
|
}
|
||||||
const port = selectedPort;
|
);
|
||||||
const userFields =
|
if (!verifyOptions) {
|
||||||
this.cachedUserFields.get(this.selectedFqbnAddress()) ?? [];
|
return;
|
||||||
if (userFields.length === 0 && this.boardRequiresUserFields) {
|
}
|
||||||
|
|
||||||
|
const uploadOptions = await this.uploadOptions(
|
||||||
|
usingProgrammer,
|
||||||
|
verifyOptions
|
||||||
|
);
|
||||||
|
if (!uploadOptions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This does not belong here.
|
||||||
|
// IDE2 should not do any preliminary checks but let the CLI fail and then toast a user consumable error message.
|
||||||
|
if (
|
||||||
|
uploadOptions.userFields.length === 0 &&
|
||||||
|
this.boardRequiresUserFields
|
||||||
|
) {
|
||||||
this.messageService.error(
|
this.messageService.error(
|
||||||
nls.localize(
|
nls.localize(
|
||||||
'arduino/sketch/userFieldsNotFoundError',
|
'arduino/sketch/userFieldsNotFoundError',
|
||||||
@@ -250,37 +230,13 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usingProgrammer) {
|
await this.doWithProgress({
|
||||||
const programmer = selectedProgrammer;
|
progressText: nls.localize('arduino/sketch/uploading', 'Uploading...'),
|
||||||
options = {
|
task: (progressId, coreService) =>
|
||||||
sketch,
|
coreService.upload({ ...uploadOptions, progressId }),
|
||||||
board,
|
keepOutput: true,
|
||||||
optimizeForDebug: Boolean(optimizeForDebug),
|
});
|
||||||
programmer,
|
|
||||||
port,
|
|
||||||
verbose,
|
|
||||||
verify,
|
|
||||||
sourceOverride,
|
|
||||||
userFields,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
options = {
|
|
||||||
sketch,
|
|
||||||
board,
|
|
||||||
optimizeForDebug: Boolean(optimizeForDebug),
|
|
||||||
port,
|
|
||||||
verbose,
|
|
||||||
verify,
|
|
||||||
sourceOverride,
|
|
||||||
userFields,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.outputChannelManager.getChannel('Arduino').clear();
|
|
||||||
if (usingProgrammer) {
|
|
||||||
await this.coreService.uploadUsingProgrammer(options);
|
|
||||||
} else {
|
|
||||||
await this.coreService.upload(options);
|
|
||||||
}
|
|
||||||
this.messageService.info(
|
this.messageService.info(
|
||||||
nls.localize('arduino/sketch/doneUploading', 'Done uploading.'),
|
nls.localize('arduino/sketch/doneUploading', 'Done uploading.'),
|
||||||
{ timeout: 3000 }
|
{ timeout: 3000 }
|
||||||
@@ -289,9 +245,78 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
this.handleError(e);
|
this.handleError(e);
|
||||||
} finally {
|
} finally {
|
||||||
this.uploadInProgress = false;
|
this.uploadInProgress = false;
|
||||||
|
this.boardsServiceProvider.attemptPostUploadAutoSelect();
|
||||||
this.onDidChangeEmitter.fire();
|
this.onDidChangeEmitter.fire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async uploadOptions(
|
||||||
|
usingProgrammer: boolean,
|
||||||
|
verifyOptions: CoreService.Options.Compile
|
||||||
|
): Promise<CoreService.Options.Upload | undefined> {
|
||||||
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (!CurrentSketch.isValid(sketch)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const userFields = this.userFields();
|
||||||
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
|
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
|
||||||
|
await Promise.all([
|
||||||
|
verifyOptions.fqbn, // already decorated FQBN
|
||||||
|
this.boardsDataStore.getData(this.sanitizeFqbn(verifyOptions.fqbn)),
|
||||||
|
this.preferences.get('arduino.upload.verify'),
|
||||||
|
this.preferences.get('arduino.upload.verbose'),
|
||||||
|
]);
|
||||||
|
const port = this.maybeUpdatePortProperties(boardsConfig.selectedPort);
|
||||||
|
return {
|
||||||
|
sketch,
|
||||||
|
fqbn,
|
||||||
|
...(usingProgrammer && { programmer }),
|
||||||
|
port,
|
||||||
|
verbose,
|
||||||
|
verify,
|
||||||
|
userFields,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a hack to ensure that the port object has the `properties` when uploading.(https://github.com/arduino/arduino-ide/issues/740)
|
||||||
|
* This method works around a bug when restoring a `port` persisted by an older version of IDE2. See the bug [here](https://github.com/arduino/arduino-ide/pull/1335#issuecomment-1224355236).
|
||||||
|
*
|
||||||
|
* Before the upload, this method checks the available ports and makes sure that the `properties` of an available port, and the port selected by the user have the same `properties`.
|
||||||
|
* This method does not update any state (for example, the `BoardsConfig.Config`) but uses the correct `properties` for the `upload`.
|
||||||
|
*/
|
||||||
|
private maybeUpdatePortProperties(port: Port | undefined): Port | undefined {
|
||||||
|
if (port) {
|
||||||
|
const key = Port.keyOf(port);
|
||||||
|
for (const candidate of this.boardsServiceProvider.availablePorts) {
|
||||||
|
if (key === Port.keyOf(candidate) && candidate.properties) {
|
||||||
|
return {
|
||||||
|
...port,
|
||||||
|
properties: deepClone(candidate.properties),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
private userFields(): BoardUserField[] {
|
||||||
|
return this.cachedUserFields.get(this.selectedFqbnAddress()) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to
|
||||||
|
* `VENDOR:ARCHITECTURE:BOARD_ID` format.
|
||||||
|
* See the details of the `{build.fqbn}` entry in the [specs](https://arduino.github.io/arduino-cli/latest/platform-specification/#global-predefined-properties).
|
||||||
|
*/
|
||||||
|
private sanitizeFqbn(fqbn: string | undefined): string | undefined {
|
||||||
|
if (!fqbn) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const [vendor, arch, id] = fqbn.split(':');
|
||||||
|
return `${vendor}:${arch}:${id}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace UploadSketch {
|
export namespace UploadSketch {
|
||||||
@@ -299,7 +324,7 @@ export namespace UploadSketch {
|
|||||||
export const UPLOAD_SKETCH: Command = {
|
export const UPLOAD_SKETCH: Command = {
|
||||||
id: 'arduino-upload-sketch',
|
id: 'arduino-upload-sketch',
|
||||||
};
|
};
|
||||||
export const UPLOAD_WITH_CONFIGURATION: Command = {
|
export const UPLOAD_WITH_CONFIGURATION: Command & { label: string } = {
|
||||||
id: 'arduino-upload-with-configuration-sketch',
|
id: 'arduino-upload-with-configuration-sketch',
|
||||||
label: nls.localize(
|
label: nls.localize(
|
||||||
'arduino/sketch/configureAndUpload',
|
'arduino/sketch/configureAndUpload',
|
||||||
|
@@ -2,8 +2,6 @@ import { inject, injectable } from '@theia/core/shared/inversify';
|
|||||||
import { Emitter } from '@theia/core/lib/common/event';
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
|
||||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
|
||||||
import {
|
import {
|
||||||
CoreServiceContribution,
|
CoreServiceContribution,
|
||||||
Command,
|
Command,
|
||||||
@@ -14,27 +12,36 @@ import {
|
|||||||
} from './contribution';
|
} from './contribution';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||||
|
import { CoreService } from '../../common/protocol';
|
||||||
|
import { CoreErrorHandler } from './core-error-handler';
|
||||||
|
|
||||||
|
export interface VerifySketchParams {
|
||||||
|
/**
|
||||||
|
* Same as `CoreService.Options.Compile#exportBinaries`
|
||||||
|
*/
|
||||||
|
readonly exportBinaries?: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, there won't be any UI indication of the verify command. It's `false` by default.
|
||||||
|
*/
|
||||||
|
readonly silent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class VerifySketch extends CoreServiceContribution {
|
export class VerifySketch extends CoreServiceContribution {
|
||||||
@inject(BoardsDataStore)
|
@inject(CoreErrorHandler)
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
private readonly coreErrorHandler: CoreErrorHandler;
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
private readonly onDidChangeEmitter = new Emitter<void>();
|
||||||
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
private readonly onDidChange = this.onDidChangeEmitter.event;
|
||||||
|
private verifyInProgress = false;
|
||||||
protected readonly onDidChangeEmitter = new Emitter<Readonly<void>>();
|
|
||||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
|
||||||
|
|
||||||
protected verifyInProgress = false;
|
|
||||||
|
|
||||||
override registerCommands(registry: CommandRegistry): void {
|
override registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
|
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
|
||||||
execute: () => this.verifySketch(),
|
execute: (params?: VerifySketchParams) => this.verifySketch(params),
|
||||||
isEnabled: () => !this.verifyInProgress,
|
isEnabled: () => !this.verifyInProgress,
|
||||||
});
|
});
|
||||||
registry.registerCommand(VerifySketch.Commands.EXPORT_BINARIES, {
|
registry.registerCommand(VerifySketch.Commands.EXPORT_BINARIES, {
|
||||||
execute: () => this.verifySketch(true),
|
execute: () => this.verifySketch({ exportBinaries: true }),
|
||||||
isEnabled: () => !this.verifyInProgress,
|
isEnabled: () => !this.verifyInProgress,
|
||||||
});
|
});
|
||||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
||||||
@@ -84,61 +91,88 @@ export class VerifySketch extends CoreServiceContribution {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifySketch(exportBinaries?: boolean): Promise<void> {
|
protected override handleError(error: unknown): void {
|
||||||
// even with buttons disabled, better to double check if a verify is already in progress
|
this.coreErrorHandler.tryHandle(error);
|
||||||
if (this.verifyInProgress) {
|
super.handleError(error);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// toggle the toolbar button and menu item state.
|
private async verifySketch(
|
||||||
// verifyInProgress will be set to false whether the compilation fails or not
|
params?: VerifySketchParams
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
): Promise<CoreService.Options.Compile | undefined> {
|
||||||
if (!CurrentSketch.isValid(sketch)) {
|
if (this.verifyInProgress) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!params?.silent) {
|
||||||
this.verifyInProgress = true;
|
this.verifyInProgress = true;
|
||||||
this.coreErrorHandler.reset();
|
|
||||||
this.onDidChangeEmitter.fire();
|
this.onDidChangeEmitter.fire();
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
}
|
||||||
const [fqbn, sourceOverride] = await Promise.all([
|
this.clearVisibleNotification();
|
||||||
this.boardsDataStore.appendConfigToFqbn(
|
this.coreErrorHandler.reset();
|
||||||
boardsConfig.selectedBoard?.fqbn
|
|
||||||
|
const options = await this.options(params?.exportBinaries);
|
||||||
|
if (!options) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.doWithProgress({
|
||||||
|
progressText: nls.localize(
|
||||||
|
'arduino/sketch/compile',
|
||||||
|
'Compiling sketch...'
|
||||||
),
|
),
|
||||||
this.sourceOverride(),
|
task: (progressId, coreService) =>
|
||||||
]);
|
coreService.compile({
|
||||||
const board = {
|
...options,
|
||||||
...boardsConfig.selectedBoard,
|
progressId,
|
||||||
name: boardsConfig.selectedBoard?.name || '',
|
}),
|
||||||
fqbn,
|
|
||||||
};
|
|
||||||
const verbose = this.preferences.get('arduino.compile.verbose');
|
|
||||||
const compilerWarnings = this.preferences.get('arduino.compile.warnings');
|
|
||||||
const optimizeForDebug =
|
|
||||||
await this.commandService.executeCommand<boolean>(
|
|
||||||
'arduino-is-optimize-for-debug'
|
|
||||||
);
|
|
||||||
this.outputChannelManager.getChannel('Arduino').clear();
|
|
||||||
await this.coreService.compile({
|
|
||||||
sketch,
|
|
||||||
board,
|
|
||||||
optimizeForDebug: Boolean(optimizeForDebug),
|
|
||||||
verbose,
|
|
||||||
exportBinaries,
|
|
||||||
sourceOverride,
|
|
||||||
compilerWarnings,
|
|
||||||
});
|
});
|
||||||
this.messageService.info(
|
this.messageService.info(
|
||||||
nls.localize('arduino/sketch/doneCompiling', 'Done compiling.'),
|
nls.localize('arduino/sketch/doneCompiling', 'Done compiling.'),
|
||||||
{ timeout: 3000 }
|
{ timeout: 3000 }
|
||||||
);
|
);
|
||||||
|
// Returns with the used options for the compilation
|
||||||
|
// so that follow-up tasks (such as upload) can reuse the compiled code.
|
||||||
|
// Note that the `fqbn` is already decorated with the board settings, if any.
|
||||||
|
return options;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.handleError(e);
|
this.handleError(e);
|
||||||
|
return undefined;
|
||||||
} finally {
|
} finally {
|
||||||
this.verifyInProgress = false;
|
this.verifyInProgress = false;
|
||||||
|
if (!params?.silent) {
|
||||||
this.onDidChangeEmitter.fire();
|
this.onDidChangeEmitter.fire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async options(
|
||||||
|
exportBinaries?: boolean
|
||||||
|
): Promise<CoreService.Options.Compile | undefined> {
|
||||||
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (!CurrentSketch.isValid(sketch)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
|
const [fqbn, sourceOverride, optimizeForDebug] = await Promise.all([
|
||||||
|
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||||
|
this.sourceOverride(),
|
||||||
|
this.commandService.executeCommand<boolean>(
|
||||||
|
'arduino-is-optimize-for-debug'
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
const verbose = this.preferences.get('arduino.compile.verbose');
|
||||||
|
const compilerWarnings = this.preferences.get('arduino.compile.warnings');
|
||||||
|
return {
|
||||||
|
sketch,
|
||||||
|
fqbn,
|
||||||
|
optimizeForDebug: Boolean(optimizeForDebug),
|
||||||
|
verbose,
|
||||||
|
exportBinaries,
|
||||||
|
sourceOverride,
|
||||||
|
compilerWarnings,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace VerifySketch {
|
export namespace VerifySketch {
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
"list.inactiveSelectionForeground": "#dae3e3",
|
"list.inactiveSelectionForeground": "#dae3e3",
|
||||||
"list.inactiveSelectionBackground": "#434f54",
|
"list.inactiveSelectionBackground": "#434f54",
|
||||||
"list.hoverBackground": "#1f272a",
|
"list.hoverBackground": "#1f272a",
|
||||||
|
"list.activeSelectionIconForeground": "#0ca1a6",
|
||||||
"progressBar.background": "#005c5f",
|
"progressBar.background": "#005c5f",
|
||||||
"editor.background": "#1f272a",
|
"editor.background": "#1f272a",
|
||||||
"editor.foreground": "#dae3e3",
|
"editor.foreground": "#dae3e3",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"editorCursor.foreground": "#434f54",
|
"editorCursor.foreground": "#434f54",
|
||||||
"editorWhitespace.foreground": "#bfbfbf",
|
"editorWhitespace.foreground": "#bfbfbf",
|
||||||
"editorWidget.background": "#171e21",
|
"editorWidget.background": "#171e21",
|
||||||
|
"editorWidget.foreground": "#dae3e3",
|
||||||
"focusBorder": "#dae3e3",
|
"focusBorder": "#dae3e3",
|
||||||
"menubar.selectionBackground": "#ffffff",
|
"menubar.selectionBackground": "#ffffff",
|
||||||
"menubar.selectionForeground": "#212121",
|
"menubar.selectionForeground": "#212121",
|
||||||
@@ -28,7 +30,7 @@
|
|||||||
"titleBar.activeBackground": "#171e21",
|
"titleBar.activeBackground": "#171e21",
|
||||||
"titleBar.activeForeground": "#dae3e3",
|
"titleBar.activeForeground": "#dae3e3",
|
||||||
"terminal.background": "#000000",
|
"terminal.background": "#000000",
|
||||||
"terminal.foreground": "#e0e0e0",
|
"terminal.foreground": "#ffffff",
|
||||||
"dropdown.border": "#7fcbcd",
|
"dropdown.border": "#7fcbcd",
|
||||||
"dropdown.background": "#2c353a",
|
"dropdown.background": "#2c353a",
|
||||||
"dropdown.foreground": "#dae3e3",
|
"dropdown.foreground": "#dae3e3",
|
||||||
@@ -64,7 +66,8 @@
|
|||||||
"settings.headerForeground": "#dae3e3",
|
"settings.headerForeground": "#dae3e3",
|
||||||
"tree.indentGuidesStroke": "#374146",
|
"tree.indentGuidesStroke": "#374146",
|
||||||
"tab.unfocusedActiveForeground": "#dae3e3",
|
"tab.unfocusedActiveForeground": "#dae3e3",
|
||||||
"tab.inactiveBackground": "#171e21"
|
"tab.inactiveBackground": "#171e21",
|
||||||
|
"textLink.foreground": "#0ca1a6"
|
||||||
},
|
},
|
||||||
"tokenColors": [
|
"tokenColors": [
|
||||||
{
|
{
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
"list.inactiveSelectionForeground": "#4e5b61",
|
"list.inactiveSelectionForeground": "#4e5b61",
|
||||||
"list.inactiveSelectionBackground": "#dae3e3",
|
"list.inactiveSelectionBackground": "#dae3e3",
|
||||||
"list.hoverBackground": "#ecf1f1",
|
"list.hoverBackground": "#ecf1f1",
|
||||||
|
"list.activeSelectionIconForeground": "#008184",
|
||||||
"progressBar.background": "#005c5f",
|
"progressBar.background": "#005c5f",
|
||||||
"editor.background": "#ffffff",
|
"editor.background": "#ffffff",
|
||||||
"editor.foreground": "#4e5b61",
|
"editor.foreground": "#4e5b61",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"editorCursor.foreground": "#434f54",
|
"editorCursor.foreground": "#434f54",
|
||||||
"editorWhitespace.foreground": "#bfbfbf",
|
"editorWhitespace.foreground": "#bfbfbf",
|
||||||
"editorWidget.background": "#f7f9f9",
|
"editorWidget.background": "#f7f9f9",
|
||||||
|
"editorWidget.foreground": "#4e5b61",
|
||||||
"focusBorder": "#7fcbcd",
|
"focusBorder": "#7fcbcd",
|
||||||
"menubar.selectionBackground": "#ffffff",
|
"menubar.selectionBackground": "#ffffff",
|
||||||
"menubar.selectionForeground": "#212121",
|
"menubar.selectionForeground": "#212121",
|
||||||
@@ -28,7 +30,7 @@
|
|||||||
"titleBar.activeBackground": "#006d70",
|
"titleBar.activeBackground": "#006d70",
|
||||||
"titleBar.activeForeground": "#f7f9f9",
|
"titleBar.activeForeground": "#f7f9f9",
|
||||||
"terminal.background": "#000000",
|
"terminal.background": "#000000",
|
||||||
"terminal.foreground": "#e0e0e0",
|
"terminal.foreground": "#ffffff",
|
||||||
"dropdown.border": "#dae3e3",
|
"dropdown.border": "#dae3e3",
|
||||||
"dropdown.background": "#ffffff",
|
"dropdown.background": "#ffffff",
|
||||||
"dropdown.foreground": "#4e5b61",
|
"dropdown.foreground": "#4e5b61",
|
||||||
@@ -64,7 +66,8 @@
|
|||||||
"settings.headerForeground": "#4e5b61",
|
"settings.headerForeground": "#4e5b61",
|
||||||
"tree.indentGuidesStroke": "#dae3e3",
|
"tree.indentGuidesStroke": "#dae3e3",
|
||||||
"tab.unfocusedActiveForeground": "#4e5b61",
|
"tab.unfocusedActiveForeground": "#4e5b61",
|
||||||
"tab.inactiveBackground": "#ecf1f1"
|
"tab.inactiveBackground": "#ecf1f1",
|
||||||
|
"textLink.foreground": "#008184"
|
||||||
},
|
},
|
||||||
"tokenColors": [
|
"tokenColors": [
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
import * as React from '@theia/core/shared/react';
|
import * as React from '@theia/core/shared/react';
|
||||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
import {
|
||||||
|
inject,
|
||||||
|
injectable,
|
||||||
|
postConstruct,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
import { DialogProps } from '@theia/core/lib/browser/dialogs';
|
import { DialogProps } from '@theia/core/lib/browser/dialogs';
|
||||||
import { AbstractDialog } from '../../theia/dialogs/dialogs';
|
import { AbstractDialog } from '../../theia/dialogs/dialogs';
|
||||||
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||||
@@ -19,6 +23,7 @@ import { CommandRegistry } from '@theia/core/lib/common/command';
|
|||||||
import { certificateList, sanifyCertString } from './utils';
|
import { certificateList, sanifyCertString } from './utils';
|
||||||
import { ArduinoFirmwareUploader } from '../../../common/protocol/arduino-firmware-uploader';
|
import { ArduinoFirmwareUploader } from '../../../common/protocol/arduino-firmware-uploader';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UploadCertificateDialogWidget extends ReactWidget {
|
export class UploadCertificateDialogWidget extends ReactWidget {
|
||||||
@@ -37,6 +42,9 @@ export class UploadCertificateDialogWidget extends ReactWidget {
|
|||||||
@inject(ArduinoFirmwareUploader)
|
@inject(ArduinoFirmwareUploader)
|
||||||
protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader;
|
protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader;
|
||||||
|
|
||||||
|
@inject(FrontendApplicationStateService)
|
||||||
|
private readonly appStateService: FrontendApplicationStateService;
|
||||||
|
|
||||||
protected certificates: string[] = [];
|
protected certificates: string[] = [];
|
||||||
protected updatableFqbns: string[] = [];
|
protected updatableFqbns: string[] = [];
|
||||||
protected availableBoards: AvailableBoard[] = [];
|
protected availableBoards: AvailableBoard[] = [];
|
||||||
@@ -66,10 +74,12 @@ export class UploadCertificateDialogWidget extends ReactWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.appStateService.reachedState('ready').then(() =>
|
||||||
this.arduinoFirmwareUploader.updatableBoards().then((fqbns) => {
|
this.arduinoFirmwareUploader.updatableBoards().then((fqbns) => {
|
||||||
this.updatableFqbns = fqbns;
|
this.updatableFqbns = fqbns;
|
||||||
this.update();
|
this.update();
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
this.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => {
|
this.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => {
|
||||||
this.availableBoards = availableBoards;
|
this.availableBoards = availableBoards;
|
||||||
@@ -147,6 +157,7 @@ export class UploadCertificateDialog extends AbstractDialog<void> {
|
|||||||
'Upload SSL Root Certificates'
|
'Upload SSL Root Certificates'
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
this.node.id = 'certificate-uploader-dialog-container';
|
||||||
this.contentNode.classList.add('certificate-uploader-dialog');
|
this.contentNode.classList.add('certificate-uploader-dialog');
|
||||||
this.acceptButton = undefined;
|
this.acceptButton = undefined;
|
||||||
}
|
}
|
||||||
|
@@ -101,6 +101,7 @@ export class UploadFirmwareDialog extends AbstractDialog<void> {
|
|||||||
protected override readonly props: UploadFirmwareDialogProps
|
protected override readonly props: UploadFirmwareDialogProps
|
||||||
) {
|
) {
|
||||||
super({ title: UploadFirmware.Commands.OPEN.label || '' });
|
super({ title: UploadFirmware.Commands.OPEN.label || '' });
|
||||||
|
this.node.id = 'firmware-uploader-dialog-container';
|
||||||
this.contentNode.classList.add('firmware-uploader-dialog');
|
this.contentNode.classList.add('firmware-uploader-dialog');
|
||||||
this.acceptButton = undefined;
|
this.acceptButton = undefined;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
import { shell } from 'electron';
|
import { shell } from 'electron';
|
||||||
import * as React from '@theia/core/shared/react';
|
import * as React from '@theia/core/shared/react';
|
||||||
@@ -7,36 +6,32 @@ import ReactMarkdown from 'react-markdown';
|
|||||||
import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
|
import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
|
||||||
import ProgressBar from '../../components/ProgressBar';
|
import ProgressBar from '../../components/ProgressBar';
|
||||||
|
|
||||||
export type IDEUpdaterComponentProps = {
|
export interface UpdateProgress {
|
||||||
updateInfo: UpdateInfo;
|
progressInfo?: ProgressInfo | undefined;
|
||||||
windowService: WindowService;
|
|
||||||
downloadFinished?: boolean;
|
downloadFinished?: boolean;
|
||||||
downloadStarted?: boolean;
|
downloadStarted?: boolean;
|
||||||
progress?: ProgressInfo;
|
|
||||||
error?: Error;
|
error?: Error;
|
||||||
onDownload: () => void;
|
}
|
||||||
onClose: () => void;
|
|
||||||
onSkipVersion: () => void;
|
export interface IDEUpdaterComponentProps {
|
||||||
onCloseAndInstall: () => void;
|
updateInfo: UpdateInfo;
|
||||||
};
|
updateProgress: UpdateProgress;
|
||||||
|
}
|
||||||
|
|
||||||
export const IDEUpdaterComponent = ({
|
export const IDEUpdaterComponent = ({
|
||||||
updateInfo: { version, releaseNotes },
|
updateInfo,
|
||||||
|
updateProgress: {
|
||||||
downloadStarted = false,
|
downloadStarted = false,
|
||||||
downloadFinished = false,
|
downloadFinished = false,
|
||||||
windowService,
|
progressInfo,
|
||||||
progress,
|
|
||||||
error,
|
error,
|
||||||
onDownload,
|
},
|
||||||
onClose,
|
|
||||||
onSkipVersion,
|
|
||||||
onCloseAndInstall,
|
|
||||||
}: IDEUpdaterComponentProps): React.ReactElement => {
|
}: IDEUpdaterComponentProps): React.ReactElement => {
|
||||||
const changelogDivRef = React.useRef() as React.MutableRefObject<
|
const { version, releaseNotes } = updateInfo;
|
||||||
HTMLDivElement
|
const changelogDivRef =
|
||||||
>;
|
React.useRef() as React.MutableRefObject<HTMLDivElement>;
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!!releaseNotes) {
|
if (!!releaseNotes && changelogDivRef.current) {
|
||||||
let changelog: string;
|
let changelog: string;
|
||||||
if (typeof releaseNotes === 'string') changelog = releaseNotes;
|
if (typeof releaseNotes === 'string') changelog = releaseNotes;
|
||||||
else
|
else
|
||||||
@@ -58,12 +53,7 @@ export const IDEUpdaterComponent = ({
|
|||||||
changelogDivRef.current
|
changelogDivRef.current
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [releaseNotes]);
|
}, [updateInfo]);
|
||||||
const closeButton = (
|
|
||||||
<button onClick={onClose} type="button" className="theia-button secondary">
|
|
||||||
{nls.localize('arduino/ide-updater/notNowButton', 'Not now')}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
|
|
||||||
const DownloadCompleted: () => React.ReactElement = () => (
|
const DownloadCompleted: () => React.ReactElement = () => (
|
||||||
<div className="ide-updater-dialog--downloaded">
|
<div className="ide-updater-dialog--downloaded">
|
||||||
@@ -80,19 +70,6 @@ export const IDEUpdaterComponent = ({
|
|||||||
'Close the software and install the update on your machine.'
|
'Close the software and install the update on your machine.'
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="buttons-container">
|
|
||||||
{closeButton}
|
|
||||||
<button
|
|
||||||
onClick={onCloseAndInstall}
|
|
||||||
type="button"
|
|
||||||
className="theia-button close-and-install"
|
|
||||||
>
|
|
||||||
{nls.localize(
|
|
||||||
'arduino/ide-updater/closeAndInstallButton',
|
|
||||||
'Close and Install'
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -104,7 +81,7 @@ export const IDEUpdaterComponent = ({
|
|||||||
'Downloading the latest version of the Arduino IDE.'
|
'Downloading the latest version of the Arduino IDE.'
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar percent={progress?.percent} showPercentage />
|
<ProgressBar percent={progressInfo?.percent} showPercentage />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -130,46 +107,14 @@ export const IDEUpdaterComponent = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{releaseNotes && (
|
{releaseNotes && (
|
||||||
<div className="dialogRow">
|
<div className="dialogRow changelog-container">
|
||||||
<div className="changelog-container" ref={changelogDivRef} />
|
<div className="changelog" ref={changelogDivRef} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="buttons-container">
|
|
||||||
<button
|
|
||||||
onClick={onSkipVersion}
|
|
||||||
type="button"
|
|
||||||
className="theia-button secondary skip-version"
|
|
||||||
>
|
|
||||||
{nls.localize(
|
|
||||||
'arduino/ide-updater/skipVersionButton',
|
|
||||||
'Skip Version'
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<div className="push"></div>
|
|
||||||
{closeButton}
|
|
||||||
<button
|
|
||||||
onClick={onDownload}
|
|
||||||
type="button"
|
|
||||||
className="theia-button primary"
|
|
||||||
>
|
|
||||||
{nls.localize('arduino/ide-updater/downloadButton', 'Download')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const onGoToDownloadClick = (
|
|
||||||
event: React.SyntheticEvent<HTMLAnchorElement, Event>
|
|
||||||
) => {
|
|
||||||
const { target } = event.nativeEvent;
|
|
||||||
if (target instanceof HTMLAnchorElement) {
|
|
||||||
event.nativeEvent.preventDefault();
|
|
||||||
windowService.openNewWindow(target.href, { external: true });
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const GoToDownloadPage: () => React.ReactElement = () => (
|
const GoToDownloadPage: () => React.ReactElement = () => (
|
||||||
<div className="ide-updater-dialog--go-to-download-page">
|
<div className="ide-updater-dialog--go-to-download-page">
|
||||||
<div>
|
<div>
|
||||||
@@ -178,19 +123,6 @@ export const IDEUpdaterComponent = ({
|
|||||||
"An update for the Arduino IDE is available, but we're not able to download and install it automatically. Please go to the download page and download the latest version from there."
|
"An update for the Arduino IDE is available, but we're not able to download and install it automatically. Please go to the download page and download the latest version from there."
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="buttons-container">
|
|
||||||
{closeButton}
|
|
||||||
<a
|
|
||||||
className="theia-button primary"
|
|
||||||
href="https://www.arduino.cc/en/software#experimental-software"
|
|
||||||
onClick={onGoToDownloadClick}
|
|
||||||
>
|
|
||||||
{nls.localize(
|
|
||||||
'arduino/ide-updater/goToDownloadButton',
|
|
||||||
'Go To Download'
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -1,113 +1,57 @@
|
|||||||
import * as React from '@theia/core/shared/react';
|
import * as React from '@theia/core/shared/react';
|
||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import {
|
||||||
|
inject,
|
||||||
|
injectable,
|
||||||
|
postConstruct,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
import { DialogProps } from '@theia/core/lib/browser/dialogs';
|
import { DialogProps } from '@theia/core/lib/browser/dialogs';
|
||||||
import { AbstractDialog } from '../../theia/dialogs/dialogs';
|
import { AbstractDialog } from '../../theia/dialogs/dialogs';
|
||||||
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||||
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
||||||
import { nls } from '@theia/core';
|
import { nls } from '@theia/core';
|
||||||
import { IDEUpdaterComponent } from './ide-updater-component';
|
import { IDEUpdaterComponent, UpdateProgress } from './ide-updater-component';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IDEUpdater,
|
IDEUpdater,
|
||||||
IDEUpdaterClient,
|
IDEUpdaterClient,
|
||||||
ProgressInfo,
|
|
||||||
SKIP_IDE_VERSION,
|
SKIP_IDE_VERSION,
|
||||||
UpdateInfo,
|
UpdateInfo,
|
||||||
} from '../../../common/protocol/ide-updater';
|
} from '../../../common/protocol/ide-updater';
|
||||||
import { LocalStorageService } from '@theia/core/lib/browser';
|
import { LocalStorageService } from '@theia/core/lib/browser';
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||||
|
|
||||||
|
const DOWNLOAD_PAGE_URL =
|
||||||
|
'https://www.arduino.cc/en/software#experimental-software';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class IDEUpdaterDialogWidget extends ReactWidget {
|
export class IDEUpdaterDialogWidget extends ReactWidget {
|
||||||
protected isOpen = new Object();
|
private _updateInfo: UpdateInfo;
|
||||||
updateInfo: UpdateInfo;
|
private _updateProgress: UpdateProgress = {};
|
||||||
progressInfo: ProgressInfo | undefined;
|
|
||||||
error: Error | undefined;
|
|
||||||
downloadFinished: boolean;
|
|
||||||
downloadStarted: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
|
|
||||||
@inject(IDEUpdater)
|
setUpdateInfo(updateInfo: UpdateInfo): void {
|
||||||
protected readonly updater: IDEUpdater;
|
this._updateInfo = updateInfo;
|
||||||
|
|
||||||
@inject(IDEUpdaterClient)
|
|
||||||
protected readonly updaterClient: IDEUpdaterClient;
|
|
||||||
|
|
||||||
@inject(LocalStorageService)
|
|
||||||
protected readonly localStorageService: LocalStorageService;
|
|
||||||
|
|
||||||
@inject(WindowService)
|
|
||||||
protected windowService: WindowService;
|
|
||||||
|
|
||||||
init(updateInfo: UpdateInfo, onClose: () => void): void {
|
|
||||||
this.updateInfo = updateInfo;
|
|
||||||
this.progressInfo = undefined;
|
|
||||||
this.error = undefined;
|
|
||||||
this.downloadStarted = false;
|
|
||||||
this.downloadFinished = false;
|
|
||||||
this.onClose = onClose;
|
|
||||||
|
|
||||||
this.updaterClient.onError((e) => {
|
|
||||||
this.error = e;
|
|
||||||
this.update();
|
|
||||||
});
|
|
||||||
this.updaterClient.onDownloadProgressChanged((e) => {
|
|
||||||
this.progressInfo = e;
|
|
||||||
this.update();
|
|
||||||
});
|
|
||||||
this.updaterClient.onDownloadFinished((e) => {
|
|
||||||
this.downloadFinished = true;
|
|
||||||
this.update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async onSkipVersion(): Promise<void> {
|
|
||||||
this.localStorageService.setData<string>(
|
|
||||||
SKIP_IDE_VERSION,
|
|
||||||
this.updateInfo.version
|
|
||||||
);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
override close(): void {
|
|
||||||
super.close();
|
|
||||||
this.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
onDispose(): void {
|
|
||||||
if (this.downloadStarted && !this.downloadFinished)
|
|
||||||
this.updater.stopDownload();
|
|
||||||
}
|
|
||||||
|
|
||||||
async onDownload(): Promise<void> {
|
|
||||||
this.progressInfo = undefined;
|
|
||||||
this.downloadStarted = true;
|
|
||||||
this.error = undefined;
|
|
||||||
this.updater.downloadUpdate();
|
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
onCloseAndInstall(): void {
|
mergeUpdateProgress(updateProgress: UpdateProgress): void {
|
||||||
this.updater.quitAndInstall();
|
this._updateProgress = { ...this._updateProgress, ...updateProgress };
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
get updateInfo(): UpdateInfo {
|
||||||
|
return this._updateInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
get updateProgress(): UpdateProgress {
|
||||||
|
return this._updateProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): React.ReactNode {
|
protected render(): React.ReactNode {
|
||||||
return !!this.updateInfo ? (
|
return !!this._updateInfo ? (
|
||||||
<form>
|
|
||||||
<IDEUpdaterComponent
|
<IDEUpdaterComponent
|
||||||
updateInfo={this.updateInfo}
|
updateInfo={this._updateInfo}
|
||||||
windowService={this.windowService}
|
updateProgress={this._updateProgress}
|
||||||
downloadStarted={this.downloadStarted}
|
|
||||||
downloadFinished={this.downloadFinished}
|
|
||||||
progress={this.progressInfo}
|
|
||||||
error={this.error}
|
|
||||||
onClose={this.close.bind(this)}
|
|
||||||
onSkipVersion={this.onSkipVersion.bind(this)}
|
|
||||||
onDownload={this.onDownload.bind(this)}
|
|
||||||
onCloseAndInstall={this.onCloseAndInstall.bind(this)}
|
|
||||||
/>
|
/>
|
||||||
</form>
|
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +62,19 @@ export class IDEUpdaterDialogProps extends DialogProps {}
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
|
export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
|
||||||
@inject(IDEUpdaterDialogWidget)
|
@inject(IDEUpdaterDialogWidget)
|
||||||
protected readonly widget: IDEUpdaterDialogWidget;
|
private readonly widget: IDEUpdaterDialogWidget;
|
||||||
|
|
||||||
|
@inject(IDEUpdater)
|
||||||
|
private readonly updater: IDEUpdater;
|
||||||
|
|
||||||
|
@inject(IDEUpdaterClient)
|
||||||
|
private readonly updaterClient: IDEUpdaterClient;
|
||||||
|
|
||||||
|
@inject(LocalStorageService)
|
||||||
|
private readonly localStorageService: LocalStorageService;
|
||||||
|
|
||||||
|
@inject(WindowService)
|
||||||
|
private readonly windowService: WindowService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(IDEUpdaterDialogProps)
|
@inject(IDEUpdaterDialogProps)
|
||||||
@@ -130,10 +86,26 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
|
|||||||
'Software Update'
|
'Software Update'
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
this.node.id = 'ide-updater-dialog-container';
|
||||||
this.contentNode.classList.add('ide-updater-dialog');
|
this.contentNode.classList.add('ide-updater-dialog');
|
||||||
this.acceptButton = undefined;
|
this.acceptButton = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
this.updaterClient.onUpdaterDidFail((error) => {
|
||||||
|
this.appendErrorButtons();
|
||||||
|
this.widget.mergeUpdateProgress({ error });
|
||||||
|
});
|
||||||
|
this.updaterClient.onDownloadProgressDidChange((progressInfo) => {
|
||||||
|
this.widget.mergeUpdateProgress({ progressInfo });
|
||||||
|
});
|
||||||
|
this.updaterClient.onDownloadDidFinish(() => {
|
||||||
|
this.appendInstallButtons();
|
||||||
|
this.widget.mergeUpdateProgress({ downloadFinished: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get value(): UpdateInfo {
|
get value(): UpdateInfo {
|
||||||
return this.widget.updateInfo;
|
return this.widget.updateInfo;
|
||||||
}
|
}
|
||||||
@@ -143,24 +115,123 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
|
|||||||
Widget.detach(this.widget);
|
Widget.detach(this.widget);
|
||||||
}
|
}
|
||||||
Widget.attach(this.widget, this.contentNode);
|
Widget.attach(this.widget, this.contentNode);
|
||||||
|
this.appendInitialButtons();
|
||||||
super.onAfterAttach(msg);
|
super.onAfterAttach(msg);
|
||||||
this.update();
|
}
|
||||||
|
|
||||||
|
private clearButtons(): void {
|
||||||
|
while (this.controlPanel.firstChild) {
|
||||||
|
this.controlPanel.removeChild(this.controlPanel.firstChild);
|
||||||
|
}
|
||||||
|
this.closeButton = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private appendNotNowButton(): void {
|
||||||
|
this.appendCloseButton(
|
||||||
|
nls.localize('arduino/ide-updater/notNowButton', 'Not now')
|
||||||
|
);
|
||||||
|
if (this.closeButton) {
|
||||||
|
this.addCloseAction(this.closeButton, 'click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private appendInitialButtons(): void {
|
||||||
|
this.clearButtons();
|
||||||
|
|
||||||
|
const skipVersionButton = this.createButton(
|
||||||
|
nls.localize('arduino/ide-updater/skipVersionButton', 'Skip Version')
|
||||||
|
);
|
||||||
|
skipVersionButton.classList.add('secondary');
|
||||||
|
skipVersionButton.classList.add('skip-version-button');
|
||||||
|
this.addAction(skipVersionButton, this.skipVersion.bind(this), 'click');
|
||||||
|
this.controlPanel.appendChild(skipVersionButton);
|
||||||
|
|
||||||
|
this.appendNotNowButton();
|
||||||
|
|
||||||
|
const downloadButton = this.createButton(
|
||||||
|
nls.localize('arduino/ide-updater/downloadButton', 'Download')
|
||||||
|
);
|
||||||
|
this.addAction(downloadButton, this.startDownload.bind(this), 'click');
|
||||||
|
this.controlPanel.appendChild(downloadButton);
|
||||||
|
downloadButton.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private appendInstallButtons(): void {
|
||||||
|
this.clearButtons();
|
||||||
|
this.appendNotNowButton();
|
||||||
|
|
||||||
|
const closeAndInstallButton = this.createButton(
|
||||||
|
nls.localize(
|
||||||
|
'arduino/ide-updater/closeAndInstallButton',
|
||||||
|
'Close and Install'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.addAction(
|
||||||
|
closeAndInstallButton,
|
||||||
|
this.closeAndInstall.bind(this),
|
||||||
|
'click'
|
||||||
|
);
|
||||||
|
this.controlPanel.appendChild(closeAndInstallButton);
|
||||||
|
closeAndInstallButton.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private appendErrorButtons(): void {
|
||||||
|
this.clearButtons();
|
||||||
|
this.appendNotNowButton();
|
||||||
|
|
||||||
|
const goToDownloadPageButton = this.createButton(
|
||||||
|
nls.localize('arduino/ide-updater/goToDownloadButton', 'Go To Download')
|
||||||
|
);
|
||||||
|
this.addAction(
|
||||||
|
goToDownloadPageButton,
|
||||||
|
this.openDownloadPage.bind(this),
|
||||||
|
'click'
|
||||||
|
);
|
||||||
|
this.controlPanel.appendChild(goToDownloadPageButton);
|
||||||
|
goToDownloadPageButton.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private openDownloadPage(): void {
|
||||||
|
this.windowService.openNewWindow(DOWNLOAD_PAGE_URL, { external: true });
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private skipVersion(): void {
|
||||||
|
this.localStorageService.setData<string>(
|
||||||
|
SKIP_IDE_VERSION,
|
||||||
|
this.widget.updateInfo.version
|
||||||
|
);
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private startDownload(): void {
|
||||||
|
this.widget.mergeUpdateProgress({
|
||||||
|
downloadStarted: true,
|
||||||
|
});
|
||||||
|
this.clearButtons();
|
||||||
|
this.updater.downloadUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeAndInstall() {
|
||||||
|
this.updater.quitAndInstall();
|
||||||
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
override async open(
|
override async open(
|
||||||
data: UpdateInfo | undefined = undefined
|
data: UpdateInfo | undefined = undefined
|
||||||
): Promise<UpdateInfo | undefined> {
|
): Promise<UpdateInfo | undefined> {
|
||||||
if (data && data.version) {
|
if (data && data.version) {
|
||||||
this.widget.init(data, this.close.bind(this));
|
this.widget.mergeUpdateProgress({
|
||||||
|
progressInfo: undefined,
|
||||||
|
downloadStarted: false,
|
||||||
|
downloadFinished: false,
|
||||||
|
error: undefined,
|
||||||
|
});
|
||||||
|
this.widget.setUpdateInfo(data);
|
||||||
return super.open();
|
return super.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override onUpdateRequest(msg: Message): void {
|
|
||||||
super.onUpdateRequest(msg);
|
|
||||||
this.widget.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override onActivateRequest(msg: Message): void {
|
protected override onActivateRequest(msg: Message): void {
|
||||||
super.onActivateRequest(msg);
|
super.onActivateRequest(msg);
|
||||||
this.widget.activate();
|
this.widget.activate();
|
||||||
@@ -168,6 +239,12 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
|
|||||||
|
|
||||||
override close(): void {
|
override close(): void {
|
||||||
this.widget.dispose();
|
this.widget.dispose();
|
||||||
|
if (
|
||||||
|
this.widget.updateProgress?.downloadStarted &&
|
||||||
|
!this.widget.updateProgress?.downloadFinished
|
||||||
|
) {
|
||||||
|
this.updater.stopDownload();
|
||||||
|
}
|
||||||
super.close();
|
super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,8 +23,8 @@ import {
|
|||||||
} from '@theia/core/lib/common/i18n/localization';
|
} from '@theia/core/lib/common/i18n/localization';
|
||||||
import SettingsStepInput from './settings-step-input';
|
import SettingsStepInput from './settings-step-input';
|
||||||
|
|
||||||
const maxScale = 200;
|
const maxScale = 280;
|
||||||
const minScale = -100;
|
const minScale = -60;
|
||||||
const scaleStep = 20;
|
const scaleStep = 20;
|
||||||
|
|
||||||
const maxFontSize = 72;
|
const maxFontSize = 72;
|
||||||
@@ -188,25 +188,22 @@ export class SettingsComponent extends React.Component<
|
|||||||
/>
|
/>
|
||||||
{nls.localize('arduino/preferences/automatic', 'Automatic')}
|
{nls.localize('arduino/preferences/automatic', 'Automatic')}
|
||||||
</label>
|
</label>
|
||||||
|
<div>
|
||||||
<SettingsStepInput
|
<SettingsStepInput
|
||||||
value={scalePercentage}
|
value={scalePercentage}
|
||||||
setSettingsStateValue={this.setInterfaceScale}
|
setSettingsStateValue={this.setInterfaceScale}
|
||||||
step={scaleStep}
|
step={scaleStep}
|
||||||
maxValue={maxScale}
|
maxValue={maxScale}
|
||||||
minValue={minScale}
|
minValue={minScale}
|
||||||
|
unitOfMeasure="%"
|
||||||
classNames={{ input: 'theia-input small with-margin' }}
|
classNames={{ input: 'theia-input small with-margin' }}
|
||||||
/>
|
/>
|
||||||
%
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-line">
|
<div className="flex-line">
|
||||||
<select
|
<select
|
||||||
className="theia-select"
|
className="theia-select"
|
||||||
value={
|
value={ThemeService.get().getCurrentTheme().label}
|
||||||
ThemeService.get()
|
|
||||||
.getThemes()
|
|
||||||
.find(({ id }) => id === this.state.themeId)?.label ||
|
|
||||||
nls.localize('arduino/common/unknown', 'Unknown')
|
|
||||||
}
|
|
||||||
onChange={this.themeDidChange}
|
onChange={this.themeDidChange}
|
||||||
>
|
>
|
||||||
{ThemeService.get()
|
{ThemeService.get()
|
||||||
@@ -591,6 +588,9 @@ export class SettingsComponent extends React.Component<
|
|||||||
const theme = ThemeService.get().getThemes()[selectedIndex];
|
const theme = ThemeService.get().getThemes()[selectedIndex];
|
||||||
if (theme) {
|
if (theme) {
|
||||||
this.setState({ themeId: theme.id });
|
this.setState({ themeId: theme.id });
|
||||||
|
if (ThemeService.get().getCurrentTheme().id !== theme.id) {
|
||||||
|
ThemeService.get().setCurrentTheme(theme.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -16,6 +16,7 @@ import { SettingsComponent } from './settings-component';
|
|||||||
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization';
|
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization';
|
||||||
import { AdditionalUrls } from '../../../common/protocol';
|
import { AdditionalUrls } from '../../../common/protocol';
|
||||||
import { AbstractDialog } from '../../theia/dialogs/dialogs';
|
import { AbstractDialog } from '../../theia/dialogs/dialogs';
|
||||||
|
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SettingsWidget extends ReactWidget {
|
export class SettingsWidget extends ReactWidget {
|
||||||
@@ -118,6 +119,17 @@ export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
|
|||||||
|
|
||||||
this.widget.activate();
|
this.widget.activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async open(): Promise<Promise<Settings> | undefined> {
|
||||||
|
const themeIdBeforeOpen = ThemeService.get().getCurrentTheme().id;
|
||||||
|
const result = await super.open();
|
||||||
|
if (!result) {
|
||||||
|
if (ThemeService.get().getCurrentTheme().id !== themeIdBeforeOpen) {
|
||||||
|
ThemeService.get().setCurrentTheme(themeIdBeforeOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
|
export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
|
||||||
|
@@ -7,73 +7,47 @@ interface SettingsStepInputProps {
|
|||||||
step: number;
|
step: number;
|
||||||
maxValue: number;
|
maxValue: number;
|
||||||
minValue: number;
|
minValue: number;
|
||||||
|
unitOfMeasure?: string;
|
||||||
classNames?: { [key: string]: string };
|
classNames?: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsStepInput: React.FC<SettingsStepInputProps> = (
|
const SettingsStepInput: React.FC<SettingsStepInputProps> = (
|
||||||
props: SettingsStepInputProps
|
props: SettingsStepInputProps
|
||||||
) => {
|
) => {
|
||||||
const { value, setSettingsStateValue, step, maxValue, minValue, classNames } =
|
const {
|
||||||
props;
|
value,
|
||||||
|
setSettingsStateValue,
|
||||||
const [stepUpDisabled, setStepUpDisabled] = React.useState(false);
|
step,
|
||||||
const [stepDownDisabled, setStepDownDisabled] = React.useState(false);
|
|
||||||
|
|
||||||
const onStepUp = (): void => {
|
|
||||||
const valueRoundedToScale = Math.ceil(value / step) * step;
|
|
||||||
const calculatedValue =
|
|
||||||
valueRoundedToScale === value ? value + step : valueRoundedToScale;
|
|
||||||
const newValue = limitValueByCondition(
|
|
||||||
calculatedValue,
|
|
||||||
maxValue,
|
maxValue,
|
||||||
calculatedValue >= maxValue,
|
minValue,
|
||||||
disableStepUp
|
unitOfMeasure,
|
||||||
);
|
classNames,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const clamp = (value: number, min: number, max: number): number => {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onStep = (
|
||||||
|
roundingOperation: 'ceil' | 'floor',
|
||||||
|
stepOperation: (a: number, b: number) => number
|
||||||
|
): void => {
|
||||||
|
const valueRoundedToScale = Math[roundingOperation](value / step) * step;
|
||||||
|
const calculatedValue =
|
||||||
|
valueRoundedToScale === value
|
||||||
|
? stepOperation(value, step)
|
||||||
|
: valueRoundedToScale;
|
||||||
|
const newValue = clamp(calculatedValue, minValue, maxValue);
|
||||||
|
|
||||||
setSettingsStateValue(newValue);
|
setSettingsStateValue(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onStepUp = (): void => {
|
||||||
|
onStep('ceil', (a: number, b: number) => a + b);
|
||||||
|
};
|
||||||
|
|
||||||
const onStepDown = (): void => {
|
const onStepDown = (): void => {
|
||||||
const valueRoundedToScale = Math.floor(value / step) * step;
|
onStep('floor', (a: number, b: number) => a - b);
|
||||||
const calculatedValue =
|
|
||||||
valueRoundedToScale === value ? value - step : valueRoundedToScale;
|
|
||||||
const newValue = limitValueByCondition(
|
|
||||||
calculatedValue,
|
|
||||||
minValue,
|
|
||||||
calculatedValue <= minValue,
|
|
||||||
disableStepDown
|
|
||||||
);
|
|
||||||
|
|
||||||
setSettingsStateValue(newValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const limitValueByCondition = (
|
|
||||||
calculatedValue: number,
|
|
||||||
limitedValue: number,
|
|
||||||
condition: boolean,
|
|
||||||
onConditionTrue: () => void,
|
|
||||||
onConditionFalse = enableButtons
|
|
||||||
): number => {
|
|
||||||
if (condition) {
|
|
||||||
onConditionTrue();
|
|
||||||
return limitedValue;
|
|
||||||
} else {
|
|
||||||
onConditionFalse();
|
|
||||||
return calculatedValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const enableButtons = (): void => {
|
|
||||||
setStepUpDisabled(false);
|
|
||||||
setStepDownDisabled(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const disableStepUp = (): void => {
|
|
||||||
setStepUpDisabled(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const disableStepDown = (): void => {
|
|
||||||
setStepDownDisabled(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUserInput = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
const onUserInput = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
@@ -86,34 +60,14 @@ const SettingsStepInput: React.FC<SettingsStepInputProps> = (
|
|||||||
const number = Number(eventValue);
|
const number = Number(eventValue);
|
||||||
|
|
||||||
if (!isNaN(number) && number !== value) {
|
if (!isNaN(number) && number !== value) {
|
||||||
let newValue;
|
const newValue = clamp(number, minValue, maxValue);
|
||||||
if (number > value) {
|
|
||||||
newValue = limitValueByCondition(
|
|
||||||
number,
|
|
||||||
maxValue,
|
|
||||||
number >= maxValue,
|
|
||||||
disableStepUp
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
newValue = limitValueByCondition(
|
|
||||||
number,
|
|
||||||
minValue,
|
|
||||||
number <= minValue,
|
|
||||||
disableStepDown
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSettingsStateValue(newValue);
|
setSettingsStateValue(newValue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// the component does not unmount when we close the settings dialog
|
const upDisabled = value >= maxValue;
|
||||||
// in theia which necessitates the below useEffect
|
const downDisabled = value <= minValue;
|
||||||
React.useEffect(() => {
|
|
||||||
if (value > minValue && value < maxValue) {
|
|
||||||
enableButtons();
|
|
||||||
}
|
|
||||||
}, [value, minValue, maxValue]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-step-input-container">
|
<div className="settings-step-input-container">
|
||||||
@@ -127,19 +81,20 @@ const SettingsStepInput: React.FC<SettingsStepInputProps> = (
|
|||||||
<div className="settings-step-input-buttons-container">
|
<div className="settings-step-input-buttons-container">
|
||||||
<button
|
<button
|
||||||
className="settings-step-input-button settings-step-input-up-button"
|
className="settings-step-input-button settings-step-input-up-button"
|
||||||
disabled={stepUpDisabled}
|
disabled={upDisabled}
|
||||||
onClick={onStepUp}
|
onClick={onStepUp}
|
||||||
>
|
>
|
||||||
▾
|
▾
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="settings-step-input-button"
|
className="settings-step-input-button"
|
||||||
disabled={stepDownDisabled}
|
disabled={downDisabled}
|
||||||
onClick={onStepDown}
|
onClick={onStepDown}
|
||||||
>
|
>
|
||||||
▾
|
▾
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{unitOfMeasure && `${unitOfMeasure}`}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -111,9 +111,11 @@ export class SettingsService {
|
|||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected async init(): Promise<void> {
|
protected async init(): Promise<void> {
|
||||||
|
this.appStateService.reachedState('ready').then(async () => {
|
||||||
const settings = await this.loadSettings();
|
const settings = await this.loadSettings();
|
||||||
this._settings = deepClone(settings);
|
this._settings = deepClone(settings);
|
||||||
this.ready.resolve();
|
this.ready.resolve();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadSettings(): Promise<Settings> {
|
protected async loadSettings(): Promise<Settings> {
|
||||||
@@ -139,7 +141,10 @@ export class SettingsService {
|
|||||||
this.preferenceService.get<number>(FONT_SIZE_SETTING, 12),
|
this.preferenceService.get<number>(FONT_SIZE_SETTING, 12),
|
||||||
this.preferenceService.get<string>(
|
this.preferenceService.get<string>(
|
||||||
'workbench.colorTheme',
|
'workbench.colorTheme',
|
||||||
'arduino-theme'
|
window.matchMedia &&
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
? 'arduino-theme-dark'
|
||||||
|
: 'arduino-theme'
|
||||||
),
|
),
|
||||||
this.preferenceService.get<Settings.AutoSave>(
|
this.preferenceService.get<Settings.AutoSave>(
|
||||||
AUTO_SAVE_SETTING,
|
AUTO_SAVE_SETTING,
|
||||||
|
@@ -65,7 +65,11 @@ export const UserFieldsComponent = ({
|
|||||||
type={field.secret ? 'password' : 'text'}
|
type={field.secret ? 'password' : 'text'}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
className="theia-input"
|
className="theia-input"
|
||||||
placeholder={'Enter ' + field.label}
|
placeholder={nls.localize(
|
||||||
|
'arduino/userFields/enterField',
|
||||||
|
'Enter {0}',
|
||||||
|
field.label
|
||||||
|
)}
|
||||||
onChange={updateUserField(index)}
|
onChange={updateUserField(index)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -5,36 +5,43 @@ import { IDEUpdaterClient } from '../../common/protocol/ide-updater';
|
|||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class IDEUpdaterClientImpl implements IDEUpdaterClient {
|
export class IDEUpdaterClientImpl implements IDEUpdaterClient {
|
||||||
protected readonly onErrorEmitter = new Emitter<Error>();
|
protected readonly onUpdaterDidFailEmitter = new Emitter<Error>();
|
||||||
protected readonly onCheckingForUpdateEmitter = new Emitter<void>();
|
protected readonly onUpdaterDidCheckForUpdateEmitter = new Emitter<void>();
|
||||||
protected readonly onUpdateAvailableEmitter = new Emitter<UpdateInfo>();
|
protected readonly onUpdaterDidFindUpdateAvailableEmitter =
|
||||||
protected readonly onUpdateNotAvailableEmitter = new Emitter<UpdateInfo>();
|
new Emitter<UpdateInfo>();
|
||||||
protected readonly onDownloadProgressEmitter = new Emitter<ProgressInfo>();
|
protected readonly onUpdaterDidNotFindUpdateAvailableEmitter =
|
||||||
protected readonly onDownloadFinishedEmitter = new Emitter<UpdateInfo>();
|
new Emitter<UpdateInfo>();
|
||||||
|
protected readonly onDownloadProgressDidChangeEmitter =
|
||||||
|
new Emitter<ProgressInfo>();
|
||||||
|
protected readonly onDownloadDidFinishEmitter = new Emitter<UpdateInfo>();
|
||||||
|
|
||||||
readonly onError = this.onErrorEmitter.event;
|
readonly onUpdaterDidFail = this.onUpdaterDidFailEmitter.event;
|
||||||
readonly onCheckingForUpdate = this.onCheckingForUpdateEmitter.event;
|
readonly onUpdaterDidCheckForUpdate =
|
||||||
readonly onUpdateAvailable = this.onUpdateAvailableEmitter.event;
|
this.onUpdaterDidCheckForUpdateEmitter.event;
|
||||||
readonly onUpdateNotAvailable = this.onUpdateNotAvailableEmitter.event;
|
readonly onUpdaterDidFindUpdateAvailable =
|
||||||
readonly onDownloadProgressChanged = this.onDownloadProgressEmitter.event;
|
this.onUpdaterDidFindUpdateAvailableEmitter.event;
|
||||||
readonly onDownloadFinished = this.onDownloadFinishedEmitter.event;
|
readonly onUpdaterDidNotFindUpdateAvailable =
|
||||||
|
this.onUpdaterDidNotFindUpdateAvailableEmitter.event;
|
||||||
|
readonly onDownloadProgressDidChange =
|
||||||
|
this.onDownloadProgressDidChangeEmitter.event;
|
||||||
|
readonly onDownloadDidFinish = this.onDownloadDidFinishEmitter.event;
|
||||||
|
|
||||||
notifyError(message: Error): void {
|
notifyUpdaterFailed(message: Error): void {
|
||||||
this.onErrorEmitter.fire(message);
|
this.onUpdaterDidFailEmitter.fire(message);
|
||||||
}
|
}
|
||||||
notifyCheckingForUpdate(message: void): void {
|
notifyCheckedForUpdate(message: void): void {
|
||||||
this.onCheckingForUpdateEmitter.fire(message);
|
this.onUpdaterDidCheckForUpdateEmitter.fire(message);
|
||||||
}
|
}
|
||||||
notifyUpdateAvailable(message: UpdateInfo): void {
|
notifyUpdateAvailableFound(message: UpdateInfo): void {
|
||||||
this.onUpdateAvailableEmitter.fire(message);
|
this.onUpdaterDidFindUpdateAvailableEmitter.fire(message);
|
||||||
}
|
}
|
||||||
notifyUpdateNotAvailable(message: UpdateInfo): void {
|
notifyUpdateAvailableNotFound(message: UpdateInfo): void {
|
||||||
this.onUpdateNotAvailableEmitter.fire(message);
|
this.onUpdaterDidNotFindUpdateAvailableEmitter.fire(message);
|
||||||
}
|
}
|
||||||
notifyDownloadProgressChanged(message: ProgressInfo): void {
|
notifyDownloadProgressChanged(message: ProgressInfo): void {
|
||||||
this.onDownloadProgressEmitter.fire(message);
|
this.onDownloadProgressDidChangeEmitter.fire(message);
|
||||||
}
|
}
|
||||||
notifyDownloadFinished(message: UpdateInfo): void {
|
notifyDownloadFinished(message: UpdateInfo): void {
|
||||||
this.onDownloadFinishedEmitter.fire(message);
|
this.onDownloadDidFinishEmitter.fire(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -54,8 +54,8 @@ export class IDEUpdaterCommands implements CommandContribution {
|
|||||||
export namespace IDEUpdaterCommands {
|
export namespace IDEUpdaterCommands {
|
||||||
export const CHECK_FOR_UPDATES: Command = Command.toLocalizedCommand(
|
export const CHECK_FOR_UPDATES: Command = Command.toLocalizedCommand(
|
||||||
{
|
{
|
||||||
id: 'arduino-ide-check-for-updates',
|
id: 'arduino-check-for-ide-updates',
|
||||||
label: 'Check for Arduino IDE updates',
|
label: 'Check for Arduino IDE Updates',
|
||||||
category: 'Arduino',
|
category: 'Arduino',
|
||||||
},
|
},
|
||||||
'arduino/ide-updater/checkForUpdates'
|
'arduino/ide-updater/checkForUpdates'
|
||||||
|
@@ -1,19 +1,28 @@
|
|||||||
import { injectable, postConstruct, inject } from '@theia/core/shared/inversify';
|
import {
|
||||||
|
injectable,
|
||||||
|
postConstruct,
|
||||||
|
inject,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||||
import { addEventListener } from '@theia/core/lib/browser/widgets/widget';
|
import { addEventListener } from '@theia/core/lib/browser/widgets/widget';
|
||||||
import { DialogProps } from '@theia/core/lib/browser/dialogs';
|
import { DialogProps } from '@theia/core/lib/browser/dialogs';
|
||||||
import { AbstractDialog } from '../theia/dialogs/dialogs';
|
import { AbstractDialog } from '../theia/dialogs/dialogs';
|
||||||
import {
|
import {
|
||||||
LibraryPackage,
|
LibraryPackage,
|
||||||
|
LibrarySearch,
|
||||||
LibraryService,
|
LibraryService,
|
||||||
} from '../../common/protocol/library-service';
|
} from '../../common/protocol/library-service';
|
||||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||||
import { Installable } from '../../common/protocol';
|
import { Installable } from '../../common/protocol';
|
||||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
export class LibraryListWidget extends ListWidget<
|
||||||
|
LibraryPackage,
|
||||||
|
LibrarySearch
|
||||||
|
> {
|
||||||
static WIDGET_ID = 'library-list-widget';
|
static WIDGET_ID = 'library-list-widget';
|
||||||
static WIDGET_LABEL = nls.localize(
|
static WIDGET_LABEL = nls.localize(
|
||||||
'arduino/library/title',
|
'arduino/library/title',
|
||||||
@@ -21,9 +30,9 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LibraryService) protected service: LibraryService,
|
@inject(LibraryService) private service: LibraryService,
|
||||||
@inject(ListItemRenderer)
|
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<LibraryPackage>,
|
||||||
protected itemRenderer: ListItemRenderer<LibraryPackage>
|
@inject(LibraryFilterRenderer) filterRenderer: LibraryFilterRenderer
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
id: LibraryListWidget.WIDGET_ID,
|
id: LibraryListWidget.WIDGET_ID,
|
||||||
@@ -34,6 +43,8 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
|||||||
itemLabel: (item: LibraryPackage) => item.name,
|
itemLabel: (item: LibraryPackage) => item.name,
|
||||||
itemDeprecated: (item: LibraryPackage) => item.deprecated,
|
itemDeprecated: (item: LibraryPackage) => item.deprecated,
|
||||||
itemRenderer,
|
itemRenderer,
|
||||||
|
filterRenderer,
|
||||||
|
defaultSearchOptions: { query: '', type: 'All', topic: 'All' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +52,9 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
|||||||
protected override init(): void {
|
protected override init(): void {
|
||||||
super.init();
|
super.init();
|
||||||
this.toDispose.pushAll([
|
this.toDispose.pushAll([
|
||||||
this.notificationCenter.onLibraryDidInstall(() => this.refresh(undefined)),
|
this.notificationCenter.onLibraryDidInstall(() =>
|
||||||
|
this.refresh(undefined)
|
||||||
|
),
|
||||||
this.notificationCenter.onLibraryDidUninstall(() =>
|
this.notificationCenter.onLibraryDidUninstall(() =>
|
||||||
this.refresh(undefined)
|
this.refresh(undefined)
|
||||||
),
|
),
|
||||||
|
@@ -145,7 +145,10 @@ export class MonitorManagerProxyClientImpl
|
|||||||
if (
|
if (
|
||||||
selectedBoard?.fqbn !==
|
selectedBoard?.fqbn !==
|
||||||
this.lastConnectedBoard?.selectedBoard?.fqbn ||
|
this.lastConnectedBoard?.selectedBoard?.fqbn ||
|
||||||
selectedPort?.id !== this.lastConnectedBoard?.selectedPort?.id
|
Port.keyOf(selectedPort) !==
|
||||||
|
(this.lastConnectedBoard.selectedPort
|
||||||
|
? Port.keyOf(this.lastConnectedBoard.selectedPort)
|
||||||
|
: undefined)
|
||||||
) {
|
) {
|
||||||
this.onMonitorShouldResetEmitter.fire(null);
|
this.onMonitorShouldResetEmitter.fire(null);
|
||||||
this.lastConnectedBoard = {
|
this.lastConnectedBoard = {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import * as monaco from '@theia/monaco-editor-core';
|
import * as monaco from '@theia/monaco-editor-core';
|
||||||
|
import { OutputUri } from '@theia/output/lib/common/output-uri';
|
||||||
/**
|
/**
|
||||||
* Exclusive "ino" document selector for monaco.
|
* Exclusive "ino" document selector for monaco.
|
||||||
*/
|
*/
|
||||||
@@ -11,3 +12,11 @@ function selectorOf(
|
|||||||
exclusive: true, // <-- this should make sure the custom formatter has higher precedence over the LS formatter.
|
exclusive: true, // <-- this should make sure the custom formatter has higher precedence over the LS formatter.
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selector for the `monaco` resource in the Arduino _Output_ channel.
|
||||||
|
*/
|
||||||
|
export const ArduinoOutputSelector: monaco.languages.LanguageSelector = {
|
||||||
|
scheme: OutputUri.SCHEME,
|
||||||
|
pattern: '**/Arduino',
|
||||||
|
};
|
@@ -5,6 +5,7 @@ import { isOSX } from '@theia/core/lib/common/os';
|
|||||||
import { DisposableCollection, nls } from '@theia/core/lib/common';
|
import { DisposableCollection, nls } from '@theia/core/lib/common';
|
||||||
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
||||||
import { MonitorModel } from '../../monitor-model';
|
import { MonitorModel } from '../../monitor-model';
|
||||||
|
import { Unknown } from '../../../common/nls';
|
||||||
|
|
||||||
export namespace SerialMonitorSendInput {
|
export namespace SerialMonitorSendInput {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -86,8 +87,8 @@ export class SerialMonitorSendInput extends React.Component<
|
|||||||
? Board.toString(board, {
|
? Board.toString(board, {
|
||||||
useFqbn: false,
|
useFqbn: false,
|
||||||
})
|
})
|
||||||
: 'unknown',
|
: Unknown,
|
||||||
port ? port.address : 'unknown'
|
port ? port.address : Unknown
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -109,7 +109,7 @@ const _Row = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const timestamp =
|
const timestamp =
|
||||||
(data.timestamp &&
|
(data.timestamp &&
|
||||||
`${dateFormat(data.lines[index].timestamp, 'H:M:ss.l')} -> `) ||
|
`${dateFormat(data.lines[index].timestamp, 'HH:MM:ss.l')} -> `) ||
|
||||||
'';
|
'';
|
||||||
return (
|
return (
|
||||||
(data.lines[index].lineLen && (
|
(data.lines[index].lineLen && (
|
||||||
|
@@ -49,3 +49,14 @@
|
|||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
padding-bottom: 0 !important;
|
padding-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* High Contrast Theme rules */
|
||||||
|
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
|
||||||
|
.hc-black.hc-theia.theia-hc .arduino-select__option--is-selected {
|
||||||
|
outline: 1px solid var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .arduino-select__option--is-focused {
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
@@ -2,9 +2,11 @@ div#select-board-dialog {
|
|||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#select-board-dialog .selectBoardContainer .body {
|
div#select-board-dialog .selectBoardContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-board-dialog .head {
|
.select-board-dialog .head {
|
||||||
@@ -15,15 +17,16 @@ div.dialogContent.select-board-dialog > div.head .title {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
color: var(--theia-arduino-branding-primary);
|
color: var(--theia-editorWidget-foreground);
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#select-board-dialog .selectBoardContainer .body .list .item.selected {
|
|
||||||
|
div#select-board-dialog .selectBoardContainer .list .item.selected {
|
||||||
background: var(--theia-secondaryButton-hoverBackground);
|
background: var(--theia-secondaryButton-hoverBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
div#select-board-dialog .selectBoardContainer .list .item.selected i {
|
||||||
color: var(--theia-arduino-branding-primary);
|
color: var(--theia-arduino-branding-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +37,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
background: var(--theia-editor-background);
|
background: var(--theia-editor-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .search input {
|
#select-board-dialog .selectBoardContainer .search input {
|
||||||
border: none;
|
border: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -43,61 +46,66 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
display: flex;
|
display: flex;
|
||||||
color: var(--theia-editor-foreground);
|
color: var(--theia-input-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .search input:focus {
|
#select-board-dialog .selectBoardContainer .search input:focus {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .container {
|
#select-board-dialog .selectBoardContainer .container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0px 10px 0px 0px;
|
overflow: hidden;
|
||||||
min-width: 240px;
|
max-height: 100%;
|
||||||
max-width: 240px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .left.container .content {
|
#select-board-dialog .selectBoardContainer .container .content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#select-board-dialog .selectBoardContainer .left.container .content {
|
||||||
margin: 0 5px 0 0;
|
margin: 0 5px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .right.container .content {
|
#select-board-dialog .selectBoardContainer .right.container .content {
|
||||||
margin: 0 0 0 5px;
|
margin: 0 0 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .container .content .title {
|
#select-board-dialog .selectBoardContainer .container .content .title {
|
||||||
color: #7f8c8d;
|
color: var(--theia-editorWidget-foreground);
|
||||||
padding: 0px 0px 10px 0px;
|
padding: 0px 0px 10px 0px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .container .content .footer {
|
#select-board-dialog .selectBoardContainer .container .content .footer {
|
||||||
padding: 10px 5px 10px 0px;
|
padding: 10px 5px 10px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .container .content .loading {
|
#select-board-dialog .selectBoardContainer .container .content .loading {
|
||||||
font-size: var(--theia-ui-font-size1);
|
font-size: var(--theia-ui-font-size1);
|
||||||
color: var(--theia-arduino-branding-secondary);
|
color: var(--theia-editorWidget-foreground);
|
||||||
padding: 10px 5px 10px 10px;
|
padding: 10px 5px 10px 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
/* The max, min-height comes from `.body .list` 200px + 47px top padding - 2 * 10px top padding */
|
/* The max, min-height comes from `.list` 200px + 47px top padding - 2 * 10px top padding */
|
||||||
max-height: 227px;
|
max-height: 227px;
|
||||||
min-height: 227px;
|
min-height: 227px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list .item {
|
#select-board-dialog .selectBoardContainer .list .item {
|
||||||
padding: 10px 5px 10px 10px;
|
padding: 10px 5px 10px 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: end;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
flex: 1 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list .item .selected-icon {
|
#select-board-dialog .selectBoardContainer .list .item .selected-icon {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list .item .details {
|
#select-board-dialog .selectBoardContainer .list .item .details {
|
||||||
font-size: var(--theia-ui-font-size1);
|
font-size: var(--theia-ui-font-size1);
|
||||||
opacity: var(--theia-mod-disabled-opacity);
|
opacity: var(--theia-mod-disabled-opacity);
|
||||||
width: 155px; /* used heuristics for the calculation */
|
width: 155px; /* used heuristics for the calculation */
|
||||||
@@ -106,48 +114,42 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list .item.missing {
|
#select-board-dialog .selectBoardContainer .list .item.missing {
|
||||||
opacity: var(--theia-mod-disabled-opacity);
|
opacity: var(--theia-mod-disabled-opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list .item:hover {
|
#select-board-dialog .selectBoardContainer .list .item:hover {
|
||||||
background: var(--theia-secondaryButton-hoverBackground);
|
background: var(--theia-secondaryButton-hoverBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list .label {
|
#select-board-dialog .selectBoardContainer .list .label {
|
||||||
max-width: 215px;
|
|
||||||
width: 215px;
|
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list {
|
#select-board-dialog .selectBoardContainer .list {
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
min-height: 200px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .ports.list {
|
#select-board-dialog .selectBoardContainer .ports.list {
|
||||||
margin: 47px 0px 0px 0px; /* 47 is 37 as input height for the `Boards`, plus 10 margin bottom. */
|
margin: 47px 0px 0px 0px; /* 47 is 37 as input height for the `Boards`, plus 10 margin bottom. */
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .search {
|
#select-board-dialog .selectBoardContainer .search {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-Widget.dialogOverlay .dialogContent.select-board-dialog {
|
|
||||||
width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arduino-boards-toolbar-item-container {
|
.arduino-boards-toolbar-item-container {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||||
|
border: 1px solid var(--theia-arduino-toolbar-dropdown-border);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
@@ -164,10 +166,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-toolbar-item--protocol {
|
.arduino-boards-toolbar-item--protocol ,
|
||||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
|
||||||
}
|
|
||||||
|
|
||||||
.arduino-boards-dropdown-item--protocol {
|
.arduino-boards-dropdown-item--protocol {
|
||||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||||
}
|
}
|
||||||
@@ -180,9 +179,6 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-toolbar-item--label {
|
.arduino-boards-toolbar-item--label {
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +204,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
.arduino-boards-dropdown-list--items-container {
|
.arduino-boards-dropdown-list--items-container {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-height: 404px;
|
max-height: 404px;
|
||||||
|
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-list--items-container::-webkit-scrollbar {
|
.arduino-boards-dropdown-list--items-container::-webkit-scrollbar {
|
||||||
@@ -226,6 +223,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-item--label {
|
.arduino-boards-dropdown-item--label {
|
||||||
|
overflow: hidden;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +246,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-item--selected
|
.arduino-boards-dropdown-item--selected
|
||||||
.arduino-boards-dropdown-item--port-label {
|
.arduino-boards-dropdown-item--port-label {
|
||||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +259,26 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arduino-board-dropdown-footer {
|
.arduino-board-dropdown-footer {
|
||||||
color: var(--theia-arduino-branding-primary);
|
color: var(--theia-secondaryButton-foreground);
|
||||||
border-top: 1px solid var(--theia-arduino-toolbar-dropdown-border);
|
border-top: 1px solid var(--theia-dropdown-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* High Contrast Theme rules */
|
||||||
|
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
|
||||||
|
.hc-black.hc-theia.theia-hc #select-board-dialog .selectBoardContainer .list .item:hover {
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc div#select-board-dialog .selectBoardContainer .list .item.selected {
|
||||||
|
outline: 1px solid var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-height: 400px) {
|
||||||
|
div.dialogContent.select-board-dialog > div.head {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#select-board-dialog .selectBoardContainer .container .content .title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
.certificate-uploader-dialog {
|
#certificate-uploader-dialog-container > .dialogBlock {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
.certificate-uploader-dialog .arduino-select__control {
|
.certificate-uploader-dialog .arduino-select__control {
|
||||||
height: 31px;
|
height: 31px;
|
||||||
background: var(--theia-menubar-selectionBackground) !important;
|
background: var(--theia-dropdown-background) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.certificate-uploader-dialog .dialogRow > button{
|
.certificate-uploader-dialog .dialogRow > button{
|
||||||
@@ -15,9 +15,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.certificate-uploader-dialog .certificate-list {
|
.certificate-uploader-dialog .certificate-list {
|
||||||
border: 1px solid #BDC7C7;
|
border: 1px solid var(--theia-editorWidget-border);
|
||||||
border-radius: 2px;;
|
border-radius: 2px;;
|
||||||
background: var(--theia-menubar-selectionBackground) !important;
|
color: var(--theia-editor-foreground);
|
||||||
|
background-color: var(--theia-editor-background);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -60,9 +61,10 @@
|
|||||||
|
|
||||||
.certificate-add {
|
.certificate-add {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background-color: var(--theia-list-hoverBackground);
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid #BDC7C7;
|
border: 1px solid var(--theia-editorWidget-border);
|
||||||
|
color: var(--theia-editorWidget-foreground);
|
||||||
|
background-color: var(--theia-editorWidget-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.certificate-add input {
|
.certificate-add input {
|
||||||
|
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
.cloud-sketchbook-welcome > .item .link {
|
.cloud-sketchbook-welcome > .item .link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--theia-arduino-branding-primary);
|
color: var(--theia-textLink-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pull-sketch-icon {
|
.pull-sketch-icon {
|
||||||
|
@@ -9,15 +9,17 @@
|
|||||||
total = padding + margin = 96px
|
total = padding + margin = 96px
|
||||||
*/
|
*/
|
||||||
max-width: calc(100% - 96px) !important;
|
max-width: calc(100% - 96px) !important;
|
||||||
|
min-width: unset;
|
||||||
|
max-height: 560px;
|
||||||
padding: 0 28px;
|
padding: 0 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-Widget.dialogOverlay .dialogBlock .dialogTitle {
|
.p-Widget.dialogOverlay .dialogBlock .dialogTitle {
|
||||||
padding: 36px 0 28px;
|
padding: 20px 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
font-size: var(--theia-ui-font-size2);
|
font-size: var(--theia-ui-font-size2);
|
||||||
color: var(--theia-list-inactiveSelectionForeground);
|
color: var(--theia-editorWidget-foreground);
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@
|
|||||||
|
|
||||||
.p-Widget.dialogOverlay .dialogBlock .dialogContent {
|
.p-Widget.dialogOverlay .dialogBlock .dialogContent {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-Widget.dialogOverlay .dialogBlock .dialogContent > input {
|
.p-Widget.dialogOverlay .dialogBlock .dialogContent > input {
|
||||||
@@ -75,3 +78,10 @@
|
|||||||
.fa.disabled {
|
.fa.disabled {
|
||||||
opacity: .4;
|
opacity: .4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media only screen and (max-height: 560px) {
|
||||||
|
.p-Widget.dialogOverlay .dialogBlock {
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
/* Show the dirty indicator on unclosable widgets. On hover, it should still show the dot instead of the X. */
|
/* Show the dirty indicator on unclosable widgets. On hover, it should still show the dot instead of the X. */
|
||||||
/* https://github.com/arduino/arduino-pro-ide/issues/380 */
|
/* https://github.com/arduino/arduino-pro-ide/issues/380 */
|
||||||
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.theia-mod-dirty > .p-TabBar-tabCloseIcon:hover {
|
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.a-mod-uncloseable.theia-mod-dirty > .p-TabBar-tabCloseIcon:before {
|
||||||
background-size: 13px;
|
content: "\ea71";
|
||||||
background-image: var(--theia-icon-circle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-list-row.show-file-icons.focused {
|
.monaco-list-row.show-file-icons.focused {
|
||||||
background-color: #d6ebff;
|
background-color: var(--theia-quickInputList-focusBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-editor .view-overlays .compiler-error {
|
.monaco-editor .view-overlays .compiler-error {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
.firmware-uploader-dialog {
|
#firmware-uploader-dialog-container > .dialogBlock {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.firmware-uploader-dialog .dialogRow > button{
|
.firmware-uploader-dialog .dialogRow > button{
|
||||||
width: 33%;
|
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
.ide-updater-dialog {
|
#ide-updater-dialog-container > .dialogBlock {
|
||||||
width: 546px;
|
width: 546px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,6 +10,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ide-updater-dialog--downloading {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.ide-updater-dialog--logo-container {
|
.ide-updater-dialog--logo-container {
|
||||||
margin-right: 28px;
|
margin-right: 28px;
|
||||||
}
|
}
|
||||||
@@ -23,38 +27,50 @@
|
|||||||
.dialogContent.ide-updater-dialog
|
.dialogContent.ide-updater-dialog
|
||||||
.ide-updater-dialog--content
|
.ide-updater-dialog--content
|
||||||
.ide-updater-dialog--new-version-text.dialogSection {
|
.ide-updater-dialog--new-version-text.dialogSection {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ide-updater-dialog .changelog-container {
|
.ide-updater-dialog .changelog {
|
||||||
color: var(--theia-dropdown-foreground);
|
color: var(--theia-editor-foreground);
|
||||||
background-color: var(--theia-dropdown-background);
|
background-color: var(--theia-editor-background);
|
||||||
border: 1px solid var(--theia-tree-indentGuidesStroke);
|
|
||||||
border-radius: 2px;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 180px;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ide-updater-dialog .changelog-container a {
|
.dialogContent.ide-updater-dialog
|
||||||
color: #018184;
|
.ide-updater-dialog--content
|
||||||
|
.ide-updater-dialog--new-version-text
|
||||||
|
.dialogRow.changelog-container {
|
||||||
|
align-items: flex-start;
|
||||||
|
border: 1px solid var(--theia-editorWidget-border);
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ide-updater-dialog .changelog-container a:hover {
|
.ide-updater-dialog .changelog a {
|
||||||
|
color: var(--theia-textLink-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ide-updater-dialog .changelog a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ide-updater-dialog .changelog-container code {
|
.ide-updater-dialog .changelog code {
|
||||||
background: #ecf1f1;
|
background: var(--theia-textBlockQuote-background);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ide-updater-dialog .changelog-container a code {
|
.ide-updater-dialog .changelog a code {
|
||||||
color: #018184;
|
color: var(--theia-textLink-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ide-updater-dialog .buttons-container {
|
.ide-updater-dialog .buttons-container {
|
||||||
@@ -77,3 +93,14 @@
|
|||||||
.ide-updater-dialog .buttons-container .push {
|
.ide-updater-dialog .buttons-container .push {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ide-updater-dialog--content {
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ide-updater-dialog-container .skip-version-button {
|
||||||
|
margin-left: 79px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
@@ -135,3 +135,23 @@ button.secondary[disabled], .theia-button.secondary[disabled] {
|
|||||||
.fa-reload {
|
.fa-reload {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* High Contrast Theme rules */
|
||||||
|
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
|
||||||
|
.hc-black.hc-theia.theia-hc button.theia-button:hover,
|
||||||
|
.hc-black.hc-theia.theia-hc .theia-button:hover {
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc button.theia-button,
|
||||||
|
.hc-black.hc-theia.theia-hc .theia-button,
|
||||||
|
.hc-black.hc-theia.theia-hc button.theia-button.secondary {
|
||||||
|
border: 1px solid var(--theia-button-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .theia-notification-list-item:hover:not(:focus) {
|
||||||
|
background-color: var(--theia-notifications-background);
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
@@ -8,13 +8,35 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arduino-list-widget .search-bar {
|
.arduino-list-widget .search-bar {
|
||||||
margin: 0px 10px 10px 15px;
|
margin: 0px 10px 5px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-list-widget .search-bar:focus {
|
.arduino-list-widget .search-bar:focus {
|
||||||
border-color: var(--theia-focusBorder);
|
border-color: var(--theia-focusBorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.arduino-list-widget .filter-bar {
|
||||||
|
margin: 0px 10px 5px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-list-widget .filter-bar > * {
|
||||||
|
padding: 5px 5px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-list-widget .filter-bar .filter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-list-widget .filter-bar .filter > select {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-list-widget .filter-bar .filter-label {
|
||||||
|
display: flex;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
.filterable-list-container {
|
.filterable-list-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -22,34 +44,21 @@
|
|||||||
height: 100%; /* This has top be 100% down to the `scrollContainer`. */
|
height: 100%; /* This has top be 100% down to the `scrollContainer`. */
|
||||||
}
|
}
|
||||||
|
|
||||||
.filterable-list-container .items-container {
|
.filterable-list-container .items-container > div > div:nth-child(odd) {
|
||||||
height: 100%; /* This has to be propagated down from the widget. */
|
|
||||||
position: relative; /* To fix the `top` of the vertical toolbar. */
|
|
||||||
}
|
|
||||||
|
|
||||||
.filterable-list-container .items-container > div:nth-child(odd) {
|
|
||||||
background-color: var(--theia-sideBar-background);
|
background-color: var(--theia-sideBar-background);
|
||||||
filter: contrast(105%);
|
filter: contrast(105%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filterable-list-container .items-container > div:nth-child(even) {
|
.filterable-list-container .items-container > div > div:nth-child(even) {
|
||||||
background-color: var(--theia-sideBar-background);
|
background-color: var(--theia-sideBar-background);
|
||||||
filter: contrast(95%);
|
filter: contrast(95%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filterable-list-container .items-container > div:hover {
|
.filterable-list-container .items-container > div > div:hover {
|
||||||
background-color: var(--theia-sideBar-background);
|
background-color: var(--theia-sideBar-background);
|
||||||
filter: contrast(90%);
|
filter: contrast(90%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Perfect scrollbar does not like if we explicitly set the `background-color` of the contained elements.
|
|
||||||
See above: `.filterable-list-container .items-container > div:nth-child(odd|event)`.
|
|
||||||
We have to increase `z-index` of the scroll-bar thumb. Otherwise, the thumb is not visible.
|
|
||||||
https://github.com/arduino/arduino-pro-ide/issues/82 */
|
|
||||||
.arduino-list-widget .filterable-list-container .items-container .ps__rail-y {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.component-list-item {
|
.component-list-item {
|
||||||
padding: 10px 10px 10px 15px;
|
padding: 10px 10px 10px 15px;
|
||||||
font-size: var(--theia-ui-font-size1);
|
font-size: var(--theia-ui-font-size1);
|
||||||
@@ -113,7 +122,7 @@ https://github.com/arduino/arduino-pro-ide/issues/82 */
|
|||||||
|
|
||||||
.component-list-item[min-width~="170px"] .footer {
|
.component-list-item[min-width~="170px"] .footer {
|
||||||
padding: 5px 5px 0px 0px;
|
padding: 5px 5px 0px 0px;
|
||||||
min-height: 30px;
|
min-height: 35px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
}
|
}
|
||||||
@@ -122,10 +131,6 @@ https://github.com/arduino/arduino-pro-ide/issues/82 */
|
|||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-list-item .footer > * {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.component-list-item:hover .footer > * {
|
.component-list-item:hover .footer > * {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 5px 0px 0px 10px;
|
margin: 5px 0px 0px 10px;
|
||||||
@@ -145,3 +150,14 @@ https://github.com/arduino/arduino-pro-ide/issues/82 */
|
|||||||
.component-list-item a:hover {
|
.component-list-item a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* High Contrast Theme rules */
|
||||||
|
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
|
||||||
|
.hc-black.hc-theia.theia-hc .component-list-item .header .installed:hover:before {
|
||||||
|
background-color: transparent;
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .component-list-item .header .installed:before {
|
||||||
|
border: 1px solid var(--theia-button-border);
|
||||||
|
}
|
@@ -173,18 +173,73 @@
|
|||||||
|
|
||||||
/* Output */
|
/* Output */
|
||||||
.theia-output .editor-container {
|
.theia-output .editor-container {
|
||||||
background-color: var(--theia-arduino-output-background);
|
background-color: var(--theia-terminal-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theia-output .monaco-editor .lines-content.monaco-editor-background {
|
.theia-output .monaco-editor .lines-content.monaco-editor-background {
|
||||||
background-color: var(--theia-arduino-output-background);
|
background-color: var(--theia-terminal-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theia-output .monaco-editor .lines-content.monaco-editor-background .view-lines .view-line .mtk1:not(.theia-output-error):not(.theia-output-warning) {
|
.theia-output .monaco-editor .lines-content.monaco-editor-background .view-lines .view-line .mtk1:not(.theia-output-error):not(.theia-output-warning) {
|
||||||
color: var(--theia-arduino-output-foreground);
|
color: var(--theia-terminal-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theia-output .monaco-editor .margin {
|
.theia-output .monaco-editor .margin {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
background-color: var(--theia-arduino-output-background);
|
background-color: var(--theia-terminal-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* High Contrast Theme rules */
|
||||||
|
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
|
||||||
|
.hc-black.hc-theia.theia-hc .p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div {
|
||||||
|
background: var(--theia-arduino-toolbar-button-background);
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div.toggle-serial-plotter,
|
||||||
|
.hc-black.hc-theia.theia-hc .p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div.toggle-serial-monitor {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .item.arduino-tool-item.toggled .arduino-verify-sketch--toolbar,
|
||||||
|
.hc-black.hc-theia.theia-hc .item.arduino-tool-item.toggled .arduino-upload-sketch--toolbar {
|
||||||
|
background-color: var(--theia-arduino-toolbar-button-background) !important;
|
||||||
|
outline: 1px solid var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .arduino-boards-dropdown-item:hover {
|
||||||
|
background: var(--theia-dropdown-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .arduino-boards-dropdown-item:hover {
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc #theia-main-content-panel .p-TabBar .p-TabBar-tab.p-mod-current {
|
||||||
|
outline: 1px solid var(--theia-focusBorder);
|
||||||
|
outline-offset: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc #theia-main-content-panel .p-TabBar .p-TabBar-tab:hover {
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
outline-offset: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon:hover {
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .quick-input-list .monaco-list-row.focused {
|
||||||
|
outline: 1px dotted var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .quick-input-list .monaco-list-row:hover {
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .quick-input-widget {
|
||||||
|
outline: 1px solid var(--theia-contrastBorder);
|
||||||
|
outline-offset: -1px;
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar--outer {
|
.progress-bar--outer {
|
||||||
background: #e5e5e5;
|
background: var(--theia-editorWidget-background);
|
||||||
border-radius: 11px;
|
border-radius: 11px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
.progress-bar--inner {
|
.progress-bar--inner {
|
||||||
transition: width 1s;
|
transition: width 1s;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #008184;
|
background: var(--theia-progressBar-background);
|
||||||
border-radius: 11px;
|
border-radius: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,6 +6,11 @@
|
|||||||
|
|
||||||
#arduino-settings-dialog-container > .dialogBlock > .dialogContent {
|
#arduino-settings-dialog-container > .dialogBlock > .dialogContent {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#arduino-settings-dialog-container > .dialogBlock > .dialogControl {
|
||||||
|
padding: 16px 0 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-settings-dialog .content {
|
.arduino-settings-dialog .content {
|
||||||
@@ -16,6 +21,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-settings-dialog .with-margin {
|
.arduino-settings-dialog .with-margin {
|
||||||
@@ -61,8 +67,19 @@
|
|||||||
color: var(--theia-textLink-activeForeground);
|
color: var(--theia-textLink-activeForeground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .react-tabs__tab--selected {
|
||||||
|
background: var(--theia-editorWidget-background);
|
||||||
|
border-color: var(--theia-tab-activeBorder);
|
||||||
|
color: var(--theia-tab-activeForeground);
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .react-tabs__tab-list {
|
||||||
|
border-color: var(--theia-tab-activeBorder);
|
||||||
|
}
|
||||||
|
|
||||||
.arduino-settings-dialog .react-tabs__tab-panel {
|
.arduino-settings-dialog .react-tabs__tab-panel {
|
||||||
padding-bottom: 25px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-settings-dialog .react-tabs__tab-list {
|
.arduino-settings-dialog .react-tabs__tab-list {
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0px;
|
right: 14px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(0px, -50%);
|
transform: translate(0px, -50%);
|
||||||
height: calc(100% - 4px);
|
height: calc(100% - 4px);
|
||||||
|
@@ -45,7 +45,7 @@
|
|||||||
.active-sketch {
|
.active-sketch {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background-color: var(--theia-list-activeSelectionBackground) !important;
|
background-color: var(--theia-list-activeSelectionBackground) !important;
|
||||||
color: var(--theia-list-inactiveSelectionForeground) !important;
|
color: var(--theia-list-activeSelectionForeground) !important;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,4 +69,21 @@
|
|||||||
.theia-Tree:focus .theia-TreeNode.theia-mod-selected,
|
.theia-Tree:focus .theia-TreeNode.theia-mod-selected,
|
||||||
.theia-Tree .ReactVirtualized__List:focus .theia-TreeNode.theia-mod-selected {
|
.theia-Tree .ReactVirtualized__List:focus .theia-TreeNode.theia-mod-selected {
|
||||||
background: var(--theia-list-inactiveSelectionBackground);
|
background: var(--theia-list-inactiveSelectionBackground);
|
||||||
|
color: var(--theia-list-inactiveSelectionForeground) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* High Contrast Theme rules */
|
||||||
|
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
|
||||||
|
.hc-black.hc-theia.theia-hc .theia-TreeNode:hover {
|
||||||
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .theia-Tree .theia-TreeNode.theia-mod-selected {
|
||||||
|
outline: 1px dotted var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black.hc-theia.theia-hc .theia-Tree:focus .theia-TreeNode.theia-mod-selected,
|
||||||
|
.hc-black.hc-theia.theia-hc .theia-Tree .ReactVirtualized__List:focus .theia-TreeNode.theia-mod-selected {
|
||||||
|
outline: 1px solid var(--theia-focusBorder);
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user-fields-dialog-content .field-label {
|
.user-fields-dialog-content .field-label {
|
||||||
color: #2c353a;
|
color: var(--theia-editorWidget-foreground);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@@ -1,65 +1,29 @@
|
|||||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
|
||||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
|
||||||
import { CommandService } from '@theia/core/lib/common/command';
|
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
|
||||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
|
||||||
import {
|
|
||||||
ConnectionStatusService,
|
|
||||||
ConnectionStatus,
|
|
||||||
} from '@theia/core/lib/browser/connection-status-service';
|
|
||||||
import {
|
import {
|
||||||
ApplicationShell as TheiaApplicationShell,
|
ApplicationShell as TheiaApplicationShell,
|
||||||
DockPanel,
|
DockPanel,
|
||||||
|
DockPanelRenderer as TheiaDockPanelRenderer,
|
||||||
Panel,
|
Panel,
|
||||||
|
SaveOptions,
|
||||||
|
SHELL_TABBAR_CONTEXT_MENU,
|
||||||
|
TabBar,
|
||||||
Widget,
|
Widget,
|
||||||
} from '@theia/core/lib/browser';
|
} from '@theia/core/lib/browser';
|
||||||
import { Sketch } from '../../../common/protocol';
|
import {
|
||||||
import { SaveAsSketch } from '../../contributions/save-as-sketch';
|
ConnectionStatus,
|
||||||
import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
ConnectionStatusService,
|
||||||
import { nls } from '@theia/core/lib/common';
|
} from '@theia/core/lib/browser/connection-status-service';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { ToolbarAwareTabBar } from './tab-bars';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ApplicationShell extends TheiaApplicationShell {
|
export class ApplicationShell extends TheiaApplicationShell {
|
||||||
@inject(CommandService)
|
|
||||||
protected readonly commandService: CommandService;
|
|
||||||
|
|
||||||
@inject(MessageService)
|
@inject(MessageService)
|
||||||
protected readonly messageService: MessageService;
|
private readonly messageService: MessageService;
|
||||||
|
|
||||||
@inject(SketchesServiceClientImpl)
|
|
||||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
|
||||||
|
|
||||||
@inject(ConnectionStatusService)
|
@inject(ConnectionStatusService)
|
||||||
protected readonly connectionStatusService: ConnectionStatusService;
|
private readonly connectionStatusService: ConnectionStatusService;
|
||||||
|
|
||||||
protected override track(widget: Widget): void {
|
|
||||||
super.track(widget);
|
|
||||||
if (widget instanceof OutputWidget) {
|
|
||||||
widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
|
|
||||||
}
|
|
||||||
if (widget instanceof EditorWidget) {
|
|
||||||
// Make the editor un-closeable asynchronously.
|
|
||||||
this.sketchesServiceClient.currentSketch().then((sketch) => {
|
|
||||||
if (CurrentSketch.isValid(sketch)) {
|
|
||||||
if (!this.isSketchFile(widget.editor.uri, sketch.uri)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Sketch.isInSketch(widget.editor.uri, sketch)) {
|
|
||||||
widget.title.closable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isSketchFile(uri: URI, sketchUriString: string): boolean {
|
|
||||||
const sketchUri = new URI(sketchUriString);
|
|
||||||
if (uri.parent.isEqual(sketchUri)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
override async addWidget(
|
override async addWidget(
|
||||||
widget: Widget,
|
widget: Widget,
|
||||||
@@ -99,7 +63,7 @@ export class ApplicationShell extends TheiaApplicationShell {
|
|||||||
return topPanel;
|
return topPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async saveAll(): Promise<void> {
|
override async saveAll(options?: SaveOptions): Promise<void> {
|
||||||
if (
|
if (
|
||||||
this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE
|
this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE
|
||||||
) {
|
) {
|
||||||
@@ -111,12 +75,33 @@ export class ApplicationShell extends TheiaApplicationShell {
|
|||||||
);
|
);
|
||||||
return; // Theia does not reject on failed save: https://github.com/eclipse-theia/theia/pull/8803
|
return; // Theia does not reject on failed save: https://github.com/eclipse-theia/theia/pull/8803
|
||||||
}
|
}
|
||||||
await super.saveAll();
|
return super.saveAll(options);
|
||||||
const options = { execOnlyIfTemp: true, openAfterMove: true };
|
}
|
||||||
await this.commandService.executeCommand(
|
}
|
||||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
|
||||||
options
|
export class DockPanelRenderer extends TheiaDockPanelRenderer {
|
||||||
|
override createTabBar(): TabBar<Widget> {
|
||||||
|
const renderer = this.tabBarRendererFactory();
|
||||||
|
// `ToolbarAwareTabBar` is from IDE2 and not from Theia. Check the imports.
|
||||||
|
const tabBar = new ToolbarAwareTabBar(
|
||||||
|
this.tabBarToolbarRegistry,
|
||||||
|
this.tabBarToolbarFactory,
|
||||||
|
this.breadcrumbsRendererFactory,
|
||||||
|
{
|
||||||
|
renderer,
|
||||||
|
// Scroll bar options
|
||||||
|
handlers: ['drag-thumb', 'keyboard', 'wheel', 'touch'],
|
||||||
|
useBothWheelAxes: true,
|
||||||
|
scrollXMarginOffset: 4,
|
||||||
|
suppressScrollY: true,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
this.tabBarClasses.forEach((c) => tabBar.addClass(c));
|
||||||
|
renderer.tabBar = tabBar;
|
||||||
|
tabBar.disposed.connect(() => renderer.dispose());
|
||||||
|
renderer.contextMenuPath = SHELL_TABBAR_CONTEXT_MENU;
|
||||||
|
tabBar.currentChanged.connect(this.onCurrentTabChanged, this);
|
||||||
|
return tabBar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { DefaultWindowService as TheiaDefaultWindowService } from '@theia/core/lib/browser/window/default-window-service';
|
||||||
|
import { ContainerModule } from '@theia/core/shared/inversify';
|
||||||
|
import { DefaultWindowService } from './default-window-service';
|
||||||
|
import { WindowServiceExt } from './window-service-ext';
|
||||||
|
|
||||||
|
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||||
|
bind(DefaultWindowService).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaDefaultWindowService).toService(DefaultWindowService);
|
||||||
|
bind(WindowServiceExt).toService(DefaultWindowService);
|
||||||
|
});
|
@@ -5,6 +5,7 @@ import {
|
|||||||
CommonCommands,
|
CommonCommands,
|
||||||
} from '@theia/core/lib/browser/common-frontend-contribution';
|
} from '@theia/core/lib/browser/common-frontend-contribution';
|
||||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||||
|
import type { OnWillStopAction } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CommonFrontendContribution extends TheiaCommonFrontendContribution {
|
export class CommonFrontendContribution extends TheiaCommonFrontendContribution {
|
||||||
@@ -48,4 +49,9 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
|
|||||||
registry.unregisterMenuAction(command);
|
registry.unregisterMenuAction(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onWillStop(): OnWillStopAction | undefined {
|
||||||
|
// This is NOOP here. All window close and app quit requests are handled in the `Close` contribution.
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ import {
|
|||||||
import { ArduinoDaemon } from '../../../common/protocol';
|
import { ArduinoDaemon } from '../../../common/protocol';
|
||||||
import { NotificationCenter } from '../../notification-center';
|
import { NotificationCenter } from '../../notification-center';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
import debounce = require('lodash.debounce');
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
|
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
|
||||||
@@ -36,10 +37,11 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
|
|||||||
this.notificationCenter.onDaemonDidStop(
|
this.notificationCenter.onDaemonDidStop(
|
||||||
() => (this.connectedPort = undefined)
|
() => (this.connectedPort = undefined)
|
||||||
);
|
);
|
||||||
this.wsConnectionProvider.onIncomingMessageActivity(() => {
|
const refresh = debounce(() => {
|
||||||
this.updateStatus(!!this.connectedPort);
|
this.updateStatus(!!this.connectedPort);
|
||||||
this.schedulePing();
|
this.schedulePing();
|
||||||
});
|
}, this.options.offlineTimeout - 10);
|
||||||
|
this.wsConnectionProvider.onIncomingMessageActivity(() => refresh());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
import { DefaultWindowService as TheiaDefaultWindowService } from '@theia/core/lib/browser/window/default-window-service';
|
||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { WindowServiceExt } from './window-service-ext';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class DefaultWindowService
|
||||||
|
extends TheiaDefaultWindowService
|
||||||
|
implements WindowServiceExt
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The default implementation always resolves to `true`.
|
||||||
|
* IDE2 does not use it. It's currently an electron-only app.
|
||||||
|
*/
|
||||||
|
async isFirstWindow(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
13
arduino-ide-extension/src/browser/theia/core/status-bar.ts
Normal file
13
arduino-ide-extension/src/browser/theia/core/status-bar.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { StatusBarImpl as TheiaStatusBarImpl } from '@theia/core/lib/browser';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class StatusBarImpl extends TheiaStatusBarImpl {
|
||||||
|
override async removeElement(id: string): Promise<void> {
|
||||||
|
await this.ready;
|
||||||
|
if (this.entries.delete(id)) {
|
||||||
|
// Unlike Theia, IDE2 updates the status bar only if the element to remove was among the entries. Otherwise, it's a NOOP.
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,13 @@
|
|||||||
import { TabBar } from '@theia/core/shared/@phosphor/widgets';
|
import type { TabBar } from '@theia/core/shared/@phosphor/widgets';
|
||||||
import { Saveable } from '@theia/core/lib/browser/saveable';
|
import { Saveable } from '@theia/core/lib/browser/saveable';
|
||||||
import { TabBarRenderer as TheiaTabBarRenderer } from '@theia/core/lib/browser/shell/tab-bars';
|
import {
|
||||||
|
TabBarRenderer as TheiaTabBarRenderer,
|
||||||
|
ToolbarAwareTabBar as TheiaToolbarAwareTabBar,
|
||||||
|
} from '@theia/core/lib/browser/shell/tab-bars';
|
||||||
|
import debounce = require('lodash.debounce');
|
||||||
|
|
||||||
export class TabBarRenderer extends TheiaTabBarRenderer {
|
export class TabBarRenderer extends TheiaTabBarRenderer {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
override createTabClass(data: TabBar.IRenderData<any>): string {
|
override createTabClass(data: TabBar.IRenderData<any>): string {
|
||||||
let className = super.createTabClass(data);
|
let className = super.createTabClass(data);
|
||||||
if (!data.title.closable && Saveable.isDirty(data.title.owner)) {
|
if (!data.title.closable && Saveable.isDirty(data.title.owner)) {
|
||||||
@@ -16,3 +21,16 @@ export class TabBarRenderer extends TheiaTabBarRenderer {
|
|||||||
// Context menus are empty, so they have been removed
|
// Context menus are empty, so they have been removed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ToolbarAwareTabBar extends TheiaToolbarAwareTabBar {
|
||||||
|
protected override async updateBreadcrumbs(): Promise<void> {
|
||||||
|
// NOOP
|
||||||
|
// IDE2 does not use breadcrumbs.
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly doUpdateToolbar = debounce(() => super.updateToolbar(), 500);
|
||||||
|
protected override updateToolbar(): void {
|
||||||
|
// Unlike Theia, IDE2 debounces the toolbar updates with 500ms
|
||||||
|
this.doUpdateToolbar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,11 +1,78 @@
|
|||||||
import type { MaybePromise } from '@theia/core';
|
import type { MaybePromise } from '@theia/core';
|
||||||
import type { Widget } from '@theia/core/lib/browser';
|
import type { Widget } from '@theia/core/lib/browser';
|
||||||
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
|
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
|
||||||
import { injectable } from '@theia/core/shared/inversify';
|
import {
|
||||||
|
inject,
|
||||||
|
injectable,
|
||||||
|
postConstruct,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
|
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||||
|
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||||
import deepEqual = require('deep-equal');
|
import deepEqual = require('deep-equal');
|
||||||
|
import {
|
||||||
|
CurrentSketch,
|
||||||
|
SketchesServiceClientImpl,
|
||||||
|
} from '../../../common/protocol/sketches-service-client-impl';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class WidgetManager extends TheiaWidgetManager {
|
export class WidgetManager extends TheiaWidgetManager {
|
||||||
|
@inject(SketchesServiceClientImpl)
|
||||||
|
private readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
this.sketchesServiceClient.onCurrentSketchDidChange((sketch) =>
|
||||||
|
this.maybeSetWidgetUncloseable(
|
||||||
|
sketch,
|
||||||
|
...Array.from(this.widgets.values())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override getOrCreateWidget<T extends Widget>(
|
||||||
|
factoryId: string,
|
||||||
|
options?: unknown
|
||||||
|
): Promise<T> {
|
||||||
|
const unresolvedWidget = super.getOrCreateWidget<T>(factoryId, options);
|
||||||
|
unresolvedWidget.then(async (widget) => {
|
||||||
|
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||||
|
this.maybeSetWidgetUncloseable(sketch, widget);
|
||||||
|
});
|
||||||
|
return unresolvedWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
private maybeSetWidgetUncloseable(
|
||||||
|
sketch: CurrentSketch,
|
||||||
|
...widgets: Widget[]
|
||||||
|
): void {
|
||||||
|
const sketchFileUris =
|
||||||
|
CurrentSketch.isValid(sketch) &&
|
||||||
|
new Set([sketch.mainFileUri, ...sketch.rootFolderFileUris]);
|
||||||
|
for (const widget of widgets) {
|
||||||
|
if (widget instanceof OutputWidget) {
|
||||||
|
this.setWidgetUncloseable(widget); // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
|
||||||
|
} else if (widget instanceof EditorWidget) {
|
||||||
|
// Make the editor un-closeable asynchronously.
|
||||||
|
const uri = widget.editor.uri.toString();
|
||||||
|
if (!!sketchFileUris && sketchFileUris.has(uri)) {
|
||||||
|
this.setWidgetUncloseable(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setWidgetUncloseable(widget: Widget): void {
|
||||||
|
const { title } = widget;
|
||||||
|
if (title.closable) {
|
||||||
|
title.closable = false;
|
||||||
|
}
|
||||||
|
// Show the dirty indicator on uncloseable widgets when hovering over the title. Instead of showing the `X` for close.
|
||||||
|
const uncloseableClass = 'a-mod-uncloseable';
|
||||||
|
if (!title.className.includes(uncloseableClass)) {
|
||||||
|
title.className += title.className + ` ${uncloseableClass}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customized to find any existing widget based on `options` deepEquals instead of string equals.
|
* Customized to find any existing widget based on `options` deepEquals instead of string equals.
|
||||||
* See https://github.com/eclipse-theia/theia/issues/11309.
|
* See https://github.com/eclipse-theia/theia/issues/11309.
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
export const WindowServiceExt = Symbol('WindowServiceExt');
|
||||||
|
export interface WindowServiceExt {
|
||||||
|
/**
|
||||||
|
* Returns with a promise that resolves to `true` if the current window is the first window.
|
||||||
|
*/
|
||||||
|
isFirstWindow(): Promise<boolean>;
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
import { MenuModelRegistry } from '@theia/core';
|
||||||
|
import { CommonCommands } from '@theia/core/lib/browser';
|
||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { EditorMenuContribution as TheiaEditorMenuContribution } from '@theia/editor/lib/browser/editor-menu';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class EditorMenuContribution extends TheiaEditorMenuContribution {
|
||||||
|
override registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
super.registerMenus(registry);
|
||||||
|
registry.unregisterMenuAction(CommonCommands.CLOSE_MAIN_TAB.id);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||||
import { LabelProvider } from '@theia/core/lib/browser';
|
import type { NavigatableWidgetOptions } from '@theia/core/lib/browser';
|
||||||
import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
|
import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
|
||||||
import {
|
import {
|
||||||
CurrentSketch,
|
CurrentSketch,
|
||||||
@@ -13,16 +13,16 @@ import { nls } from '@theia/core/lib/common';
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class EditorWidgetFactory extends TheiaEditorWidgetFactory {
|
export class EditorWidgetFactory extends TheiaEditorWidgetFactory {
|
||||||
@inject(SketchesService)
|
@inject(SketchesService)
|
||||||
protected readonly sketchesService: SketchesService;
|
private readonly sketchesService: SketchesService;
|
||||||
|
|
||||||
@inject(SketchesServiceClientImpl)
|
@inject(SketchesServiceClientImpl)
|
||||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
private readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||||
|
|
||||||
@inject(LabelProvider)
|
protected override async createEditor(
|
||||||
protected override readonly labelProvider: LabelProvider;
|
uri: URI,
|
||||||
|
options: NavigatableWidgetOptions
|
||||||
protected override async createEditor(uri: URI): Promise<EditorWidget> {
|
): Promise<EditorWidget> {
|
||||||
const widget = await super.createEditor(uri);
|
const widget = await super.createEditor(uri, options);
|
||||||
return this.maybeUpdateCaption(widget);
|
return this.maybeUpdateCaption(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
import {
|
||||||
|
inject,
|
||||||
|
injectable,
|
||||||
|
postConstruct,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
import { Diagnostic } from 'vscode-languageserver-types';
|
import { Diagnostic } from 'vscode-languageserver-types';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { ILogger } from '@theia/core';
|
import { ILogger } from '@theia/core';
|
||||||
import { Marker } from '@theia/markers/lib/common/marker';
|
import { Marker } from '@theia/markers/lib/common/marker';
|
||||||
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
|
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||||
import { ConfigService } from '../../../common/protocol/config-service';
|
import { ConfigService } from '../../../common/protocol/config-service';
|
||||||
|
import debounce = require('lodash.debounce');
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ProblemManager extends TheiaProblemManager {
|
export class ProblemManager extends TheiaProblemManager {
|
||||||
@@ -37,4 +42,12 @@ export class ProblemManager extends TheiaProblemManager {
|
|||||||
}
|
}
|
||||||
return super.setMarkers(uri, owner, data);
|
return super.setMarkers(uri, owner, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly debouncedFireOnDidChangeMakers = debounce(
|
||||||
|
(uri: URI) => this.onDidChangeMarkersEmitter.fire(uri),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
protected override fireOnDidChangeMarkers(uri: URI): void {
|
||||||
|
this.debouncedFireOnDidChangeMakers(uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import { injectable } from '@theia/core/shared/inversify';
|
|
||||||
import { CancellationToken } from '@theia/core/lib/common/cancellation';
|
import { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||||
import {
|
import type {
|
||||||
|
Message,
|
||||||
ProgressMessage,
|
ProgressMessage,
|
||||||
ProgressUpdate,
|
ProgressUpdate,
|
||||||
} from '@theia/core/lib/common/message-service-protocol';
|
} from '@theia/core/lib/common/message-service-protocol';
|
||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@@ -34,7 +35,9 @@ export class NotificationManager extends TheiaNotificationManager {
|
|||||||
this.fireUpdatedEvent();
|
this.fireUpdatedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override toPlainProgress(update: ProgressUpdate): number | undefined {
|
protected override toPlainProgress(
|
||||||
|
update: ProgressUpdate
|
||||||
|
): number | undefined {
|
||||||
if (!update.work) {
|
if (!update.work) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -43,4 +46,11 @@ export class NotificationManager extends TheiaNotificationManager {
|
|||||||
}
|
}
|
||||||
return Math.min((update.work.done / update.work.total) * 100, 100);
|
return Math.min((update.work.done / update.work.total) * 100, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For `public` visibility.
|
||||||
|
*/
|
||||||
|
override getMessageId(message: Message): string {
|
||||||
|
return super.getMessageId(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,29 @@
|
|||||||
import * as React from '@theia/core/shared/react';
|
import * as React from '@theia/core/shared/react';
|
||||||
import * as ReactDOM from '@theia/core/shared/react-dom';
|
import * as ReactDOM from '@theia/core/shared/react-dom';
|
||||||
import { injectable } from '@theia/core/shared/inversify';
|
import {
|
||||||
|
inject,
|
||||||
|
injectable,
|
||||||
|
postConstruct,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
import { NotificationCenterComponent } from './notification-center-component';
|
import { NotificationCenterComponent } from './notification-center-component';
|
||||||
import { NotificationToastsComponent } from './notification-toasts-component';
|
import { NotificationToastsComponent } from './notification-toasts-component';
|
||||||
import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/messages/lib/browser/notifications-renderer';
|
import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/messages/lib/browser/notifications-renderer';
|
||||||
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class NotificationsRenderer extends TheiaNotificationsRenderer {
|
export class NotificationsRenderer extends TheiaNotificationsRenderer {
|
||||||
|
@inject(FrontendApplicationStateService)
|
||||||
|
private readonly appStateService: FrontendApplicationStateService;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected override init(): void {
|
||||||
|
// Unlike Theia, IDE2 renders the notification area only when the app is ready.
|
||||||
|
this.appStateService.reachedState('ready').then(() => {
|
||||||
|
this.createOverlayContainer();
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected override render(): void {
|
protected override render(): void {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<div>
|
<div>
|
||||||
|
@@ -0,0 +1,24 @@
|
|||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||||
|
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
|
||||||
|
import { OutputEditorFactory as TheiaOutputEditorFactory } from '@theia/output/lib/browser/output-editor-factory';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class OutputEditorFactory extends TheiaOutputEditorFactory {
|
||||||
|
protected override createOptions(
|
||||||
|
model: MonacoEditorModel,
|
||||||
|
defaultOptions: MonacoEditor.IOptions
|
||||||
|
): MonacoEditor.IOptions {
|
||||||
|
const options = super.createOptions(model, defaultOptions);
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
// Taken from https://github.com/microsoft/vscode/blob/35b971c92d210face8c446a1c6f1e470ad2bcb54/src/vs/workbench/contrib/output/browser/outputView.ts#L211-L214
|
||||||
|
// To fix https://github.com/arduino/arduino-ide/issues/1210
|
||||||
|
unicodeHighlight: {
|
||||||
|
nonBasicASCII: false,
|
||||||
|
invisibleCharacters: false,
|
||||||
|
ambiguousCharacters: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +0,0 @@
|
|||||||
import { injectable } from '@theia/core/shared/inversify';
|
|
||||||
import { Message, Widget } from '@theia/core/lib/browser';
|
|
||||||
import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/output-widget';
|
|
||||||
|
|
||||||
// Patched after https://github.com/eclipse-theia/theia/issues/8361
|
|
||||||
// Remove this module after ATL-222 and the Theia update.
|
|
||||||
@injectable()
|
|
||||||
export class OutputWidget extends TheiaOutputWidget {
|
|
||||||
protected override onAfterShow(msg: Message): void {
|
|
||||||
super.onAfterShow(msg);
|
|
||||||
this.onResize(Widget.ResizeMessage.UnknownSize);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,19 @@
|
|||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/preferences/lib/browser/views/preference-editor-widget';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class PreferencesEditorWidget extends TheiaPreferencesEditorWidget {
|
||||||
|
protected override resetScroll(
|
||||||
|
nodeIDToScrollTo?: string,
|
||||||
|
filterWasCleared = false
|
||||||
|
): void {
|
||||||
|
if (this.scrollBar) {
|
||||||
|
// Absent on widget creation
|
||||||
|
this.doResetScroll(nodeIDToScrollTo, filterWasCleared);
|
||||||
|
} else {
|
||||||
|
// NOOP
|
||||||
|
// Unlike Theia, IDE2 does not start multiple tasks to check if the scrollbar is ready to reset it.
|
||||||
|
// If the "scroll reset" request arrived before the existence of the scrollbar, what to reset?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +0,0 @@
|
|||||||
import * as monaco from '@theia/monaco-editor-core';
|
|
||||||
|
|
||||||
export function fullRange(model: monaco.editor.ITextModel): monaco.Range {
|
|
||||||
const lastLine = model.getLineCount();
|
|
||||||
const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
|
|
||||||
const end = new monaco.Position(lastLine, lastLineMaxColumn);
|
|
||||||
return monaco.Range.fromPositions(new monaco.Position(1, 1), end);
|
|
||||||
}
|
|
@@ -5,3 +5,11 @@
|
|||||||
export function setURL(url: URL, data: any = {}): void {
|
export function setURL(url: URL, data: any = {}): void {
|
||||||
history.pushState(data, '', url);
|
history.pushState(data, '', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If available from the `window` object, then it means, the IDE2 has successfully patched the `MonacoThemingService#init` static method,
|
||||||
|
* and can wait the custom theme registration.
|
||||||
|
*/
|
||||||
|
export const MonacoThemeServiceIsReady = Symbol(
|
||||||
|
'@arduino-ide#monaco-theme-service-is-ready'
|
||||||
|
);
|
||||||
|
@@ -14,11 +14,38 @@ export class ComponentListItem<
|
|||||||
)[0];
|
)[0];
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedVersion: version,
|
selectedVersion: version,
|
||||||
|
focus: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async install(item: T): Promise<void> {
|
override componentDidUpdate(
|
||||||
|
prevProps: ComponentListItem.Props<T>,
|
||||||
|
prevState: ComponentListItem.State
|
||||||
|
): void {
|
||||||
|
if (this.state.focus !== prevState.focus) {
|
||||||
|
this.props.onFocusDidChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override render(): React.ReactNode {
|
||||||
|
const { item, itemRenderer } = this.props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onMouseEnter={() => this.setState({ focus: true })}
|
||||||
|
onMouseLeave={() => this.setState({ focus: false })}
|
||||||
|
>
|
||||||
|
{itemRenderer.renderItem(
|
||||||
|
Object.assign(this.state, { item }),
|
||||||
|
this.install.bind(this),
|
||||||
|
this.uninstall.bind(this),
|
||||||
|
this.onVersionChange.bind(this)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async install(item: T): Promise<void> {
|
||||||
const toInstall = this.state.selectedVersion;
|
const toInstall = this.state.selectedVersion;
|
||||||
const version = this.props.item.availableVersions.filter(
|
const version = this.props.item.availableVersions.filter(
|
||||||
(version) => version !== this.state.selectedVersion
|
(version) => version !== this.state.selectedVersion
|
||||||
@@ -35,23 +62,13 @@ export class ComponentListItem<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async uninstall(item: T): Promise<void> {
|
private async uninstall(item: T): Promise<void> {
|
||||||
await this.props.uninstall(item);
|
await this.props.uninstall(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onVersionChange(version: Installable.Version) {
|
private onVersionChange(version: Installable.Version): void {
|
||||||
this.setState({ selectedVersion: version });
|
this.setState({ selectedVersion: version });
|
||||||
}
|
}
|
||||||
|
|
||||||
override render(): React.ReactNode {
|
|
||||||
const { item, itemRenderer } = this.props;
|
|
||||||
return itemRenderer.renderItem(
|
|
||||||
Object.assign(this.state, { item }),
|
|
||||||
this.install.bind(this),
|
|
||||||
this.uninstall.bind(this),
|
|
||||||
this.onVersionChange.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace ComponentListItem {
|
export namespace ComponentListItem {
|
||||||
@@ -60,9 +77,11 @@ export namespace ComponentListItem {
|
|||||||
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
||||||
readonly uninstall: (item: T) => Promise<void>;
|
readonly uninstall: (item: T) => Promise<void>;
|
||||||
readonly itemRenderer: ListItemRenderer<T>;
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
|
readonly onFocusDidChange: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
selectedVersion?: Installable.Version;
|
selectedVersion?: Installable.Version;
|
||||||
|
focus: boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,43 +1,147 @@
|
|||||||
|
import 'react-virtualized/styles.css';
|
||||||
import * as React from '@theia/core/shared/react';
|
import * as React from '@theia/core/shared/react';
|
||||||
import { Installable } from '../../../common/protocol/installable';
|
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
|
||||||
|
import {
|
||||||
|
CellMeasurer,
|
||||||
|
CellMeasurerCache,
|
||||||
|
} from 'react-virtualized/dist/commonjs/CellMeasurer';
|
||||||
|
import type {
|
||||||
|
ListRowProps,
|
||||||
|
ListRowRenderer,
|
||||||
|
} from 'react-virtualized/dist/commonjs/List';
|
||||||
|
import List from 'react-virtualized/dist/commonjs/List';
|
||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
|
import { Installable } from '../../../common/protocol/installable';
|
||||||
import { ComponentListItem } from './component-list-item';
|
import { ComponentListItem } from './component-list-item';
|
||||||
import { ListItemRenderer } from './list-item-renderer';
|
import { ListItemRenderer } from './list-item-renderer';
|
||||||
|
|
||||||
|
function sameAs<T>(
|
||||||
|
left: T[],
|
||||||
|
right: T[],
|
||||||
|
...compareProps: (keyof T)[]
|
||||||
|
): boolean {
|
||||||
|
if (left === right) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const leftLength = left.length;
|
||||||
|
if (leftLength !== right.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < leftLength; i++) {
|
||||||
|
for (const prop of compareProps) {
|
||||||
|
const leftValue = left[i][prop];
|
||||||
|
const rightValue = right[i][prop];
|
||||||
|
if (leftValue !== rightValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export class ComponentList<T extends ArduinoComponent> extends React.Component<
|
export class ComponentList<T extends ArduinoComponent> extends React.Component<
|
||||||
ComponentList.Props<T>
|
ComponentList.Props<T>
|
||||||
> {
|
> {
|
||||||
protected container?: HTMLElement;
|
private readonly cache: CellMeasurerCache;
|
||||||
|
private resizeAllFlag: boolean;
|
||||||
|
private list: List | undefined;
|
||||||
|
private mostRecentWidth: number | undefined;
|
||||||
|
|
||||||
|
constructor(props: ComponentList.Props<T>) {
|
||||||
|
super(props);
|
||||||
|
this.cache = new CellMeasurerCache({
|
||||||
|
defaultHeight: 140,
|
||||||
|
fixedWidth: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
override render(): React.ReactNode {
|
override render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className={'items-container'} ref={this.setRef}>
|
<AutoSizer>
|
||||||
{this.props.items.map((item) => this.createItem(item))}
|
{({ width, height }) => {
|
||||||
</div>
|
if (this.mostRecentWidth && this.mostRecentWidth !== width) {
|
||||||
|
this.resizeAllFlag = true;
|
||||||
|
setTimeout(() => this.clearAll(), 0);
|
||||||
|
}
|
||||||
|
this.mostRecentWidth = width;
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
className={'items-container'}
|
||||||
|
rowRenderer={this.createItem}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
rowCount={this.props.items.length}
|
||||||
|
rowHeight={this.cache.rowHeight}
|
||||||
|
deferredMeasurementCache={this.cache}
|
||||||
|
ref={this.setListRef}
|
||||||
|
estimatedRowSize={140}
|
||||||
|
// If default value, then `react-virtualized` will optimize and list item will not receive a `:hover` event.
|
||||||
|
// Hence, install and version `<select>` won't be visible even if the mouse cursor is over the `<div>`.
|
||||||
|
// See https://github.com/bvaughn/react-virtualized/blob/005be24a608add0344284053dae7633be86053b2/source/Grid/Grid.js#L38-L42
|
||||||
|
scrollingResetTimeInterval={0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</AutoSizer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override componentDidMount(): void {
|
override componentDidUpdate(prevProps: ComponentList.Props<T>): void {
|
||||||
if (this.container && this.props.resolveContainer) {
|
if (
|
||||||
this.props.resolveContainer(this.container);
|
this.resizeAllFlag ||
|
||||||
|
!sameAs(this.props.items, prevProps.items, 'name', 'installedVersion')
|
||||||
|
) {
|
||||||
|
this.clearAll(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setRef = (element: HTMLElement | null) => {
|
private readonly setListRef = (ref: List | null): void => {
|
||||||
this.container = element || undefined;
|
this.list = ref || undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
protected createItem(item: T): React.ReactNode {
|
private clearAll(scrollToTop = false): void {
|
||||||
|
this.resizeAllFlag = false;
|
||||||
|
this.cache.clearAll();
|
||||||
|
if (this.list) {
|
||||||
|
this.list.recomputeRowHeights();
|
||||||
|
if (scrollToTop) {
|
||||||
|
this.list.scrollToPosition(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly createItem: ListRowRenderer = ({
|
||||||
|
index,
|
||||||
|
parent,
|
||||||
|
key,
|
||||||
|
style,
|
||||||
|
}: ListRowProps): React.ReactNode => {
|
||||||
|
const item = this.props.items[index];
|
||||||
return (
|
return (
|
||||||
|
<CellMeasurer
|
||||||
|
cache={this.cache}
|
||||||
|
columnIndex={0}
|
||||||
|
key={key}
|
||||||
|
rowIndex={index}
|
||||||
|
parent={parent}
|
||||||
|
>
|
||||||
|
{({ measure, registerChild }) => (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
<div ref={registerChild} style={style}>
|
||||||
<ComponentListItem<T>
|
<ComponentListItem<T>
|
||||||
key={this.props.itemLabel(item)}
|
key={this.props.itemLabel(item)}
|
||||||
item={item}
|
item={item}
|
||||||
itemRenderer={this.props.itemRenderer}
|
itemRenderer={this.props.itemRenderer}
|
||||||
install={this.props.install}
|
install={this.props.install}
|
||||||
uninstall={this.props.uninstall}
|
uninstall={this.props.uninstall}
|
||||||
|
onFocusDidChange={() => measure()}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CellMeasurer>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace ComponentList {
|
export namespace ComponentList {
|
||||||
@@ -48,6 +152,5 @@ export namespace ComponentList {
|
|||||||
readonly itemRenderer: ListItemRenderer<T>;
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
||||||
readonly uninstall: (item: T) => Promise<void>;
|
readonly uninstall: (item: T) => Promise<void>;
|
||||||
readonly resolveContainer: (element: HTMLElement) => void;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,121 @@
|
|||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import * as React from '@theia/core/shared/react';
|
||||||
|
import {
|
||||||
|
BoardSearch,
|
||||||
|
LibrarySearch,
|
||||||
|
Searchable,
|
||||||
|
} from '../../../common/protocol';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export abstract class FilterRenderer<S extends Searchable.Options> {
|
||||||
|
render(
|
||||||
|
options: S,
|
||||||
|
handlePropChange: (prop: keyof S, value: S[keyof S]) => void
|
||||||
|
): React.ReactNode {
|
||||||
|
const props = this.props();
|
||||||
|
return (
|
||||||
|
<div className="filter-bar">
|
||||||
|
{Object.entries(options)
|
||||||
|
.filter(([prop]) => props.includes(prop as keyof S))
|
||||||
|
.map(([prop, value]) => (
|
||||||
|
<div key={prop} className="filter">
|
||||||
|
<div className="filter-label">
|
||||||
|
{`${this.propertyLabel(prop as keyof S)}:`}
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
className="theia-select"
|
||||||
|
value={value}
|
||||||
|
onChange={(event) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
handlePropChange(prop as keyof S, event.target.value as any)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{this.options(prop as keyof S).map((key) => (
|
||||||
|
<option key={key} value={key}>
|
||||||
|
{this.valueLabel(prop as keyof S, key)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
protected abstract props(): (keyof S)[];
|
||||||
|
protected abstract options(prop: keyof S): string[];
|
||||||
|
protected abstract valueLabel(prop: keyof S, key: string): string;
|
||||||
|
protected abstract propertyLabel(prop: keyof S): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BoardsFilterRenderer extends FilterRenderer<BoardSearch> {
|
||||||
|
protected props(): (keyof BoardSearch)[] {
|
||||||
|
return ['type'];
|
||||||
|
}
|
||||||
|
protected options(prop: keyof BoardSearch): string[] {
|
||||||
|
switch (prop) {
|
||||||
|
case 'type':
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return BoardSearch.TypeLiterals as any;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected prop: ${prop}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected valueLabel(prop: keyof BoardSearch, key: string): string {
|
||||||
|
switch (prop) {
|
||||||
|
case 'type':
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return (BoardSearch.TypeLabels as any)[key];
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected key: ${prop}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected propertyLabel(prop: keyof BoardSearch): string {
|
||||||
|
switch (prop) {
|
||||||
|
case 'type':
|
||||||
|
return BoardSearch.PropertyLabels[prop];
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected key: ${prop}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class LibraryFilterRenderer extends FilterRenderer<LibrarySearch> {
|
||||||
|
protected props(): (keyof LibrarySearch)[] {
|
||||||
|
return ['type', 'topic'];
|
||||||
|
}
|
||||||
|
protected options(prop: keyof LibrarySearch): string[] {
|
||||||
|
switch (prop) {
|
||||||
|
case 'type':
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return LibrarySearch.TypeLiterals as any;
|
||||||
|
case 'topic':
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return LibrarySearch.TopicLiterals as any;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected prop: ${prop}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected propertyLabel(prop: keyof LibrarySearch): string {
|
||||||
|
switch (prop) {
|
||||||
|
case 'type':
|
||||||
|
case 'topic':
|
||||||
|
return LibrarySearch.PropertyLabels[prop];
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected key: ${prop}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected valueLabel(prop: keyof LibrarySearch, key: string): string {
|
||||||
|
switch (prop) {
|
||||||
|
case 'type':
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return (LibrarySearch.TypeLabels as any)[key] as any;
|
||||||
|
case 'topic':
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return (LibrarySearch.TopicLabels as any)[key] as any;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected prop: ${prop}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,7 @@ import { CommandService } from '@theia/core/lib/common/command';
|
|||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
||||||
import { Searchable } from '../../../common/protocol/searchable';
|
import { Searchable } from '../../../common/protocol/searchable';
|
||||||
|
import { ExecuteWithProgress } from '../../../common/protocol/progressible';
|
||||||
import { Installable } from '../../../common/protocol/installable';
|
import { Installable } from '../../../common/protocol/installable';
|
||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
import { SearchBar } from './search-bar';
|
import { SearchBar } from './search-bar';
|
||||||
@@ -13,25 +14,30 @@ import { ComponentList } from './component-list';
|
|||||||
import { ListItemRenderer } from './list-item-renderer';
|
import { ListItemRenderer } from './list-item-renderer';
|
||||||
import { ResponseServiceClient } from '../../../common/protocol';
|
import { ResponseServiceClient } from '../../../common/protocol';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
import { FilterRenderer } from './filter-renderer';
|
||||||
|
|
||||||
export class FilterableListContainer<
|
export class FilterableListContainer<
|
||||||
T extends ArduinoComponent
|
T extends ArduinoComponent,
|
||||||
|
S extends Searchable.Options
|
||||||
> extends React.Component<
|
> extends React.Component<
|
||||||
FilterableListContainer.Props<T>,
|
FilterableListContainer.Props<T, S>,
|
||||||
FilterableListContainer.State<T>
|
FilterableListContainer.State<T, S>
|
||||||
> {
|
> {
|
||||||
constructor(props: Readonly<FilterableListContainer.Props<T>>) {
|
constructor(props: Readonly<FilterableListContainer.Props<T, S>>) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
filterText: '',
|
searchOptions: props.defaultSearchOptions,
|
||||||
items: [],
|
items: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
override componentDidMount(): void {
|
override componentDidMount(): void {
|
||||||
this.search = debounce(this.search, 500);
|
this.search = debounce(this.search, 500);
|
||||||
this.handleFilterTextChange('');
|
this.search(this.state.searchOptions);
|
||||||
this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this));
|
this.props.searchOptionsDidChange((newSearchOptions) => {
|
||||||
|
const { searchOptions } = this.state;
|
||||||
|
this.setSearchOptionsAndUpdate({ ...searchOptions, ...newSearchOptions });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override componentDidUpdate(): void {
|
override componentDidUpdate(): void {
|
||||||
@@ -43,30 +49,40 @@ export class FilterableListContainer<
|
|||||||
override render(): React.ReactNode {
|
override render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className={'filterable-list-container'}>
|
<div className={'filterable-list-container'}>
|
||||||
{this.renderSearchFilter()}
|
|
||||||
{this.renderSearchBar()}
|
{this.renderSearchBar()}
|
||||||
|
{this.renderSearchFilter()}
|
||||||
|
<div className="filterable-list-container">
|
||||||
{this.renderComponentList()}
|
{this.renderComponentList()}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderSearchFilter(): React.ReactNode {
|
protected renderSearchFilter(): React.ReactNode {
|
||||||
return undefined;
|
return (
|
||||||
|
<>
|
||||||
|
{this.props.filterRenderer.render(
|
||||||
|
this.state.searchOptions,
|
||||||
|
this.handlePropChange.bind(this)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderSearchBar(): React.ReactNode {
|
protected renderSearchBar(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<SearchBar
|
<SearchBar
|
||||||
resolveFocus={this.props.resolveFocus}
|
resolveFocus={this.props.resolveFocus}
|
||||||
filterText={this.state.filterText}
|
filterText={this.state.searchOptions.query ?? ''}
|
||||||
onFilterTextChanged={this.handleFilterTextChange}
|
onFilterTextChanged={(query) =>
|
||||||
|
this.handlePropChange('query', query as S['query'])
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderComponentList(): React.ReactNode {
|
protected renderComponentList(): React.ReactNode {
|
||||||
const { itemLabel, itemDeprecated, resolveContainer, itemRenderer } =
|
const { itemLabel, itemDeprecated, itemRenderer } = this.props;
|
||||||
this.props;
|
|
||||||
return (
|
return (
|
||||||
<ComponentList<T>
|
<ComponentList<T>
|
||||||
items={this.state.items}
|
items={this.state.items}
|
||||||
@@ -75,22 +91,26 @@ export class FilterableListContainer<
|
|||||||
itemRenderer={itemRenderer}
|
itemRenderer={itemRenderer}
|
||||||
install={this.install.bind(this)}
|
install={this.install.bind(this)}
|
||||||
uninstall={this.uninstall.bind(this)}
|
uninstall={this.uninstall.bind(this)}
|
||||||
resolveContainer={resolveContainer}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleFilterTextChange = (
|
protected handlePropChange = (prop: keyof S, value: S[keyof S]): void => {
|
||||||
filterText: string = this.state.filterText
|
const searchOptions = {
|
||||||
) => {
|
...this.state.searchOptions,
|
||||||
this.setState({ filterText });
|
[prop]: value,
|
||||||
this.search(filterText);
|
};
|
||||||
|
this.setSearchOptionsAndUpdate(searchOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
protected search(query: string): void {
|
private setSearchOptionsAndUpdate(searchOptions: S) {
|
||||||
|
this.setState({ searchOptions }, () => this.search(searchOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected search(searchOptions: S): void {
|
||||||
const { searchable } = this.props;
|
const { searchable } = this.props;
|
||||||
searchable
|
searchable
|
||||||
.search({ query: query.trim() })
|
.search(searchOptions)
|
||||||
.then((items) => this.setState({ items: this.sort(items) }));
|
.then((items) => this.setState({ items: this.sort(items) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,14 +131,14 @@ export class FilterableListContainer<
|
|||||||
version: Installable.Version
|
version: Installable.Version
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { install, searchable } = this.props;
|
const { install, searchable } = this.props;
|
||||||
await Installable.doWithProgress({
|
await ExecuteWithProgress.doWithProgress({
|
||||||
...this.props,
|
...this.props,
|
||||||
progressText:
|
progressText:
|
||||||
nls.localize('arduino/common/processing', 'Processing') +
|
nls.localize('arduino/common/processing', 'Processing') +
|
||||||
` ${item.name}:${version}`,
|
` ${item.name}:${version}`,
|
||||||
run: ({ progressId }) => install({ item, progressId, version }),
|
run: ({ progressId }) => install({ item, progressId, version }),
|
||||||
});
|
});
|
||||||
const items = await searchable.search({ query: this.state.filterText });
|
const items = await searchable.search(this.state.searchOptions);
|
||||||
this.setState({ items: this.sort(items) });
|
this.setState({ items: this.sort(items) });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +157,7 @@ export class FilterableListContainer<
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { uninstall, searchable } = this.props;
|
const { uninstall, searchable } = this.props;
|
||||||
await Installable.doWithProgress({
|
await ExecuteWithProgress.doWithProgress({
|
||||||
...this.props,
|
...this.props,
|
||||||
progressText:
|
progressText:
|
||||||
nls.localize('arduino/common/processing', 'Processing') +
|
nls.localize('arduino/common/processing', 'Processing') +
|
||||||
@@ -146,21 +166,25 @@ export class FilterableListContainer<
|
|||||||
}`,
|
}`,
|
||||||
run: ({ progressId }) => uninstall({ item, progressId }),
|
run: ({ progressId }) => uninstall({ item, progressId }),
|
||||||
});
|
});
|
||||||
const items = await searchable.search({ query: this.state.filterText });
|
const items = await searchable.search(this.state.searchOptions);
|
||||||
this.setState({ items: this.sort(items) });
|
this.setState({ items: this.sort(items) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace FilterableListContainer {
|
export namespace FilterableListContainer {
|
||||||
export interface Props<T extends ArduinoComponent> {
|
export interface Props<
|
||||||
readonly container: ListWidget<T>;
|
T extends ArduinoComponent,
|
||||||
readonly searchable: Searchable<T>;
|
S extends Searchable.Options
|
||||||
|
> {
|
||||||
|
readonly defaultSearchOptions: S;
|
||||||
|
readonly container: ListWidget<T, S>;
|
||||||
|
readonly searchable: Searchable<T, S>;
|
||||||
readonly itemLabel: (item: T) => string;
|
readonly itemLabel: (item: T) => string;
|
||||||
readonly itemDeprecated: (item: T) => boolean;
|
readonly itemDeprecated: (item: T) => boolean;
|
||||||
readonly itemRenderer: ListItemRenderer<T>;
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
readonly resolveContainer: (element: HTMLElement) => void;
|
readonly filterRenderer: FilterRenderer<S>;
|
||||||
readonly resolveFocus: (element: HTMLElement | undefined) => void;
|
readonly resolveFocus: (element: HTMLElement | undefined) => void;
|
||||||
readonly filterTextChangeEvent: Event<string | undefined>;
|
readonly searchOptionsDidChange: Event<Partial<S> | undefined>;
|
||||||
readonly messageService: MessageService;
|
readonly messageService: MessageService;
|
||||||
readonly responseService: ResponseServiceClient;
|
readonly responseService: ResponseServiceClient;
|
||||||
readonly install: ({
|
readonly install: ({
|
||||||
@@ -182,8 +206,8 @@ export namespace FilterableListContainer {
|
|||||||
readonly commandService: CommandService;
|
readonly commandService: CommandService;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State<T> {
|
export interface State<T, S extends Searchable.Options> {
|
||||||
filterText: string;
|
searchOptions: S;
|
||||||
items: T[];
|
items: T[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import { Installable } from '../../../common/protocol/installable';
|
|||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
import { ComponentListItem } from './component-list-item';
|
import { ComponentListItem } from './component-list-item';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
import { Unknown } from '../../../common/nls';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ListItemRenderer<T extends ArduinoComponent> {
|
export class ListItemRenderer<T extends ArduinoComponent> {
|
||||||
@@ -13,7 +14,7 @@ export class ListItemRenderer<T extends ArduinoComponent> {
|
|||||||
|
|
||||||
protected onMoreInfoClick = (
|
protected onMoreInfoClick = (
|
||||||
event: React.SyntheticEvent<HTMLAnchorElement, Event>
|
event: React.SyntheticEvent<HTMLAnchorElement, Event>
|
||||||
) => {
|
): void => {
|
||||||
const { target } = event.nativeEvent;
|
const { target } = event.nativeEvent;
|
||||||
if (target instanceof HTMLAnchorElement) {
|
if (target instanceof HTMLAnchorElement) {
|
||||||
this.windowService.openNewWindow(target.href, { external: true });
|
this.windowService.openNewWindow(target.href, { external: true });
|
||||||
@@ -27,7 +28,7 @@ export class ListItemRenderer<T extends ArduinoComponent> {
|
|||||||
uninstall: (item: T) => Promise<void>,
|
uninstall: (item: T) => Promise<void>,
|
||||||
onVersionChange: (version: Installable.Version) => void
|
onVersionChange: (version: Installable.Version) => void
|
||||||
): React.ReactNode {
|
): React.ReactNode {
|
||||||
const { item } = input;
|
const { item, focus } = input;
|
||||||
let nameAndAuthor: JSX.Element;
|
let nameAndAuthor: JSX.Element;
|
||||||
if (item.name && item.author) {
|
if (item.name && item.author) {
|
||||||
const name = <span className="name">{item.name}</span>;
|
const name = <span className="name">{item.name}</span>;
|
||||||
@@ -42,11 +43,7 @@ export class ListItemRenderer<T extends ArduinoComponent> {
|
|||||||
} else if ((item as any).id) {
|
} else if ((item as any).id) {
|
||||||
nameAndAuthor = <span className="name">{(item as any).id}</span>;
|
nameAndAuthor = <span className="name">{(item as any).id}</span>;
|
||||||
} else {
|
} else {
|
||||||
nameAndAuthor = (
|
nameAndAuthor = <span className="name">{Unknown}</span>;
|
||||||
<span className="name">
|
|
||||||
{nls.localize('arduino/common/unknown', 'Unknown')}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const onClickUninstall = () => uninstall(item);
|
const onClickUninstall = () => uninstall(item);
|
||||||
const installedVersion = !!item.installedVersion && (
|
const installedVersion = !!item.installedVersion && (
|
||||||
@@ -123,10 +120,12 @@ export class ListItemRenderer<T extends ArduinoComponent> {
|
|||||||
{description}
|
{description}
|
||||||
</div>
|
</div>
|
||||||
<div className="info">{moreInfo}</div>
|
<div className="info">{moreInfo}</div>
|
||||||
|
{focus && (
|
||||||
<div className="footer">
|
<div className="footer">
|
||||||
{versions}
|
{versions}
|
||||||
{installButton}
|
{installButton}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -3,13 +3,20 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
|
|||||||
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
import { ListWidget } from './list-widget';
|
import { ListWidget } from './list-widget';
|
||||||
|
import { Searchable } from '../../../common/protocol';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent>
|
export abstract class ListWidgetFrontendContribution<
|
||||||
extends AbstractViewContribution<ListWidget<T>>
|
T extends ArduinoComponent,
|
||||||
|
S extends Searchable.Options
|
||||||
|
>
|
||||||
|
extends AbstractViewContribution<ListWidget<T, S>>
|
||||||
implements FrontendApplicationContribution
|
implements FrontendApplicationContribution
|
||||||
{
|
{
|
||||||
async initializeLayout(): Promise<void> {}
|
async initializeLayout(): Promise<void> {
|
||||||
|
// TS requires at least one method from `FrontendApplicationContribution`.
|
||||||
|
// Expected to be empty.
|
||||||
|
}
|
||||||
|
|
||||||
override registerMenus(): void {
|
override registerMenus(): void {
|
||||||
// NOOP
|
// NOOP
|
||||||
|
@@ -6,9 +6,7 @@ import {
|
|||||||
} from '@theia/core/shared/inversify';
|
} from '@theia/core/shared/inversify';
|
||||||
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
||||||
import { Emitter } from '@theia/core/lib/common/event';
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
|
||||||
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
||||||
import { CommandService } from '@theia/core/lib/common/command';
|
import { CommandService } from '@theia/core/lib/common/command';
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
@@ -21,10 +19,12 @@ import {
|
|||||||
import { FilterableListContainer } from './filterable-list-container';
|
import { FilterableListContainer } from './filterable-list-container';
|
||||||
import { ListItemRenderer } from './list-item-renderer';
|
import { ListItemRenderer } from './list-item-renderer';
|
||||||
import { NotificationCenter } from '../../notification-center';
|
import { NotificationCenter } from '../../notification-center';
|
||||||
|
import { FilterRenderer } from './filter-renderer';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export abstract class ListWidget<
|
export abstract class ListWidget<
|
||||||
T extends ArduinoComponent
|
T extends ArduinoComponent,
|
||||||
|
S extends Searchable.Options
|
||||||
> extends ReactWidget {
|
> extends ReactWidget {
|
||||||
@inject(MessageService)
|
@inject(MessageService)
|
||||||
protected readonly messageService: MessageService;
|
protected readonly messageService: MessageService;
|
||||||
@@ -42,9 +42,8 @@ export abstract class ListWidget<
|
|||||||
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
||||||
*/
|
*/
|
||||||
protected focusNode: HTMLElement | undefined;
|
protected focusNode: HTMLElement | undefined;
|
||||||
protected readonly deferredContainer = new Deferred<HTMLElement>();
|
protected readonly searchOptionsChangeEmitter = new Emitter<
|
||||||
protected readonly filterTextChangeEmitter = new Emitter<
|
Partial<S> | undefined
|
||||||
string | undefined
|
|
||||||
>();
|
>();
|
||||||
/**
|
/**
|
||||||
* Instead of running an `update` from the `postConstruct` `init` method,
|
* Instead of running an `update` from the `postConstruct` `init` method,
|
||||||
@@ -52,7 +51,7 @@ export abstract class ListWidget<
|
|||||||
*/
|
*/
|
||||||
protected firstActivate = true;
|
protected firstActivate = true;
|
||||||
|
|
||||||
constructor(protected options: ListWidget.Options<T>) {
|
constructor(protected options: ListWidget.Options<T, S>) {
|
||||||
super();
|
super();
|
||||||
const { id, label, iconClass } = options;
|
const { id, label, iconClass } = options;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -62,10 +61,8 @@ export abstract class ListWidget<
|
|||||||
this.title.closable = true;
|
this.title.closable = true;
|
||||||
this.addClass('arduino-list-widget');
|
this.addClass('arduino-list-widget');
|
||||||
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
||||||
this.scrollOptions = {
|
this.scrollOptions = undefined;
|
||||||
suppressScrollX: true,
|
this.toDispose.push(this.searchOptionsChangeEmitter);
|
||||||
};
|
|
||||||
this.toDispose.push(this.filterTextChangeEmitter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
@@ -77,10 +74,6 @@ export abstract class ListWidget<
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override getScrollContainer(): MaybePromise<HTMLElement> {
|
|
||||||
return this.deferredContainer.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override onAfterShow(message: Message): void {
|
protected override onAfterShow(message: Message): void {
|
||||||
this.maybeUpdateOnFirstRender();
|
this.maybeUpdateOnFirstRender();
|
||||||
super.onAfterShow(message);
|
super.onAfterShow(message);
|
||||||
@@ -109,7 +102,7 @@ export abstract class ListWidget<
|
|||||||
this.updateScrollBar();
|
this.updateScrollBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
protected onFocusResolved = (element: HTMLElement | undefined): void => {
|
||||||
this.focusNode = element;
|
this.focusNode = element;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -137,9 +130,9 @@ export abstract class ListWidget<
|
|||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<FilterableListContainer<T>
|
<FilterableListContainer<T, S>
|
||||||
|
defaultSearchOptions={this.options.defaultSearchOptions}
|
||||||
container={this}
|
container={this}
|
||||||
resolveContainer={this.deferredContainer.resolve}
|
|
||||||
resolveFocus={this.onFocusResolved}
|
resolveFocus={this.onFocusResolved}
|
||||||
searchable={this.options.searchable}
|
searchable={this.options.searchable}
|
||||||
install={this.install.bind(this)}
|
install={this.install.bind(this)}
|
||||||
@@ -147,7 +140,8 @@ export abstract class ListWidget<
|
|||||||
itemLabel={this.options.itemLabel}
|
itemLabel={this.options.itemLabel}
|
||||||
itemDeprecated={this.options.itemDeprecated}
|
itemDeprecated={this.options.itemDeprecated}
|
||||||
itemRenderer={this.options.itemRenderer}
|
itemRenderer={this.options.itemRenderer}
|
||||||
filterTextChangeEvent={this.filterTextChangeEmitter.event}
|
filterRenderer={this.options.filterRenderer}
|
||||||
|
searchOptionsDidChange={this.searchOptionsChangeEmitter.event}
|
||||||
messageService={this.messageService}
|
messageService={this.messageService}
|
||||||
commandService={this.commandService}
|
commandService={this.commandService}
|
||||||
responseService={this.responseService}
|
responseService={this.responseService}
|
||||||
@@ -159,10 +153,8 @@ export abstract class ListWidget<
|
|||||||
* If `filterText` is defined, sets the filter text to the argument.
|
* If `filterText` is defined, sets the filter text to the argument.
|
||||||
* If it is `undefined`, updates the view state by re-running the search with the current `filterText` term.
|
* If it is `undefined`, updates the view state by re-running the search with the current `filterText` term.
|
||||||
*/
|
*/
|
||||||
refresh(filterText: string | undefined): void {
|
refresh(searchOptions: Partial<S> | undefined): void {
|
||||||
this.deferredContainer.promise.then(() =>
|
this.searchOptionsChangeEmitter.fire(searchOptions);
|
||||||
this.filterTextChangeEmitter.fire(filterText)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScrollBar(): void {
|
updateScrollBar(): void {
|
||||||
@@ -173,14 +165,19 @@ export abstract class ListWidget<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace ListWidget {
|
export namespace ListWidget {
|
||||||
export interface Options<T extends ArduinoComponent> {
|
export interface Options<
|
||||||
|
T extends ArduinoComponent,
|
||||||
|
S extends Searchable.Options
|
||||||
|
> {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
readonly iconClass: string;
|
readonly iconClass: string;
|
||||||
readonly installable: Installable<T>;
|
readonly installable: Installable<T>;
|
||||||
readonly searchable: Searchable<T>;
|
readonly searchable: Searchable<T, S>;
|
||||||
readonly itemLabel: (item: T) => string;
|
readonly itemLabel: (item: T) => string;
|
||||||
readonly itemDeprecated: (item: T) => boolean;
|
readonly itemDeprecated: (item: T) => boolean;
|
||||||
readonly itemRenderer: ListItemRenderer<T>;
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
|
readonly filterRenderer: FilterRenderer<S>;
|
||||||
|
readonly defaultSearchOptions: S;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user