Compare commits

..

80 Commits

Author SHA1 Message Date
Akos Kitta
6447191bf5 [win] Implemented naive reconnecting.
For some reason, port is reported to be busy on Windows.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-11 09:40:41 +01:00
Akos Kitta
e78ed85761 [win]: Launch config fixes for Windows.
It is a workaround for eclipse-theia/theia#3590

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 14:37:10 +01:00
Akos Kitta
9bc520ccf9 Removed unused chance.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:54:01 +01:00
Akos Kitta
cfdb00529c Fixed focus.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:06:16 +01:00
Akos Kitta
8ccea24452 Let the serial monitor connect to unknown boards.
Fixes arduino/arduino-pro-ide#127

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:06:16 +01:00
Akos Kitta
ad563d26ba Re-enabled the build on this branch.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:06:15 +01:00
Akos Kitta
f0a628534e Updated to 0.14.0-next.0159cd5b Theia.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:05:55 +01:00
Akos Kitta
1b95242ad1 forward messages only when connected.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:30 +01:00
Akos Kitta
729588770e fixed broken promise when reconnecting on baudRate changes.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:30 +01:00
Akos Kitta
6b2046e090 Do not disconnect from the widget, it is done automatically.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:30 +01:00
Akos Kitta
80673ad18f Styled the input.
Do not render the widget after a close request.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:30 +01:00
Akos Kitta
2f33038695 No disconnect/reconnect when DNDing the widget.
- Updated to next Theia,
 - Added elecron launch config,
 - Yet another syling for the input + selects,
 - Close monitor connection on widget close not detach.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:28 +01:00
Akos Kitta
6154d1e8d5 aligned input to the selects.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
557ec2ae42 Wait until the boards config has been reset
from the local storage, then start the monitor connection.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
8c49c04359 🤞 finalized the monitor UI
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
29ebf055e6 Do not re-render the entire widget.
Otherwise the selects do not work.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
71842abfa3 Got rid of the send button, use keybinding instead
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
ed660ccd64 fixed the input focus when the view is activated
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
6af22ec9b8 aligned select height with Theia styles
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
79f588d067 Finalized the styling for react-select
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
e3e4a96db3 do not show border on focus.
when the list is opened, we have the accent color anyway.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
4129544738 generalized the react-select component
so tha we can reuse it all over the application.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
a866bde4d1 fixed background style when holding down select
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
92b6208a76 aligned core/lib input style with react-select
made the UI slicker by removing any non- :hover, :select borders.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
12deceef19 got rid of the border when not focused/selected
it does not remove the border but uses the background of the container.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
f635751a8c fix the react-select styling
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
85bf50213d Removed more logic from the widget.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
5aeb2d388e Flip the auto install buttons.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
b6b4c75718 CI skip.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:16 +01:00
Akos Kitta
c4a8062df4 Updated to next Theia.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:02:14 +01:00
Akos Kitta
6e89e89738 Fixed layout initialization.
Fixes for eclipse-theia/theia#6688

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:01:54 +01:00
Akos Kitta
c7242ca34f slightly better reconnecting experience.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:01:54 +01:00
Akos Kitta
a4e5e65286 simplified monitor connection API.
we have one connenction per editor anyways.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:01:54 +01:00
Akos Kitta
80549db289 a few bugfixes. updated grpc dependencies.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:01:54 +01:00
Akos Kitta
eb7b3ad683 Reconnect on interrupted system call.
However, we have to figure out why does it happen at all.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:01:53 +01:00
Akos Kitta
9efcbcf2ae [rewrite-me]: initial serial monitor changes
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 12:01:53 +01:00
Akos Kitta
d22c0b9e55 Merge pull request #90 from bcmi-labs/jx/update-theia
Update Theia and adjust Electron package name
2019-12-10 11:59:45 +01:00
Jan Keromnes
5d2f09354d Adjust Electron package 'name' and 'appId'. 2019-12-10 11:03:01 +01:00
Jan Keromnes
fcd6c792e3 Updated to next (0.14.0-next.0159cd5b) Theia.
Co-authored-by: Jan Keromnes <jan.keromnes@typefox.io>
Co-authored-by: Akos Kitta <kittaakos@typefox.io>

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-12-10 11:02:49 +01:00
Akos Kitta
94233a1a19 Merge pull request #80 from bcmi-labs/0.3.0-rc
All in on PR for 0.0.3
2019-12-09 09:58:59 +01:00
Jan Keromnes
7fb32766ca [doc] Fix a few typos 2019-12-07 09:59:45 +01:00
Akos Kitta
85cf8757c4 Merge pull request #89 from bcmi-labs/jx/document-boards-service
[doc] Document Arduino Pro IDE extension and a few services
2019-12-07 09:50:19 +01:00
Jan Keromnes
41c56c1126 [doc] Document Arduino Pro IDE extension and a few services 2019-12-06 16:03:58 +00:00
Jan Keromnes
4c503c0c5e Merge pull request #86 from bcmi-labs/jx/support-electron-dev
Support running the Electron app in Gitpod for faster code reviews
2019-12-06 11:10:03 +01:00
Jan Keromnes
3f180b6059 [minor] Make the README.md GIFs a bit smaller 2019-12-06 11:07:03 +01:00
Jan Keromnes
6a8a76f720 [minor] Fix project name in README.md 2019-12-06 11:04:01 +01:00
Jan Keromnes
9a27252d91 Support running the Electron app in Gitpod for faster code reviews 2019-12-05 10:26:53 +00:00
Akos Kitta
4e683b237d Made the editors closeable in pro mode.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-26 10:33:59 +01:00
Akos Kitta
a2a9cbb02e Fixed a styling issue in the boards select dialog.
Closes: arduino/arduino-pro-ide#137
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-26 09:18:46 +01:00
Akos Kitta
dd10436051 Fixed the Platform ordering.
We have to pick the installed version first.
Otherwise we lose the FQBN of the boards.
FBQN is used to check if a board has the corresponding core installed.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-26 09:10:01 +01:00
Akos Kitta
e79d42d6bd Updated to next (0.13.0-next.145f9137) Theia.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-25 09:16:22 +01:00
Akos Kitta
a9c9dcde7b Fixed a selection bug after installing a core/lib
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
62b18ccbed Fixed lib uninstall. Do not log it more than once.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
0a8b6bc41e Restart the LS on core install/remove.
Fixes bcmi-labs/arduino-language-server#9

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
b1388be5f9 Implemented uninstall.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
b4848f62fa Corrected the docs.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
f359843635 Fixed the version ordering for libs.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
6448b447b3 Resolved a React warning.
Removed the widget update due to an incorrect manual merge.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
c7bb3abf19 Updated doc, code style changes.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
c3e2aa4feb Generalized the list item renderers.
To support update/downgrade.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
63cd2701b4 Simplified code when deceding which CLI to use.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
35ac73181b Enabled New Folder in classic mode.
-  Made sure the `Explorer` is visible before raising the input dialog.
 - Removed unused module.

Fixes arduino/arduino-pro-ide#84

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
840cde872c Made the editor mode dynamic.Signed-off-by: Akos Kitta <kittaakos@typefox.io> 2019-11-22 17:09:45 +01:00
Akos Kitta
c2008460b0 Fixed storeLayout when toggling the editor mode.
Workaround for eclipse-theia/theia#6579.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
435fdcdf7f Adjusted the version numbers.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
7e6343e60e Update the ListWidget on list container render.
To recalculate the desired size of the scroll-bar thumb.

arduino/arduino-pro-ide#101

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
fdda4a72d0 Initial support for updating/downgrading cores.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
7077303a36 Hid the Explorer. Disabled new folder in workspace
when not in `pro-mode`.

Closes arduino/arduino-pro-ide#84.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
acd9bf1354 Fixed the editor restoration when toggling mode.
- Do not reset the layout. It messes up the editor's restoration.
 - Close all pro-mode only views when leaving pro-mode.
 - Initialize all views `onStart`. It is a NOOP in normal mode.

Closes arduino/arduino-pro-ide#101

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
d92fc25769 Fixed endless loop in the sketch service.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
f9a98d708e Removed an unused field.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
df33c5689f Gracefully handle disconnected frontends.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
2dc73eb3b5 Updated to next (0.13.0-next.83c59134) Theia.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
f6444b2570 Update the ListWidget on list container render.
To recalculate the desired size of the scroll-bar thumb.

arduino/arduino-pro-ide#101

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Akos Kitta
186180800f Increased the z-index of the scroll-bar thumb
to make it visible even with the customized list item `background-color`

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
2019-11-22 17:09:45 +01:00
Jan Keromnes
20bc3c6f13 Merge pull request #81 from bcmi-labs/choose-cli
Only pick `arduino-cli` from the PATH if it's more recent
2019-11-21 14:53:23 +01:00
Jan Keromnes
125ee70fa3 Sort package.json dependencies alphabetically 2019-11-21 10:37:14 +00:00
Jan Keromnes
3cfb1450c0 Only pick arduino-cli from the PATH if it's more recent 2019-11-21 10:37:14 +00:00
Akos Kitta
9643dd397f [doc]: Updated the CI section.
Documented when do we build the electron-based applications.
2019-11-21 11:16:52 +01:00
Akos Kitta
7c1ebf273c Merge pull request #75 from bcmi-labs/25-10-release
Various bug fixes, plus one or two enhancements.
2019-10-28 12:41:29 -04:00
89 changed files with 4682 additions and 2809 deletions

View File

@@ -2,8 +2,12 @@ image:
file: Dockerfile
ports:
- port: 3000
onOpen: open-browser
- port: 3000
onOpen: open-preview
- port: 5900
onOpen: ignore
- port: 6080
onOpen: ignore
tasks:
- init: >

140
.vscode/launch.json vendored
View File

@@ -1,39 +1,105 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Electron Packager",
"program": "${workspaceRoot}/electron/packager/index.js",
"cwd": "${workspaceFolder}/electron/packager"
},
{
"type": "node",
"request": "launch",
"name": "Launch Backend",
"program": "${workspaceRoot}/browser-app/src-gen/backend/main.js",
"args": [
"--hostname=0.0.0.0",
"--port=3000",
"--no-cluster",
"--no-app-auto-install"
],
"env": {
"NODE_ENV": "development"
},
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
"${workspaceRoot}/browser-app/lib/**/*.js",
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
],
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart",
"outputCapture": "std"
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "App (Electron)",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd",
"env": {
"NODE_ENV": "development",
"NODE_PRESERVE_SYMLINKS": "1"
}
]
}
},
"program": "${workspaceRoot}/electron-app/src-gen/frontend/electron-main.js",
"protocol": "inspector",
"args": [
"--log-level=debug",
"--hostname=localhost",
"--no-cluster",
"--remote-debugging-port=9222",
"--no-app-auto-install"
],
"env": {
"NODE_ENV": "development"
},
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/electron-app/src-gen/backend/*.js",
"${workspaceRoot}/electron-app/src-gen/frontend/*.js",
"${workspaceRoot}/electron-app/lib/**/*.js",
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
],
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart",
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "App (Browser)",
"program": "${workspaceRoot}/browser-app/src-gen/backend/main.js",
"args": [
"--hostname=0.0.0.0",
"--port=3000",
"--no-cluster",
"--no-app-auto-install"
],
"windows": {
"env": {
"NODE_ENV": "development",
"NODE_PRESERVE_SYMLINKS": "1"
}
},
"env": {
"NODE_ENV": "development"
},
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
"${workspaceRoot}/browser-app/lib/**/*.js",
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
],
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart",
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "App (Browser - Debug CLI daemon)",
"program": "${workspaceRoot}/browser-app/src-gen/backend/main.js",
"args": [
"--hostname=0.0.0.0",
"--port=3000",
"--no-cluster",
"--no-app-auto-install",
"--debug-cli=true"
],
"env": {
"NODE_ENV": "development"
},
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
"${workspaceRoot}/browser-app/lib/**/*.js",
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
],
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart",
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "Packager",
"program": "${workspaceRoot}/electron/packager/index.js",
"cwd": "${workspaceFolder}/electron/packager"
}
]
}

22
.vscode/settings.json vendored
View File

@@ -1,3 +1,21 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
"tslint.enable": true,
"tslint.configFile": "./tslint.json",
"editor.formatOnSave": true,
"files.exclude": {
"**/lib": false
},
"editor.insertSpaces": true,
"editor.detectIndentation": false,
"[typescript]": {
"editor.tabSize": 4
},
"[json]": {
"editor.tabSize": 2
},
"[jsonc]": {
"editor.tabSize": 2
},
"files.insertFinalNewline": true,
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@@ -1,4 +1,4 @@
FROM gitpod/workspace-full
FROM gitpod/workspace-full-vnc
USER root
RUN apt-get update -q --fix-missing && \
@@ -7,7 +7,8 @@ RUN apt-get update -q --fix-missing && \
build-essential \
libssl-dev \
golang-go \
libxkbfile-dev
libxkbfile-dev \
libnss3-dev
RUN set -ex && \
tmpdir=$(mktemp -d) && \

View File

@@ -1,4 +1,4 @@
# Arduino IDE PoC
# Arduino Pro IDE
[![Build Status](https://dev.azure.com/typefox/Arduino/_apis/build/status/bcmi-labs.arduino-editor?branchName=master)](https://dev.azure.com/typefox/Arduino/_build/latest?definitionId=4&branchName=master)
@@ -29,6 +29,9 @@ Then you can start the browser example again:
yarn --cwd browser-app start
```
Click [here](./arduino-ide-extension/README.md) for more details on various IDE services, and the Arduino Pro IDE implementation in general.
## Arduino Pro IDE Electron Application
The project is built on [Azure DevOps](https://dev.azure.com/typefox/Arduino).
@@ -41,7 +44,7 @@ You can download the latest release applications fom [here](https://github.com/b
If you want to get a nightly build, go to the [Azure DevOps page](https://dev.azure.com/typefox/Arduino/_build?definitionId=4),
and follow the steps from below.
![](static/download_01.gif)
![](static/download_02.gif)
<img width="500" src="static/download_01.gif">
<img width="500" src="static/download_02.gif">
Click [here](./electron/README.md) for more details on the CI/CD, the GitHub release, and the build process in general.

View File

@@ -0,0 +1,52 @@
## Arduino IDE Extension
Arduino Pro IDE is based on Theia, and most of its IDE features, UIs and customizations are implemented in this Theia extension.
### IDE Services
IDE services typically have a backend part in [src/node/](./src/node/) and a front-end part in [src/browser/](./src/browser/).
#### Boards Service
The Boards Service continuously checks the computer's ports, in order to detect when you connect or disconnect an Arduino board.
The Boards Manager lists all the known board types, and allows downloading new cores to get additional board types.
- [src/common/protocol/boards-service.ts](./src/common/protocol/boards-service.ts) implements the common classes and interfaces
- [src/node/boards-service-impl.ts](./src/node/boards-service-impl.ts) implements the service backend:
- discovering ports & boards
- searching for compatible board types
- installing new board types
- [src/browser/boards/boards-list-widget.ts](./src/browser/boards/boards-service-client-impl.ts) implements the Boards Manager front-end:
- browsing/searching available board types
- installing new board types
#### Core Service
The Core Service is responsible for building your sketches and uploading them to a board.
- [src/common/protocol/core-service.ts](./src/common/protocol/core-service.ts) implements the common classes and interfaces
- [src/node/core-service-impl.ts](./src/node/core-service-impl.ts) implements the service backend:
- compiling a sketch for a selected board type
- uploading a sketch to a connected board
#### Monitor Service
The Monitor Service allows getting information back from sketches running on your Arduino boards.
- [src/common/protocol/monitor-service.ts](./src/common/protocol/monitor-service.ts) implements the common classes and interfaces
- [src/node/monitor-service-impl.ts](./src/node/monitor-service-impl.ts) implements the service backend:
- connecting to / disconnecting from a board
- receiving and sending data
- [src/browser/monitor/monitor-widget.tsx](./src/browser/monitor/monitor-widget.tsx) implements the serial monitor front-end:
- viewing the output from a connected board
- entering data to send to the board
#### Config Service
The Config Service knows about your system, like for example the default sketch locations.
- [src/common/protocol/config-service.ts](./src/common/protocol/config-service.ts) implements the common classes and interfaces
- [src/node/config-service-impl.ts](./src/node/config-service-impl.ts) implements the service backend:
- getting the `arduino-cli` version and configuration
- checking whether a file is in a data or sketch directory

View File

@@ -1,35 +1,39 @@
{
"name": "arduino-ide-extension",
"version": "0.0.2",
"version": "0.0.3",
"description": "An extension for Theia building the Arduino IDE",
"license": "MIT",
"engines": {
"node": ">=10.10.0"
},
"dependencies": {
"@grpc/grpc-js": "^0.4.0",
"@grpc/grpc-js": "^0.6.12",
"@theia/application-package": "next",
"@theia/core": "next",
"@theia/cpp": "next",
"@theia/editor": "next",
"@theia/filesystem": "next",
"@theia/git": "next",
"@theia/languages": "next",
"@theia/markers": "next",
"@theia/monaco": "next",
"@theia/outline-view": "next",
"@theia/workspace": "next",
"@theia/navigator": "next",
"@theia/terminal": "next",
"@theia/outline-view": "next",
"@theia/search-in-workspace": "next",
"@theia/cpp": "next",
"@types/ps-tree": "^1.1.0",
"@types/which": "^1.3.1",
"@types/react-select": "^3.0.0",
"@theia/terminal": "next",
"@theia/workspace": "next",
"@types/google-protobuf": "^3.7.1",
"@types/dateformat": "^3.0.1",
"@types/ps-tree": "^1.1.0",
"@types/react-select": "^3.0.0",
"@types/which": "^1.3.1",
"css-element-queries": "^1.2.0",
"react-select": "^3.0.4",
"dateformat": "^3.0.3",
"google-protobuf": "^3.11.0",
"p-queue": "^5.0.0",
"ps-tree": "^1.2.0",
"react-select": "^3.0.4",
"semver": "^6.3.0",
"string-natural-compare": "^2.0.3",
"tree-kill": "^1.2.1",
"upath": "^1.1.2",
@@ -50,14 +54,14 @@
"decompress-targz": "^4.1.1",
"decompress-unzip": "^4.0.1",
"download": "^7.1.0",
"grpc-tools": "^1.7.3",
"grpc_tools_node_protoc_ts": "^2.5.0",
"grpc-tools": "^1.8.0",
"grpc_tools_node_protoc_ts": "^2.5.8",
"moment": "^2.24.0",
"ncp": "^2.0.0",
"rimraf": "^2.6.1",
"shelljs": "^0.8.3",
"tslint": "^5.5.0",
"typescript": "2.9.1",
"typescript": "3.5.3",
"uuid": "^3.2.1",
"yargs": "^11.1.0"
},
@@ -77,4 +81,4 @@
"frontendElectron": "lib/electron-browser/electron-arduino-menu-module"
}
]
}
}

View File

@@ -3,7 +3,7 @@ import { injectable, inject, postConstruct } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
import { MessageService } from '@theia/core/lib/common/message-service';
import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common/command';
import { CommandContribution, CommandRegistry, Command, CommandHandler } from '@theia/core/lib/common/command';
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { BoardsService } from '../common/protocol/boards-service';
import { ArduinoCommands } from './arduino-commands';
@@ -22,9 +22,11 @@ import {
OpenerService,
Widget,
StatusBar,
ShellLayoutRestorer,
StatusBarAlignment,
QuickOpenService
QuickOpenService,
ApplicationShell,
FrontendApplicationContribution,
FrontendApplication
} from '@theia/core/lib/browser';
import { OpenFileDialogProps, FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
import { FileSystem, FileStat } from '@theia/filesystem/lib/common';
@@ -39,11 +41,18 @@ import { MaybePromise } from '@theia/core/lib/common/types';
import { BoardsConfigDialog } from './boards/boards-config-dialog';
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
import { BoardsConfig } from './boards/boards-config';
import { MonitorService } from '../common/protocol/monitor-service';
import { ConfigService } from '../common/protocol/config-service';
import { MonitorConnection } from './monitor/monitor-connection';
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
import { ArduinoWorkspaceService } from './arduino-workspace-service';
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
import { EditorMode } from './editor-mode';
export namespace ArduinoMenus {
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
@@ -57,16 +66,8 @@ export namespace ArduinoToolbarContextMenu {
export const EXAMPLE_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '3_examples'];
}
export namespace ArduinoAdvancedMode {
export const LS_ID = 'arduino-advanced-mode';
export const TOGGLED: boolean = (() => {
const advancedModeStr = window.localStorage.getItem(LS_ID);
return advancedModeStr === 'true';
})();
}
@injectable()
export class ArduinoFrontendContribution implements TabBarToolbarContribution, CommandContribution, MenuContribution {
export class ArduinoFrontendContribution implements FrontendApplicationContribution, TabBarToolbarContribution, CommandContribution, MenuContribution {
@inject(MessageService)
protected readonly messageService: MessageService;
@@ -77,9 +78,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(MonitorService)
protected readonly monitorService: MonitorService;
@inject(WorkspaceServiceExt)
protected readonly workspaceServiceExt: WorkspaceServiceExt;
@@ -126,13 +124,13 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
protected readonly menuRegistry: MenuModelRegistry;
@inject(CommandRegistry)
protected readonly commands: CommandRegistry;
protected readonly commandRegistry: CommandRegistry;
@inject(StatusBar)
protected readonly statusBar: StatusBar;
@inject(ShellLayoutRestorer)
protected readonly layoutRestorer: ShellLayoutRestorer;
@inject(ArduinoShellLayoutRestorer)
protected readonly layoutRestorer: ArduinoShellLayoutRestorer;
@inject(QuickOpenService)
protected readonly quickOpenService: QuickOpenService;
@@ -146,8 +144,29 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
@inject(MonitorConnection)
protected readonly monitorConnection: MonitorConnection;
protected boardsToolbarItem: BoardsToolBarItem | null;
protected wsSketchCount: number = 0;
@inject(ApplicationShell)
protected readonly shell: ApplicationShell;
@inject(FileNavigatorContribution)
protected readonly fileNavigatorContributions: FileNavigatorContribution;
@inject(OutlineViewContribution)
protected readonly outlineContribution: OutlineViewContribution;
@inject(ProblemContribution)
protected readonly problemContribution: ProblemContribution;
@inject(ScmContribution)
protected readonly scmContribution: ScmContribution;
@inject(SearchInWorkspaceFrontendContribution)
protected readonly siwContribution: SearchInWorkspaceFrontendContribution;
@inject(EditorMode)
protected readonly editorMode: EditorMode;
protected application: FrontendApplication;
protected wsSketchCount: number = 0; // TODO: this does not belong here, does it?
@postConstruct()
protected async init(): Promise<void> {
@@ -171,6 +190,22 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
]).then(([{ boards }, { ports }]) => this.boardsServiceClient.tryReconnect(boards, ports));
}
onStart(app: FrontendApplication): void {
this.application = app;
// Initialize all `pro-mode` widgets. This is a NOOP if in normal mode.
for (const viewContribution of [
this.fileNavigatorContributions,
this.outlineContribution,
this.problemContribution,
this.scmContribution,
this.siwContribution] as Array<FrontendApplicationContribution>) {
if (viewContribution.initializeLayout) {
viewContribution.initializeLayout(this.application);
}
}
}
registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem({
id: ArduinoCommands.VERIFY.id,
@@ -196,8 +231,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
id: BoardsToolBarItem.TOOLBAR_ID,
render: () => <BoardsToolBarItem
key='boardsToolbarItem'
ref={ref => this.boardsToolbarItem = ref}
commands={this.commands}
commands={this.commandRegistry}
boardsServiceClient={this.boardsServiceClient}
boardService={this.boardsService} />,
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left'
@@ -213,12 +247,48 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
id: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
command: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
tooltip: 'Toggle Advanced Mode',
text: (ArduinoAdvancedMode.TOGGLED ? '$(toggle-on)' : '$(toggle-off)'),
text: (this.editorMode.proMode ? '$(toggle-on)' : '$(toggle-off)'),
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right'
});
}
registerCommands(registry: CommandRegistry): void {
// TODO: use proper API https://github.com/eclipse-theia/theia/pull/6599
const allHandlers: { [id: string]: CommandHandler[] } = (registry as any)._handlers;
// Make sure to reveal the `Explorer` before executing `New File` and `New Folder`.
for (const command of [WorkspaceCommands.NEW_FILE, WorkspaceCommands.NEW_FOLDER]) {
const { id } = command;
const handlers = allHandlers[id].slice();
registry.unregisterCommand(id);
registry.registerCommand(command);
for (const handler of handlers) {
const wrapper: CommandHandler = {
execute: (...args: any[]) => {
this.fileNavigatorContributions.openView({ reveal: true }).then(() => handler.execute(args));
},
isVisible: (...args: any[]) => {
return handler.isVisible!(args);
},
isEnabled: (args: any[]) => {
return handler.isEnabled!(args);
},
isToggled: (args: any[]) => {
return handler.isToggled!(args);
}
};
if (!handler.isEnabled) {
delete wrapper.isEnabled;
}
if (!handler.isToggled) {
delete wrapper.isToggled;
}
if (!handler.isVisible) {
delete wrapper.isVisible;
}
registry.registerHandler(id, wrapper);
}
}
registry.registerCommand(ArduinoCommands.VERIFY, {
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
isEnabled: widget => true,
@@ -261,8 +331,10 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
return;
}
const connectionConfig = this.monitorConnection.connectionConfig;
await this.monitorConnection.disconnect();
const monitorConfig = this.monitorConnection.monitorConfig;
if (monitorConfig) {
await this.monitorConnection.disconnect();
}
try {
const { boardsConfig } = this.boardsServiceClient;
@@ -277,8 +349,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
} catch (e) {
await this.messageService.error(e.toString());
} finally {
if (connectionConfig) {
await this.monitorConnection.connect(connectionConfig);
if (monitorConfig) {
await this.monitorConnection.connect(monitorConfig);
}
}
}
@@ -296,7 +368,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
});
}
} else {
this.commands.executeCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR.id);
this.commandRegistry.executeCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR.id);
}
}
});
@@ -342,18 +414,14 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
}
})
registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE, {
execute: () => {
const oldModeState = ArduinoAdvancedMode.TOGGLED;
window.localStorage.setItem(ArduinoAdvancedMode.LS_ID, oldModeState ? 'false' : 'true');
registry.executeCommand('reset.layout');
},
execute: () => this.editorMode.toggle(),
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right',
isToggled: () => ArduinoAdvancedMode.TOGGLED
isToggled: () => this.editorMode.proMode
})
}
registerMenus(registry: MenuModelRegistry) {
if (!ArduinoAdvancedMode.TOGGLED) {
if (!this.editorMode.proMode) {
// If are not in pro-mode, we have to disable the context menu for the tabs.
// Such as `Close`, `Close All`, etc.
for (const command of [
@@ -362,7 +430,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
CommonCommands.CLOSE_RIGHT_TABS,
CommonCommands.CLOSE_ALL_TABS,
CommonCommands.COLLAPSE_PANEL,
CommonCommands.TOGGLE_MAXIMIZED
CommonCommands.TOGGLE_MAXIMIZED,
FileNavigatorCommands.REVEAL_IN_NAVIGATOR
]) {
registry.unregisterMenuAction(command);
}
@@ -370,8 +439,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
registry.unregisterMenuAction(FileSystemCommands.UPLOAD);
registry.unregisterMenuAction(FileDownloadCommands.DOWNLOAD);
registry.unregisterMenuAction(WorkspaceCommands.NEW_FOLDER);
registry.unregisterMenuAction(WorkspaceCommands.OPEN_FOLDER);
registry.unregisterMenuAction(WorkspaceCommands.OPEN_WORKSPACE);
registry.unregisterMenuAction(WorkspaceCommands.OPEN_RECENT_WORKSPACE);
@@ -425,8 +492,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
const command: Command = {
id: 'openSketch' + sketch.name
}
this.commands.registerCommand(command, {
execute: () => this.commands.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
this.commandRegistry.registerCommand(command, {
execute: () => this.commandRegistry.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
});
registry.registerMenuAction(ArduinoToolbarContextMenu.WS_SKETCHES_GROUP, {
@@ -466,7 +533,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
if (destinationFile && !destinationFile.isDirectory) {
const message = await this.validate(destinationFile);
if (!message) {
await this.workspaceService.open(destinationFileUri);
this.workspaceService.open(destinationFileUri);
return destinationFileUri;
} else {
this.messageService.warn(message);

View File

@@ -10,7 +10,7 @@ import { LanguageGrammarDefinitionContribution } from '@theia/monaco/lib/browser
import { LanguageClientContribution } from '@theia/languages/lib/browser';
import { ArduinoLanguageClientContribution } from './language/arduino-language-client-contribution';
import { LibraryListWidget } from './library/library-list-widget';
import { ArduinoFrontendContribution, ArduinoAdvancedMode } from './arduino-frontend-contribution';
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution';
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
import { BoardsService, BoardsServicePath, BoardsServiceClient } from '../common/protocol/boards-service';
@@ -30,30 +30,28 @@ import { ThemeService } from '@theia/core/lib/browser/theming';
import { ArduinoTheme } from './arduino-theme';
import { MenuContribution } from '@theia/core';
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
import { SilentOutlineViewContribution } from './customization/silent-outline-contribution';
import { ArduinoOutlineViewContribution } from './customization/arduino-outline-contribution';
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
import { SilentProblemContribution } from './customization/silent-problem-contribution';
import { SilentNavigatorContribution } from './customization/silent-navigator-contribution';
import { ArduinoProblemContribution } from './customization/arduino-problem-contribution';
import { ArduinoNavigatorContribution } from './customization/arduino-navigator-contribution';
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
import { ArduinoToolbarContribution } from './toolbar/arduino-toolbar-contribution';
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
import { ArduinoOutputToolContribution } from './customization/silent-output-tool-contribution';
import { ArduinoOutputToolContribution } from './customization/arduino-output-tool-contribution';
import { EditorContribution } from '@theia/editor/lib/browser/editor-contribution';
import { ArduinoEditorContribution } from './customization/arduino-editor-contribution';
import { MonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
import { ArduinoMonacoStatusBarContribution } from './customization/arduino-monaco-status-bar-contribution';
import { ApplicationShell } from '@theia/core/lib/browser';
import { ApplicationShell, ShellLayoutRestorer } from '@theia/core/lib/browser';
import { ArduinoApplicationShell } from './customization/arduino-application-shell';
import { ArduinoFrontendApplication } from './customization/arduino-frontend-application';
import { BoardsConfigDialog, BoardsConfigDialogProps } from './boards/boards-config-dialog';
import { BoardsConfigDialogWidget } from './boards/boards-config-dialog-widget';
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
import { SilentScmContribution } from './customization/silent-scm-contribution';
import { ArduinoScmContribution } from './customization/arduino-scm-contribution';
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
import { SilentSearchInWorkspaceContribution } from './customization/silent-search-in-workspace-contribution';
import { ArduinoSearchInWorkspaceContribution } from './customization/arduino-search-in-workspace-contribution';
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
import { LibraryItemRenderer } from './library/library-item-renderer';
import { BoardItemRenderer } from './boards/boards-item-renderer';
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
import { ConfigService, ConfigServicePath } from '../common/protocol/config-service';
@@ -70,6 +68,9 @@ import { ArduinoProblemManager } from './markers/arduino-problem-manager';
import { BoardsAutoInstaller } from './boards/boards-auto-installer';
import { AboutDialog } from '@theia/core/lib/browser/about-dialog';
import { ArduinoAboutDialog } from './customization/arduino-about-dialog';
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
import { EditorMode } from './editor-mode';
import { ListItemRenderer } from './components/component-list/list-item-renderer';
const ElementQueries = require('css-element-queries/src/ElementQueries');
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
@@ -90,9 +91,11 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
bind(LanguageGrammarDefinitionContribution).to(ArduinoLanguageGrammarContribution).inSingletonScope();
bind(LanguageClientContribution).to(ArduinoLanguageClientContribution).inSingletonScope();
// Renderer for both the library and the core widgets.
bind(ListItemRenderer).toSelf().inSingletonScope();
// Library service
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
// Library list widget
bind(LibraryListWidget).toSelf();
bindViewContribution(bind, LibraryListWidgetFrontendContribution);
@@ -101,7 +104,6 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
createWidget: () => context.container.get(LibraryListWidget)
}));
bind(FrontendApplicationContribution).toService(LibraryListWidgetFrontendContribution);
bind(LibraryItemRenderer).toSelf().inSingletonScope();
// Sketch list service
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
@@ -117,6 +119,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
}).inSingletonScope();
// Boards service client to receive and delegate notifications from the backend.
bind(BoardsServiceClientImpl).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(BoardsServiceClientImpl);
bind(BoardsServiceClient).toDynamicValue(context => {
const client = context.container.get(BoardsServiceClientImpl);
WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath, client);
@@ -135,7 +138,6 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
createWidget: () => context.container.get(BoardsListWidget)
}));
bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution);
bind(BoardItemRenderer).toSelf().inSingletonScope();
// Board select dialog
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();
@@ -158,7 +160,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
}).inSingletonScope();
// The workspace service extension
bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt) => {
bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt: WorkspaceServiceExt) => {
WebSocketConnectionProvider.createProxy(container, WorkspaceServiceExtPath, workspaceServiceExt);
// Eagerly active the core, library, and boards services.
container.get(CoreService);
@@ -170,6 +172,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
// Serial Monitor
bind(MonitorModel).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(MonitorModel);
bind(MonitorWidget).toSelf();
bindViewContribution(bind, MonitorViewContribution);
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
@@ -198,50 +201,37 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
const themeService = ThemeService.get();
themeService.register(...ArduinoTheme.themes);
// Customizing default Theia layout
if (!ArduinoAdvancedMode.TOGGLED) {
unbind(OutlineViewContribution);
bind(OutlineViewContribution).to(SilentOutlineViewContribution).inSingletonScope();
unbind(ProblemContribution);
bind(ProblemContribution).to(SilentProblemContribution).inSingletonScope();
unbind(FileNavigatorContribution);
bind(FileNavigatorContribution).to(SilentNavigatorContribution).inSingletonScope();
unbind(OutputToolbarContribution);
bind(OutputToolbarContribution).to(ArduinoOutputToolContribution).inSingletonScope();
unbind(EditorContribution);
bind(EditorContribution).to(ArduinoEditorContribution).inSingletonScope();
unbind(MonacoStatusBarContribution);
bind(MonacoStatusBarContribution).to(ArduinoMonacoStatusBarContribution).inSingletonScope();
unbind(ApplicationShell);
bind(ApplicationShell).to(ArduinoApplicationShell).inSingletonScope();
unbind(ScmContribution);
bind(ScmContribution).to(SilentScmContribution).inSingletonScope();
unbind(SearchInWorkspaceFrontendContribution);
bind(SearchInWorkspaceFrontendContribution).to(SilentSearchInWorkspaceContribution).inSingletonScope();
} else {
// We use this CSS class on the body to modify the visibility of the close button for the editors and views.
document.body.classList.add(ArduinoAdvancedMode.LS_ID);
}
unbind(FrontendApplication);
bind(FrontendApplication).to(ArduinoFrontendApplication).inSingletonScope();
// Customizing default Theia layout based on the editor mode: `pro-mode` or `classic`.
bind(EditorMode).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(EditorMode);
rebind(OutlineViewContribution).to(ArduinoOutlineViewContribution).inSingletonScope();
rebind(ProblemContribution).to(ArduinoProblemContribution).inSingletonScope();
rebind(FileNavigatorContribution).to(ArduinoNavigatorContribution).inSingletonScope();
rebind(OutputToolbarContribution).to(ArduinoOutputToolContribution).inSingletonScope();
rebind(EditorContribution).to(ArduinoEditorContribution).inSingletonScope();
rebind(MonacoStatusBarContribution).to(ArduinoMonacoStatusBarContribution).inSingletonScope();
rebind(ApplicationShell).to(ArduinoApplicationShell).inSingletonScope();
rebind(ScmContribution).to(ArduinoScmContribution).inSingletonScope();
rebind(SearchInWorkspaceFrontendContribution).to(ArduinoSearchInWorkspaceContribution).inSingletonScope();
rebind(FrontendApplication).to(ArduinoFrontendApplication).inSingletonScope();
// Monaco customizations
unbind(MonacoEditorProvider);
bind(ArduinoMonacoEditorProvider).toSelf().inSingletonScope();
bind(MonacoEditorProvider).toService(ArduinoMonacoEditorProvider);
rebind(MonacoEditorProvider).toService(ArduinoMonacoEditorProvider);
// Decorator customizations
unbind(TabBarDecoratorService);
bind(ArduinoTabBarDecoratorService).toSelf().inSingletonScope();
bind(TabBarDecoratorService).toService(ArduinoTabBarDecoratorService);
rebind(TabBarDecoratorService).toService(ArduinoTabBarDecoratorService);
// Problem markers
unbind(ProblemManager);
bind(ArduinoProblemManager).toSelf().inSingletonScope();
bind(ProblemManager).toService(ArduinoProblemManager);
rebind(ProblemManager).toService(ArduinoProblemManager);
// About dialog to show the CLI version
unbind(AboutDialog);
bind(ArduinoAboutDialog).toSelf().inSingletonScope();
bind(AboutDialog).toService(ArduinoAboutDialog);
rebind(AboutDialog).toService(ArduinoAboutDialog);
// Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579
bind(ArduinoShellLayoutRestorer).toSelf().inSingletonScope();
rebind(ShellLayoutRestorer).toService(ArduinoShellLayoutRestorer);
});

View File

@@ -9,6 +9,7 @@ const ARDUINO_JSON = MonacoThemeRegistry.SINGLETON.register(
export class ArduinoTheme {
static readonly arduino: Theme = {
type: 'light',
id: 'arduino-theme',
label: 'Arduino Light Theme',
description: 'Arduino Light Theme',

View File

@@ -4,7 +4,7 @@ import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service
import { ConfigService } from '../common/protocol/config-service';
import { SketchesService } from '../common/protocol/sketches-service';
import { ArduinoWorkspaceRootResolver } from './arduino-workspace-resolver';
import { ArduinoAdvancedMode } from './arduino-frontend-contribution';
import { EditorMode } from './editor-mode';
@injectable()
export class ArduinoWorkspaceService extends WorkspaceService {
@@ -18,7 +18,10 @@ export class ArduinoWorkspaceService extends WorkspaceService {
@inject(LabelProvider)
protected readonly labelProvider: LabelProvider;
async getDefaultWorkspacePath(): Promise<string | undefined> {
@inject(EditorMode)
protected readonly editorMode: EditorMode;
async getDefaultWorkspaceUri(): Promise<string | undefined> {
const [hash, recentWorkspaces, recentSketches] = await Promise.all([
window.location.hash,
this.sketchService.getSketches().then(sketches => sketches.map(({ uri }) => uri)),
@@ -36,7 +39,8 @@ export class ArduinoWorkspaceService extends WorkspaceService {
await this.server.setMostRecentlyUsedWorkspace(uri);
return toOpen.uri;
}
return (await this.sketchService.createNewSketch()).uri;
const { sketchDirUri } = (await this.configService.getConfiguration());
return (await this.sketchService.createNewSketch(sketchDirUri)).uri;
}
private async isValid(uri: string): Promise<boolean> {
@@ -46,7 +50,7 @@ export class ArduinoWorkspaceService extends WorkspaceService {
}
// The workspace root location must exist. However, when opening a workspace root in pro-mode,
// the workspace root must not be a sketch folder. It can be the default sketch directory, or any other directories, for instance.
if (!ArduinoAdvancedMode.TOGGLED) {
if (this.editorMode.proMode) {
return true;
}
const sketchFolder = await this.sketchService.isSketchFolder(uri);

View File

@@ -4,7 +4,7 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
import { BoardsService, Board } from '../../common/protocol/boards-service';
import { BoardsServiceClientImpl } from './boards-service-client-impl';
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
import { InstallationProgressDialog } from '../components/installation-progress-dialog';
import { InstallationProgressDialog } from '../components/progress-dialog';
import { BoardsConfig } from './boards-config';
@@ -41,23 +41,23 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
.filter(({ installable, installedVersion }) => installable && !installedVersion);
for (const candidate of candidates) {
// tslint:disable-next-line:max-line-length
this.messageService.info(`The \`"${candidate.name}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, 'Yes', 'Install Manually').then(async answer => {
this.messageService.info(`The \`"${candidate.name}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, 'Install Manually', 'Yes').then(async answer => {
if (answer === 'Yes') {
const dialog = new InstallationProgressDialog(candidate.name);
const dialog = new InstallationProgressDialog(candidate.name, candidate.availableVersions[0]);
dialog.open();
try {
await this.boardsService.install(candidate);
await this.boardsService.install({ item: candidate });
} finally {
dialog.close();
}
}
if (answer) {
this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase()));
}
}
});
}
})
}
}
}
}

View File

@@ -1,54 +0,0 @@
import * as React from 'react';
import { injectable } from 'inversify';
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
import { BoardPackage } from '../../common/protocol/boards-service';
@injectable()
export class BoardItemRenderer extends ListItemRenderer<BoardPackage> {
renderItem(item: BoardPackage, install: (item: BoardPackage) => Promise<void>): React.ReactNode {
const name = <span className='name'>{item.name}</span>;
const author = <span className='author'>{item.author}</span>;
const installedVersion = !!item.installedVersion && <div className='version-info'>
<span className='version'>Version {item.installedVersion}</span>
<span className='installed'>INSTALLED</span>
</div>;
const summary = <div className='summary'>{item.summary}</div>;
const description = <div className='summary'>{item.description}</div>;
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
const installButton = item.installable && !item.installedVersion &&
<button className='install' onClick={install.bind(this, item)}>INSTALL</button>;
const versions = (() => {
const { availableVersions } = item;
if (!!item.installedVersion || availableVersions.length === 0) {
return undefined;
} else if (availableVersions.length === 1) {
return <label>{availableVersions[0]}</label>
} else {
return <select>{item.availableVersions.map(version => <option value={version} key={version}>{version}</option>)}</select>;
}
})();
return <div className='component-list-item noselect'>
<div className='header'>
<span>{name} by {author}</span>
{installedVersion}
</div>
<div className='content'>
{summary}
{description}
</div>
<div className='info'>
{moreInfo}
</div>
<div className='footer'>
{installButton}
{versions}
</div>
</div>;
}
}

View File

@@ -1,7 +1,7 @@
import { inject, injectable } from 'inversify';
import { BoardPackage, BoardsService } from '../../common/protocol/boards-service';
import { ListWidget } from '../components/component-list/list-widget';
import { BoardItemRenderer } from './boards-item-renderer';
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
@injectable()
export class BoardsListWidget extends ListWidget<BoardPackage> {
@@ -11,7 +11,7 @@ export class BoardsListWidget extends ListWidget<BoardPackage> {
constructor(
@inject(BoardsService) protected service: BoardsService,
@inject(BoardItemRenderer) protected itemRenderer: BoardItemRenderer) {
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<BoardPackage>) {
super({
id: BoardsListWidget.WIDGET_ID,

View File

@@ -1,21 +1,27 @@
import { injectable, inject, postConstruct } from 'inversify';
import { injectable, inject } from 'inversify';
import { Emitter } from '@theia/core/lib/common/event';
import { ILogger } from '@theia/core/lib/common/logger';
import { MessageService } from '@theia/core/lib/common/message-service';
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { RecursiveRequired } from '../../common/types';
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, AttachedSerialBoard, Board, Port } from '../../common/protocol/boards-service';
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, AttachedSerialBoard, Board, Port, BoardUninstalledEvent } from '../../common/protocol/boards-service';
import { BoardsConfig } from './boards-config';
@injectable()
export class BoardsServiceClientImpl implements BoardsServiceClient {
export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApplicationContribution {
@inject(ILogger)
protected logger: ILogger;
@inject(MessageService)
protected messageService: MessageService;
@inject(LocalStorageService)
protected storageService: LocalStorageService;
protected readonly onBoardInstalledEmitter = new Emitter<BoardInstalledEvent>();
protected readonly onBoardUninstalledEmitter = new Emitter<BoardUninstalledEvent>();
protected readonly onAttachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
protected readonly onSelectedBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
@@ -31,11 +37,11 @@ export class BoardsServiceClientImpl implements BoardsServiceClient {
readonly onBoardsChanged = this.onAttachedBoardsChangedEmitter.event;
readonly onBoardInstalled = this.onBoardInstalledEmitter.event;
readonly onBoardUninstalled = this.onBoardUninstalledEmitter.event;
readonly onBoardsConfigChanged = this.onSelectedBoardsConfigChangedEmitter.event;
@postConstruct()
protected init(): void {
this.loadState();
async onStart(): Promise<void> {
return this.loadState();
}
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
@@ -87,6 +93,11 @@ export class BoardsServiceClientImpl implements BoardsServiceClient {
this.onBoardInstalledEmitter.fire(event);
}
notifyBoardUninstalled(event: BoardUninstalledEvent): void {
this.logger.info('Board uninstalled: ', JSON.stringify(event));
this.onBoardUninstalledEmitter.fire(event);
}
set boardsConfig(config: BoardsConfig.Config) {
this.logger.info('Board config changed: ', JSON.stringify(config));
this._boardsConfig = config;
@@ -100,6 +111,56 @@ export class BoardsServiceClientImpl implements BoardsServiceClient {
return this._boardsConfig;
}
/**
* `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`.
*/
canVerify(
config: BoardsConfig.Config | undefined = this.boardsConfig,
options: { silent: boolean } = { silent: true }): config is BoardsConfig.Config & { selectedBoard: Board } {
if (!config) {
return false;
}
if (!config.selectedBoard) {
if (!options.silent) {
this.messageService.warn('No boards selected.', { timeout: 3000 });
}
return false;
}
return true;
}
/**
* `true` if the `canVerify` and the `config.selectedPort` is also set with FQBN, hence can upload to board. Otherwise, `false`.
*/
canUploadTo(
config: BoardsConfig.Config | undefined = this.boardsConfig,
options: { silent: boolean } = { silent: true }): config is RecursiveRequired<BoardsConfig.Config> {
if (!this.canVerify(config, options)) {
return false;
}
const { name } = config.selectedBoard;
if (!config.selectedPort) {
if (!options.silent) {
this.messageService.warn(`No ports selected for board: '${name}'.`, { timeout: 3000 });
}
return false;
}
if (!config.selectedBoard.fqbn) {
if (!options.silent) {
this.messageService.warn(`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, { timeout: 3000 });
}
return false;
}
return true;
}
protected saveState(): Promise<void> {
return this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig);
}
@@ -108,15 +169,10 @@ export class BoardsServiceClientImpl implements BoardsServiceClient {
const storedValidBoardsConfig = await this.storageService.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
if (storedValidBoardsConfig) {
this.latestValidBoardsConfig = storedValidBoardsConfig;
if (this.canUploadTo(this.latestValidBoardsConfig)) {
this.boardsConfig = this.latestValidBoardsConfig;
}
}
}
protected canVerify(config: BoardsConfig.Config | undefined): config is BoardsConfig.Config & { selectedBoard: Board } {
return !!config && !!config.selectedBoard;
}
protected canUploadTo(config: BoardsConfig.Config | undefined): config is RecursiveRequired<BoardsConfig.Config> {
return this.canVerify(config) && !!config.selectedPort && !!config.selectedBoard.fqbn;
}
}

View File

@@ -0,0 +1,62 @@
import * as React from 'react';
import Select from 'react-select';
import { Styles } from 'react-select/src/styles';
import { Props } from 'react-select/src/components';
import { ThemeConfig } from 'react-select/src/theme';
export class ArduinoSelect<T> extends Select<T> {
constructor(props: Readonly<Props<T>>) {
super(props);
}
render(): React.ReactNode {
const controlHeight = 27; // from `monitor.css` -> `.serial-monitor-container .head` (`height: 27px;`)
const styles: Styles = {
control: styles => ({
...styles,
minWidth: 120,
color: 'var(--theia-ui-font-color1)'
}),
dropdownIndicator: styles => ({
...styles,
padding: 0
}),
indicatorSeparator: () => ({
display: 'none'
}),
indicatorsContainer: () => ({
padding: '0px 5px'
}),
menu: styles => ({
...styles,
marginTop: 0
})
};
const theme: ThemeConfig = theme => ({
...theme,
borderRadius: 0,
spacing: {
controlHeight,
baseUnit: 2,
menuGutter: 4
}, colors: {
...theme.colors,
// `primary50`??? it's crazy but apparently, without this, we would get a light-blueish
// color when selecting an option in the select by clicking and then not releasing the button.
// https://react-select.com/styles#overriding-the-theme
primary50: 'var(--theia-accent-color4)',
}
});
const DropdownIndicator = () => <span className='fa fa-caret-down caret' />;
return <Select
{...this.props}
components={{ DropdownIndicator }}
theme={theme}
styles={styles}
classNamePrefix='arduino-select'
isSearchable={false}
/>
}
}

View File

@@ -1,25 +1,66 @@
import * as React from 'react';
import { Installable } from '../../../common/protocol/installable';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ListItemRenderer } from './list-item-renderer';
export class ComponentListItem<T> extends React.Component<ComponentListItem.Props<T>> {
export class ComponentListItem<T extends ArduinoComponent> extends React.Component<ComponentListItem.Props<T>, ComponentListItem.State> {
constructor(props: ComponentListItem.Props<T>) {
super(props);
if (props.item.installable) {
const version = props.item.availableVersions.filter(version => version !== props.item.installedVersion)[0];
this.state = {
selectedVersion: version
};
}
}
protected async install(item: T): Promise<void> {
await this.props.install(item);
const toInstall = this.state.selectedVersion;
const version = this.props.item.availableVersions.filter(version => version !== this.state.selectedVersion)[0];
this.setState({
selectedVersion: version
});
try {
await this.props.install(item, toInstall);
} catch {
this.setState({
selectedVersion: toInstall
});
}
}
protected async uninstall(item: T): Promise<void> {
await this.props.uninstall(item);
}
protected onVersionChange(version: Installable.Version) {
this.setState({ selectedVersion: version });
}
render(): React.ReactNode {
const { item, itemRenderer, install } = this.props;
return itemRenderer.renderItem(item, install.bind(this));
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 interface Props<T> {
export interface Props<T extends ArduinoComponent> {
readonly item: T;
readonly install: (item: T) => Promise<void>;
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
readonly uninstall: (item: T) => Promise<void>;
readonly itemRenderer: ListItemRenderer<T>;
}
export interface State {
selectedVersion?: Installable.Version;
}
}

View File

@@ -1,8 +1,10 @@
import * as React from 'react';
import { Installable } from '../../../common/protocol/installable';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ComponentListItem } from './component-list-item';
import { ListItemRenderer } from './list-item-renderer';
export class ComponentList<T> extends React.Component<ComponentList.Props<T>> {
export class ComponentList<T extends ArduinoComponent> extends React.Component<ComponentList.Props<T>> {
protected container?: HTMLElement;
@@ -29,18 +31,20 @@ export class ComponentList<T> extends React.Component<ComponentList.Props<T>> {
key={this.props.itemLabel(item)}
item={item}
itemRenderer={this.props.itemRenderer}
install={this.props.install} />
install={this.props.install}
uninstall={this.props.uninstall} />
}
}
export namespace ComponentList {
export interface Props<T> {
export interface Props<T extends ArduinoComponent> {
readonly items: T[];
readonly itemLabel: (item: T) => string;
readonly itemRenderer: ListItemRenderer<T>;
readonly install: (item: T) => Promise<void>;
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
readonly uninstall: (item: T) => Promise<void>;
readonly resolveContainer: (element: HTMLElement) => void;
}

View File

@@ -1,14 +1,17 @@
import * as React from 'react';
import debounce = require('lodash.debounce');
import { Event } from '@theia/core/lib/common/event';
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
import { Searchable } from '../../../common/protocol/searchable';
import { Installable } from '../../../common/protocol/installable';
import { InstallationProgressDialog } from '../installation-progress-dialog';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { InstallationProgressDialog, UninstallationProgressDialog } from '../progress-dialog';
import { SearchBar } from './search-bar';
import { ListWidget } from './list-widget';
import { ComponentList } from './component-list';
import { ListItemRenderer } from './list-item-renderer';
export class FilterableListContainer<T> extends React.Component<FilterableListContainer.Props<T>, FilterableListContainer.State<T>> {
export class FilterableListContainer<T extends ArduinoComponent> extends React.Component<FilterableListContainer.Props<T>, FilterableListContainer.State<T>> {
constructor(props: Readonly<FilterableListContainer.Props<T>>) {
super(props);
@@ -18,12 +21,18 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
};
}
componentWillMount(): void {
componentDidMount(): void {
this.search = debounce(this.search, 500);
this.handleFilterTextChange('');
this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this));
}
componentDidUpdate(): void {
// See: arduino/arduino-pro-ide#101
// Resets the top of the perfect scroll-bar's thumb.
this.props.container.updateScrollBar();
}
render(): React.ReactNode {
return <div className={'filterable-list-container'}>
{this.renderSearchFilter()}
@@ -51,6 +60,7 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
itemLabel={itemLabel}
itemRenderer={itemRenderer}
install={this.install.bind(this)}
uninstall={this.uninstall.bind(this)}
resolveContainer={resolveContainer}
/>
}
@@ -75,12 +85,34 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
return items.sort((left, right) => itemLabel(left).localeCompare(itemLabel(right)));
}
protected async install(item: T): Promise<void> {
protected async install(item: T, version: Installable.Version): Promise<void> {
const { installable, searchable, itemLabel } = this.props;
const dialog = new InstallationProgressDialog(itemLabel(item));
const dialog = new InstallationProgressDialog(itemLabel(item), version);
dialog.open();
try {
await installable.install(item);
await installable.install({ item, version });
const { items } = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
} finally {
dialog.close();
}
}
protected async uninstall(item: T): Promise<void> {
const uninstall = await new ConfirmDialog({
title: 'Uninstall',
msg: `Do you want to uninstall ${item.name}?`,
ok: 'Yes',
cancel: 'No'
}).open();
if (!uninstall) {
return;
}
const { installable, searchable, itemLabel } = this.props;
const dialog = new UninstallationProgressDialog(itemLabel(item));
dialog.open();
try {
await installable.uninstall({ item });
const { items } = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
} finally {
@@ -92,7 +124,8 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
export namespace FilterableListContainer {
export interface Props<T> {
export interface Props<T extends ArduinoComponent> {
readonly container: ListWidget<T>;
readonly installable: Installable<T>;
readonly searchable: Searchable<T>;
readonly itemLabel: (item: T) => string;

View File

@@ -1,14 +1,17 @@
import * as React from 'react';
import { inject, injectable } from 'inversify';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { Installable } from '../../../common/protocol/installable';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ComponentListItem } from './component-list-item';
@injectable()
export abstract class ListItemRenderer<T> {
export class ListItemRenderer<T extends ArduinoComponent> {
@inject(WindowService)
protected windowService: WindowService;
protected onClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
protected onMoreInfoClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
const { target } = event.nativeEvent;
if (target instanceof HTMLAnchorElement) {
this.windowService.openNewWindow(target.href, { external: true });
@@ -16,6 +19,73 @@ export abstract class ListItemRenderer<T> {
}
}
abstract renderItem(item: T, install: (item: T) => Promise<void>): React.ReactNode;
renderItem(
input: ComponentListItem.State & { item: T },
install: (item: T) => Promise<void>,
uninstall: (item: T) => Promise<void>,
onVersionChange: (version: Installable.Version) => void
): React.ReactNode {
}
const { item } = input;
const name = <span className='name'>{item.name}</span>;
const author = <span className='author'>{item.author}</span>;
const onClickUninstall = () => uninstall(item);
const installedVersion = !!item.installedVersion && <div className='version-info'>
<span className='version'>Version {item.installedVersion}</span>
<span className='installed' onClick={onClickUninstall} />
</div>;
const summary = <div className='summary'>{item.summary}</div>;
const description = <div className='summary'>{item.description}</div>;
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>More info</a>;
const onClickInstall = () => install(item);
const installButton = item.installable &&
<button className='install' onClick={onClickInstall}>INSTALL</button>;
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const version = event.target.value;
if (version) {
onVersionChange(version);
}
}
const versions = (() => {
const { availableVersions } = item;
if (availableVersions.length === 0) {
return undefined;
} else if (availableVersions.length === 1) {
return <label>{availableVersions[0]}</label>
} else {
return <select
value={input.selectedVersion}
onChange={onSelectChange}>
{
item.availableVersions
.filter(version => version !== item.installedVersion) // Filter the version that is currently installed.
.map(version => <option value={version} key={version}>{version}</option>)
}
</select>;
}
})();
return <div className='component-list-item noselect'>
<div className='header'>
<span>{name} by {author}</span>
{installedVersion}
</div>
<div className='content'>
{summary}
{description}
</div>
<div className='info'>
{moreInfo}
</div>
<div className='footer'>
{installButton}
{versions}
</div>
</div>;
}
}

View File

@@ -1,10 +1,11 @@
import { injectable } from 'inversify';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ListWidget } from './list-widget';
@injectable()
export abstract class ListWidgetFrontendContribution<T> extends AbstractViewContribution<ListWidget<T>> implements FrontendApplicationContribution {
export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent> extends AbstractViewContribution<ListWidget<T>> implements FrontendApplicationContribution {
async initializeLayout(): Promise<void> {
}

View File

@@ -7,11 +7,12 @@ import { MaybePromise } from '@theia/core/lib/common/types';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { Installable } from '../../../common/protocol/installable';
import { Searchable } from '../../../common/protocol/searchable';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { FilterableListContainer } from './filterable-list-container';
import { ListItemRenderer } from './list-item-renderer';
@injectable()
export abstract class ListWidget<T> extends ReactWidget {
export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget {
/**
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
@@ -61,6 +62,7 @@ export abstract class ListWidget<T> extends ReactWidget {
render(): React.ReactNode {
return <FilterableListContainer<T>
container={this}
resolveContainer={this.deferredContainer.resolve}
resolveFocus={this.onFocusResolved}
searchable={this.options.searchable}
@@ -74,10 +76,16 @@ export abstract class ListWidget<T> extends ReactWidget {
this.deferredContainer.promise.then(() => this.filterTextChangeEmitter.fire(filterText));
}
updateScrollBar(): void {
if (this.scrollBar) {
this.scrollBar.update();
}
}
}
export namespace ListWidget {
export interface Options<T> {
export interface Options<T extends ArduinoComponent> {
readonly id: string;
readonly label: string;
readonly iconClass: string;

View File

@@ -1,12 +0,0 @@
import { AbstractDialog } from '@theia/core/lib/browser';
export class InstallationProgressDialog extends AbstractDialog<undefined> {
readonly value = undefined;
constructor(componentName: string) {
super({ title: 'Installation in progress' });
this.contentNode.textContent = `Installing ${componentName}. Please wait.`;
}
}

View File

@@ -0,0 +1,23 @@
import { AbstractDialog } from '@theia/core/lib/browser';
export class InstallationProgressDialog extends AbstractDialog<undefined> {
readonly value = undefined;
constructor(componentName: string, version: string) {
super({ title: 'Installation in progress' });
this.contentNode.textContent = `Installing ${componentName} [${version}]. Please wait...`;
}
}
export class UninstallationProgressDialog extends AbstractDialog<undefined> {
readonly value = undefined;
constructor(componentName: string) {
super({ title: 'Uninstallation in progress' });
this.contentNode.textContent = `Uninstalling ${componentName}. Please wait...`;
}
}

View File

@@ -1,22 +1,35 @@
import { ApplicationShell, Widget, Saveable, FocusTracker, Message } from '@theia/core/lib/browser';
import { EditorWidget } from '@theia/editor/lib/browser';
import { injectable, inject } from 'inversify';
import { EditorMode } from '../editor-mode';
@injectable()
export class ArduinoApplicationShell extends ApplicationShell {
@inject(EditorMode)
protected readonly editorMode: EditorMode;
protected refreshBottomPanelToggleButton() {
if (this.editorMode.proMode) {
super.refreshBottomPanelToggleButton();
}
}
protected async track(widget: Widget): Promise<void> {
const tracker = (this as any).tracker as FocusTracker<Widget>;
tracker.add(widget);
this.disableClose(Saveable.apply(widget));
if (ApplicationShell.TrackableWidgetProvider.is(widget)) {
for (const toTrack of await widget.getTrackableWidgets()) {
tracker.add(toTrack);
this.disableClose(Saveable.apply(toTrack));
}
if (widget.onDidChangeTrackableWidgets) {
widget.onDidChangeTrackableWidgets(widgets => widgets.forEach(w => this.track(w)));
if (this.editorMode.proMode) {
super.track(widget);
} else {
const tracker = (this as any).tracker as FocusTracker<Widget>;
tracker.add(widget);
this.disableClose(Saveable.apply(widget));
if (ApplicationShell.TrackableWidgetProvider.is(widget)) {
for (const toTrack of await widget.getTrackableWidgets()) {
tracker.add(toTrack);
this.disableClose(Saveable.apply(toTrack));
}
if (widget.onDidChangeTrackableWidgets) {
widget.onDidChangeTrackableWidgets(widgets => widgets.forEach(w => this.track(w)));
}
}
}
}

View File

@@ -1,11 +0,0 @@
import { injectable } from 'inversify';
import { FileMenuContribution } from '@theia/workspace/lib/browser';
import { MenuModelRegistry } from '@theia/core';
@injectable()
export class ArduinoFileMenuContribution extends FileMenuContribution {
registerMenus(registry: MenuModelRegistry) {
}
}

View File

@@ -2,7 +2,8 @@ import { injectable, inject } from 'inversify';
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { ArduinoFrontendContribution, ArduinoAdvancedMode } from '../arduino-frontend-contribution';
import { EditorMode } from '../editor-mode';
import { ArduinoFrontendContribution } from '../arduino-frontend-contribution';
@injectable()
export class ArduinoFrontendApplication extends FrontendApplication {
@@ -16,12 +17,15 @@ export class ArduinoFrontendApplication extends FrontendApplication {
@inject(ArduinoFrontendContribution)
protected readonly frontendContribution: ArduinoFrontendContribution;
@inject(EditorMode)
protected readonly editorMode: EditorMode;
protected async initializeLayout(): Promise<void> {
super.initializeLayout().then(() => {
return super.initializeLayout().then(() => {
// If not in PRO mode, we open the sketch file with all the related files.
// Otherwise, we reuse the workbench's restore functionality and we do not open anything at all.
// TODO: check `otherwise`. Also, what if we check for opened editors, instead of blindly opening them?
if (!ArduinoAdvancedMode.TOGGLED) {
if (!this.editorMode.proMode) {
this.workspaceService.roots.then(roots => {
for (const root of roots) {
this.fileSystem.exists(root.uri).then(exists => {

View File

@@ -0,0 +1,18 @@
import { injectable, inject } from 'inversify';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
import { EditorMode } from '../editor-mode';
@injectable()
export class ArduinoNavigatorContribution extends FileNavigatorContribution {
@inject(EditorMode)
protected readonly editorMode: EditorMode;
async initializeLayout(app: FrontendApplication): Promise<void> {
if (this.editorMode.proMode) {
return super.initializeLayout(app);
}
}
}

View File

@@ -0,0 +1,19 @@
import { injectable, inject } from 'inversify';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
import { EditorMode } from '../editor-mode';
@injectable()
export class ArduinoOutlineViewContribution extends OutlineViewContribution {
@inject(EditorMode)
protected readonly editorMode: EditorMode;
async initializeLayout(app: FrontendApplication): Promise<void> {
if (this.editorMode.proMode) {
return super.initializeLayout(app);
}
}
}

View File

@@ -1,11 +1,18 @@
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
import { inject, injectable } from 'inversify';
import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { injectable } from 'inversify';
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
import { EditorMode } from '../editor-mode';
@injectable()
export class ArduinoOutputToolContribution extends OutputToolbarContribution {
@inject(EditorMode)
protected readonly editorMode: EditorMode;
async registerToolbarItems(toolbarRegistry: TabBarToolbarRegistry): Promise<void> {
if (this.editorMode.proMode) {
super.registerToolbarItems(toolbarRegistry);
}
}
}

View File

@@ -0,0 +1,25 @@
import { inject, injectable } from 'inversify';
import { ProblemStat } from '@theia/markers/lib/browser/problem/problem-manager';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
import { EditorMode } from '../editor-mode';
@injectable()
export class ArduinoProblemContribution extends ProblemContribution {
@inject(EditorMode)
protected readonly editorMode: EditorMode;
async initializeLayout(app: FrontendApplication): Promise<void> {
if (this.editorMode.proMode) {
return super.initializeLayout(app);
}
}
protected setStatusBarElement(problemStat: ProblemStat): void {
if (this.editorMode.proMode) {
super.setStatusBarElement(problemStat);
}
}
}

View File

@@ -0,0 +1,24 @@
import { inject, injectable } from 'inversify';
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar';
import { EditorMode } from '../editor-mode';
@injectable()
export class ArduinoScmContribution extends ScmContribution {
@inject(EditorMode)
protected readonly editorMode: EditorMode;
async initializeLayout(): Promise<void> {
if (this.editorMode.proMode) {
return super.initializeLayout();
}
}
protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
if (this.editorMode.proMode) {
super.setStatusBarEntry(id, entry);
}
}
}

View File

@@ -0,0 +1,18 @@
import { inject, injectable } from 'inversify';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
import { EditorMode } from '../editor-mode';
@injectable()
export class ArduinoSearchInWorkspaceContribution extends SearchInWorkspaceFrontendContribution {
@inject(EditorMode)
protected readonly editorMode: EditorMode;
async initializeLayout(app: FrontendApplication): Promise<void> {
if (this.editorMode.proMode) {
return super.initializeLayout(app);
}
}
}

View File

@@ -1,11 +0,0 @@
import { injectable } from 'inversify';
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
import { FrontendApplication } from '@theia/core/lib/browser';
@injectable()
export class SilentNavigatorContribution extends FileNavigatorContribution {
async initializeLayout(app: FrontendApplication): Promise<void> {
}
}

View File

@@ -1,12 +0,0 @@
import { injectable } from 'inversify';
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
import { FrontendApplication } from '@theia/core/lib/browser';
@injectable()
export class SilentOutlineViewContribution extends OutlineViewContribution {
async initializeLayout(app: FrontendApplication): Promise<void> {
}
}

View File

@@ -1,15 +0,0 @@
import { injectable } from 'inversify';
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
import { ProblemStat } from '@theia/markers/lib/browser/problem/problem-manager';
import { FrontendApplication } from '@theia/core/lib/browser';
@injectable()
export class SilentProblemContribution extends ProblemContribution {
async initializeLayout(app: FrontendApplication): Promise<void> {
}
protected setStatusBarElement(problemStat: ProblemStat) {
}
}

View File

@@ -1,14 +0,0 @@
import { injectable } from 'inversify';
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
import { StatusBarEntry } from '@theia/core/lib/browser';
@injectable()
export class SilentScmContribution extends ScmContribution {
async initializeLayout(): Promise<void> {
}
protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
}
}

View File

@@ -1,11 +0,0 @@
import { injectable } from 'inversify';
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
import { FrontendApplication } from '@theia/core/lib/browser';
@injectable()
export class SilentSearchInWorkspaceContribution extends SearchInWorkspaceFrontendContribution {
async initializeLayout(app: FrontendApplication): Promise<void> {
}
}

View File

@@ -0,0 +1,48 @@
import { injectable } from 'inversify';
import { ApplicationShell, FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
import { EditorWidget } from '@theia/editor/lib/browser';
@injectable()
export class EditorMode implements FrontendApplicationContribution {
protected app: FrontendApplication;
onStart(app: FrontendApplication): void {
this.app = app;
if (this.proMode) {
// We use this CSS class on the body to modify the visibility of the close button for the editors and views.
document.body.classList.add(EditorMode.PRO_MODE_KEY);
}
}
get proMode(): boolean {
const value = window.localStorage.getItem(EditorMode.PRO_MODE_KEY);
return value === 'true';
}
async toggle(): Promise<void> {
const oldState = this.proMode;
const inAdvancedMode = !oldState;
window.localStorage.setItem(EditorMode.PRO_MODE_KEY, String(inAdvancedMode));
if (!inAdvancedMode) {
const { shell } = this.app;
// Close all widget that is neither editor nor `Output`.
for (const area of ['left', 'right', 'bottom', 'main'] as Array<ApplicationShell.Area>) {
shell.closeTabs(area, ({ owner }) => !(owner instanceof EditorWidget || owner instanceof OutputWidget));
}
}
// `storeLayout` has a sync API but the implementation is async, we store the layout manually before we reload the page.
// See: https://github.com/eclipse-theia/theia/issues/6579
// XXX: hack instead of injecting the `ArduinoShellLayoutRestorer` we have to retrieve it from the application to avoid DI cycle.
const layoutRestorer = (this.app as any).layoutRestorer as ArduinoShellLayoutRestorer
await layoutRestorer.storeLayoutAsync(this.app);
window.location.reload(true);
}
}
export namespace EditorMode {
export const PRO_MODE_KEY = 'arduino-advanced-mode';
}

View File

Before

Width:  |  Height:  |  Size: 962 B

After

Width:  |  Height:  |  Size: 962 B

View File

@@ -2,6 +2,7 @@ import { injectable, inject, postConstruct } from 'inversify';
import { BaseLanguageClientContribution } from '@theia/languages/lib/browser';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
import { BoardsConfig } from '../boards/boards-config';
import { Board, BoardPackage } from '../../common/protocol/boards-service';
@injectable()
export class ArduinoLanguageClientContribution extends BaseLanguageClientContribution {
@@ -25,6 +26,18 @@ export class ArduinoLanguageClientContribution extends BaseLanguageClientContrib
@postConstruct()
protected init() {
this.boardsServiceClient.onBoardsConfigChanged(this.selectBoard.bind(this));
const restartIfAffected = (pkg: BoardPackage) => {
if (!this.boardConfig) {
this.restart();
return;
}
const { selectedBoard } = this.boardConfig;
if (selectedBoard && pkg.boards.some(board => Board.sameAs(board, selectedBoard))) {
this.restart();
}
}
this.boardsServiceClient.onBoardInstalled(({ pkg }) => restartIfAffected(pkg));
this.boardsServiceClient.onBoardUninstalled(({ pkg }) => restartIfAffected(pkg));
}
selectBoard(config: BoardsConfig.Config): void {

View File

@@ -1,52 +0,0 @@
import * as React from 'react';
import { injectable } from 'inversify';
import { Library } from '../../common/protocol/library-service';
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
@injectable()
export class LibraryItemRenderer extends ListItemRenderer<Library> {
renderItem(item: Library, install: (item: Library) => Promise<void>): React.ReactNode {
const name = <span className='name'>{item.name}</span>;
const author = <span className='author'>by {item.author}</span>;
const installedVersion = !!item.installedVersion && <div className='version-info'>
<span className='version'>Version {item.installedVersion}</span>
<span className='installed'>INSTALLED</span>
</div>;
const summary = <div className='summary'>{item.summary}</div>;
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
const installButton = item.installable && !item.installedVersion &&
<button className='install' onClick={install.bind(this, item)}>INSTALL</button>;
const versions = (() => {
const { availableVersions } = item;
if (!!item.installedVersion || availableVersions.length === 0) {
return undefined;
} else if (availableVersions.length === 1) {
return <label>{availableVersions[0]}</label>
} else {
return <select>{item.availableVersions.map(version => <option value={version} key={version}>{version}</option>)}</select>;
}
})();
return <div className='component-list-item noselect'>
<div className='header'>
<span>{name} {author}</span>
{installedVersion}
</div>
<div className='content'>
{summary}
</div>
<div className='info'>
{moreInfo}
</div>
<div className='footer'>
{installButton}
{versions}
</div>
</div>;
}
}

View File

@@ -1,7 +1,7 @@
import { inject, injectable } from 'inversify';
import { Library, LibraryService } from '../../common/protocol/library-service';
import { ListWidget } from '../components/component-list/list-widget';
import { LibraryItemRenderer } from './library-item-renderer';
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
@injectable()
export class LibraryListWidget extends ListWidget<Library> {
@@ -11,7 +11,7 @@ export class LibraryListWidget extends ListWidget<Library> {
constructor(
@inject(LibraryService) protected service: LibraryService,
@inject(LibraryItemRenderer) protected itemRenderer: LibraryItemRenderer) {
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<Library>) {
super({
id: LibraryListWidget.WIDGET_ID,

View File

@@ -1,53 +1,265 @@
import { injectable, inject } from "inversify";
import { MonitorService, ConnectionConfig } from "../../common/protocol/monitor-service";
import { Emitter, Event } from "@theia/core";
import { injectable, inject, postConstruct } from 'inversify';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { MessageService } from '@theia/core/lib/common/message-service';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { MonitorService, MonitorConfig, MonitorError, Status, MonitorReadEvent } from '../../common/protocol/monitor-service';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
import { Port, Board, BoardsService, AttachedSerialBoard, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
import { BoardsConfig } from '../boards/boards-config';
import { MonitorModel } from './monitor-model';
@injectable()
export class MonitorConnection {
@inject(MonitorModel)
protected readonly monitorModel: MonitorModel;
@inject(MonitorService)
protected readonly monitorService: MonitorService;
connectionId: string | undefined;
@inject(MonitorServiceClientImpl)
protected readonly monitorServiceClient: MonitorServiceClientImpl;
protected _connectionConfig: ConnectionConfig | undefined;
@inject(BoardsService)
protected readonly boardsService: BoardsService;
protected readonly onConnectionChangedEmitter = new Emitter<string | undefined>();
readonly onConnectionChanged: Event<string | undefined> = this.onConnectionChangedEmitter.event;
@inject(BoardsServiceClientImpl)
protected boardsServiceClient: BoardsServiceClientImpl;
get connectionConfig(): ConnectionConfig | undefined {
return this._connectionConfig;
@inject(MessageService)
protected messageService: MessageService;
@inject(FrontendApplicationStateService)
protected readonly applicationState: FrontendApplicationStateService;
protected state: MonitorConnection.State | undefined;
/**
* Note: The idea is to toggle this property from the UI (`Monitor` view)
* and the boards config and the boards attachment/detachment logic can be at on place, here.
*/
protected _autoConnect: boolean = false;
protected readonly onConnectionChangedEmitter = new Emitter<MonitorConnection.State | undefined>();
/**
* This emitter forwards all read events **iff** the connection is established.
*/
protected readonly onReadEmitter = new Emitter<MonitorReadEvent>();
/**
* Array for storing previous monitor errors received from the server, and based on the number of elements in this array,
* we adjust the reconnection delay.
* Super naive way: we wait `array.length * 1000` ms. Once we hit 10 errors, we do not try to reconnect and clean the array.
*/
protected monitorErrors: MonitorError[] = [];
protected reconnectTimeout?: number;
@postConstruct()
protected init(): void {
// Forward the messages from the board **iff** connected.
this.monitorServiceClient.onRead(event => {
if (this.connected) {
this.onReadEmitter.fire(event);
}
});
this.monitorServiceClient.onError(async error => {
let shouldReconnect = false;
if (this.state) {
const { code, config } = error;
const { board, port } = config;
const options = { timeout: 3000 };
switch (code) {
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
console.debug(`Connection was canceled by client: ${MonitorConnection.State.toString(this.state)}.`);
break;
}
case MonitorError.ErrorCodes.DEVICE_BUSY: {
this.messageService.warn(`Connection failed. Serial port is busy: ${Port.toString(port)}.`, options);
shouldReconnect = this.autoConnect;
this.monitorErrors.push(error);
break;
}
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
this.messageService.info(`Disconnected ${Board.toString(board, { useFqbn: false })} from ${Port.toString(port)}.`, options);
break;
}
case undefined: {
this.messageService.error(`Unexpected error. Reconnecting ${Board.toString(board)} on port ${Port.toString(port)}.`, options);
console.error(JSON.stringify(error));
shouldReconnect = this.connected && this.autoConnect;
break;
}
}
const oldState = this.state;
this.state = undefined;
this.onConnectionChangedEmitter.fire(this.state);
if (shouldReconnect) {
if (this.monitorErrors.length >= 10) {
this.messageService.warn(`Failed to reconnect ${Board.toString(board, { useFqbn: false })} to the the serial-monitor after 10 consecutive attempts. The ${Port.toString(port)} serial port is busy. after 10 consecutive attempts.`);
this.monitorErrors.length = 0;
} else {
const attempts = (this.monitorErrors.length || 1);
if (this.reconnectTimeout !== undefined) {
// Clear the previous timer.
window.clearTimeout(this.reconnectTimeout);
}
const timeout = attempts * 1000;
this.messageService.warn(`Reconnecting ${Board.toString(board, { useFqbn: false })} to ${Port.toString(port)} in ${attempts} seconds...`, { timeout });
this.reconnectTimeout = window.setTimeout(() => this.connect(oldState.config), timeout);
}
}
}
});
this.boardsServiceClient.onBoardsConfigChanged(this.handleBoardConfigChange.bind(this));
this.boardsServiceClient.onBoardsChanged(event => {
if (this.autoConnect && this.connected) {
const { boardsConfig } = this.boardsServiceClient;
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
const { attached } = AttachedBoardsChangeEvent.diff(event);
if (attached.boards.some(board => AttachedSerialBoard.is(board) && BoardsConfig.Config.sameAs(boardsConfig, board))) {
const { selectedBoard: board, selectedPort: port } = boardsConfig;
const { baudRate } = this.monitorModel;
this.disconnect()
.then(() => this.connect({ board, port, baudRate }));
}
}
}
});
// Handles the `baudRate` changes by reconnecting if required.
this.monitorModel.onChange(({ property }) => {
if (property === 'baudRate' && this.autoConnect && this.connected) {
const { boardsConfig } = this.boardsServiceClient;
this.handleBoardConfigChange(boardsConfig);
}
});
}
async connect(config: ConnectionConfig): Promise<string | undefined> {
if (this.connectionId) {
await this.disconnect();
}
const { connectionId } = await this.monitorService.connect(config);
this.connectionId = connectionId;
this._connectionConfig = config;
this.onConnectionChangedEmitter.fire(this.connectionId);
return connectionId;
get connected(): boolean {
return !!this.state;
}
async disconnect(): Promise<boolean> {
let result = true;
const connections = await this.monitorService.getConnectionIds();
if (this.connectionId && connections.findIndex(id => id === this.connectionId) >= 0) {
console.log('>>> Disposing existing monitor connection before establishing a new one...');
result = await this.monitorService.disconnect(this.connectionId);
if (!result) {
// TODO: better!!!
console.error(`Could not close connection: ${this.connectionId}. Check the backend logs.`);
} else {
console.log(`<<< Disposed ${this.connectionId} connection.`);
this.connectionId = undefined;
this._connectionConfig = undefined;
this.onConnectionChangedEmitter.fire(this.connectionId);
get monitorConfig(): MonitorConfig | undefined {
return this.state ? this.state.config : undefined;
}
get autoConnect(): boolean {
return this._autoConnect;
}
set autoConnect(value: boolean) {
const oldValue = this._autoConnect;
this._autoConnect = value;
// When we enable the auto-connect, we have to connect
if (!oldValue && value) {
// We have to make sure the previous boards config has been restored.
// Otherwise, we might start the auto-connection without configured boards.
this.applicationState.reachedState('started_contributions').then(() => {
const { boardsConfig } = this.boardsServiceClient;
this.handleBoardConfigChange(boardsConfig);
});
} else if (oldValue && !value) {
if (this.reconnectTimeout !== undefined) {
window.clearTimeout(this.reconnectTimeout);
this.monitorErrors.length = 0;
}
}
return result;
}
}
async connect(config: MonitorConfig): Promise<Status> {
if (this.connected) {
const disconnectStatus = await this.disconnect();
if (!Status.isOK(disconnectStatus)) {
return disconnectStatus;
}
}
console.info(`>>> Creating serial monitor connection for ${Board.toString(config.board)} on port ${Port.toString(config.port)}...`);
const connectStatus = await this.monitorService.connect(config);
if (Status.isOK(connectStatus)) {
this.state = { config };
console.info(`<<< Serial monitor connection created for ${Board.toString(config.board, { useFqbn: false })} on port ${Port.toString(config.port)}.`);
}
this.onConnectionChangedEmitter.fire(this.state);
return Status.isOK(connectStatus);
}
async disconnect(): Promise<Status> {
if (!this.state) { // XXX: we user `this.state` instead of `this.connected` to make the type checker happy.
return Status.OK;
}
console.log('>>> Disposing existing monitor connection...');
const status = await this.monitorService.disconnect();
if (Status.isOK(status)) {
console.log(`<<< Disposed connection. Was: ${MonitorConnection.State.toString(this.state)}`);
} else {
console.warn(`<<< Could not dispose connection. Activate connection: ${MonitorConnection.State.toString(this.state)}`);
}
this.state = undefined;
this.onConnectionChangedEmitter.fire(this.state);
return status;
}
/**
* Sends the data to the connected serial monitor.
* The desired EOL is appended to `data`, you do not have to add it.
* It is a NOOP if connected.
*/
async send(data: string): Promise<Status> {
if (!this.connected) {
return Status.NOT_CONNECTED;
}
return new Promise<Status>(resolve => {
this.monitorService.send(data + this.monitorModel.lineEnding)
.then(() => resolve(Status.OK));
});
}
get onConnectionChanged(): Event<MonitorConnection.State | undefined> {
return this.onConnectionChangedEmitter.event;
}
get onRead(): Event<MonitorReadEvent> {
return this.onReadEmitter.event;
}
protected async handleBoardConfigChange(boardsConfig: BoardsConfig.Config): Promise<void> {
if (this.autoConnect) {
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
// Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports.
// The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881
this.boardsService.getAvailablePorts().then(({ ports }) => {
if (ports.some(port => Port.equals(port, boardsConfig.selectedPort))) {
new Promise<void>(resolve => {
// First, disconnect if connected.
if (this.connected) {
this.disconnect().then(() => resolve());
return;
}
resolve();
}).then(() => {
// Then (re-)connect.
const { selectedBoard: board, selectedPort: port } = boardsConfig;
const { baudRate } = this.monitorModel;
this.connect({ board, port, baudRate });
});
}
});
}
}
}
}
export namespace MonitorConnection {
export interface State {
readonly config: MonitorConfig;
}
export namespace State {
export function toString(state: State): string {
const { config } = state;
const { board, port } = config;
return `${Board.toString(board)} ${Port.toString(port)}`;
}
}
}

View File

@@ -1,58 +1,119 @@
import { injectable } from "inversify";
import { Emitter } from "@theia/core";
export namespace MonitorModel {
export interface Data {
autoscroll: boolean,
timestamp: boolean,
baudRate: number,
lineEnding: string
}
}
import { injectable, inject } from 'inversify';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { MonitorConfig } from '../../common/protocol/monitor-service';
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
@injectable()
export class MonitorModel {
export class MonitorModel implements FrontendApplicationContribution {
protected readonly onChangeEmitter = new Emitter<void>();
protected static STORAGE_ID = 'arduino-monitor-model';
readonly onChange = this.onChangeEmitter.event;
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
protected _autoscroll: boolean = true;
protected _timestamp: boolean = false;
baudRate: number;
lineEnding: string = '\n';
@inject(BoardsServiceClientImpl)
protected readonly boardsServiceClient: BoardsServiceClientImpl;
protected readonly onChangeEmitter: Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>;
protected _autoscroll: boolean;
protected _timestamp: boolean;
protected _baudRate: MonitorConfig.BaudRate;
protected _lineEnding: MonitorModel.EOL;
constructor() {
this._autoscroll = true;
this._timestamp = false;
this._baudRate = MonitorConfig.BaudRate.DEFAULT;
this._lineEnding = MonitorModel.EOL.DEFAULT;
this.onChangeEmitter = new Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>();
}
onStart(): void {
this.localStorageService.getData<MonitorModel.State>(MonitorModel.STORAGE_ID).then(state => {
if (state) {
this.restoreState(state);
}
});
}
get onChange(): Event<MonitorModel.State.Change<keyof MonitorModel.State>> {
return this.onChangeEmitter.event;
}
get autoscroll(): boolean {
return this._autoscroll;
}
toggleAutoscroll(): void {
this._autoscroll = !this._autoscroll;
this.storeState();
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'autoscroll', value: this._autoscroll }));
}
get timestamp(): boolean {
return this._timestamp;
}
toggleAutoscroll(): void {
this._autoscroll = !this._autoscroll;
this.onChangeEmitter.fire(undefined);
}
toggleTimestamp(): void {
this._timestamp = !this._timestamp;
this.onChangeEmitter.fire(undefined);
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'timestamp', value: this._timestamp }));
}
restore(model: MonitorModel.Data) {
this._autoscroll = model.autoscroll;
this._timestamp = model.timestamp;
this.baudRate = model.baudRate;
this.lineEnding = model.lineEnding;
get baudRate(): MonitorConfig.BaudRate {
return this._baudRate;
}
store(): MonitorModel.Data {
return {
set baudRate(baudRate: MonitorConfig.BaudRate) {
this._baudRate = baudRate;
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'baudRate', value: this._baudRate }));
}
get lineEnding(): MonitorModel.EOL {
return this._lineEnding;
}
set lineEnding(lineEnding: MonitorModel.EOL) {
this._lineEnding = lineEnding;
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'lineEnding', value: this._lineEnding }));
}
protected restoreState(state: MonitorModel.State): void {
this._autoscroll = state.autoscroll;
this._timestamp = state.timestamp;
this._baudRate = state.baudRate;
this._lineEnding = state.lineEnding;
}
protected async storeState(): Promise<void> {
return this.localStorageService.setData(MonitorModel.STORAGE_ID, {
autoscroll: this._autoscroll,
timestamp: this._timestamp,
baudRate: this.baudRate,
lineEnding: this.lineEnding
baudRate: this._baudRate,
lineEnding: this._lineEnding
});
}
}
export namespace MonitorModel {
export interface State {
autoscroll: boolean;
timestamp: boolean;
baudRate: MonitorConfig.BaudRate;
lineEnding: EOL;
}
export namespace State {
export interface Change<K extends keyof State> {
readonly property: K;
readonly value: State[K];
}
}
}
export type EOL = '' | '\n' | '\r' | '\r\n';
export namespace EOL {
export const DEFAULT: EOL = '\n';
}
}

View File

@@ -12,8 +12,8 @@ export class MonitorServiceClientImpl implements MonitorServiceClient {
notifyRead(event: MonitorReadEvent): void {
this.onReadEmitter.fire(event);
const { connectionId, data } = event;
console.log(`Received data from ${connectionId}: ${data}`);
const { data } = event;
console.debug(`Received data: ${data}`);
}
notifyError(error: MonitorError): void {

View File

@@ -54,18 +54,18 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
}
}
async registerToolbarItems(registry: TabBarToolbarRegistry) {
registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem({
id: 'monitor-autoscroll',
render: () => this.renderAutoScrollButton(),
isVisible: widget => widget instanceof MonitorWidget,
onDidChange: this.model.onChange
onDidChange: this.model.onChange as any // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
});
registry.registerItem({
id: 'monitor-timestamp',
render: () => this.renderTimestampButton(),
isVisible: widget => widget instanceof MonitorWidget,
onDidChange: this.model.onChange
onDidChange: this.model.onChange as any // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
});
registry.registerItem({
id: SerialMonitor.Commands.CLEAR_OUTPUT.id,
@@ -80,7 +80,7 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
isVisible: widget => widget instanceof MonitorWidget,
execute: widget => {
if (widget instanceof MonitorWidget) {
widget.clear();
widget.clearConsole();
}
}
});
@@ -106,7 +106,7 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
}
protected readonly toggleAutoScroll = () => this.doToggleAutoScroll();
protected async doToggleAutoScroll() {
protected async doToggleAutoScroll(): Promise<void> {
this.model.toggleAutoscroll();
}
@@ -121,7 +121,8 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
}
protected readonly toggleTimestamp = () => this.doToggleTimestamp();
protected async doToggleTimestamp() {
protected async doToggleTimestamp(): Promise<void> {
this.model.toggleTimestamp();
}
}
}

View File

@@ -1,285 +1,117 @@
import { ReactWidget, Message, Widget, StatefulWidget } from "@theia/core/lib/browser";
import { postConstruct, injectable, inject } from "inversify";
import * as React from 'react';
import Select, { components } from 'react-select';
import { Styles } from "react-select/src/styles";
import { ThemeConfig } from "react-select/src/theme";
import { OptionsType } from "react-select/src/types";
import { MonitorServiceClientImpl } from "./monitor-service-client-impl";
import { MessageService } from "@theia/core";
import { ConnectionConfig, MonitorService } from "../../common/protocol/monitor-service";
import { MonitorConnection } from "./monitor-connection";
import { BoardsServiceClientImpl } from "../boards/boards-service-client-impl";
import { AttachedSerialBoard, BoardsService, Board } from "../../common/protocol/boards-service";
import { BoardsConfig } from "../boards/boards-config";
import { MonitorModel } from "./monitor-model";
export namespace SerialMonitorSendField {
export interface Props {
onSend: (text: string) => void
}
export interface State {
value: string;
}
}
export class SerialMonitorSendField extends React.Component<SerialMonitorSendField.Props, SerialMonitorSendField.State> {
protected inputField: HTMLInputElement | null;
constructor(props: SerialMonitorSendField.Props) {
super(props);
this.state = { value: '' };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
if (this.inputField) {
this.inputField.focus();
}
}
render() {
return <React.Fragment>
<form onSubmit={this.handleSubmit}>
<input
tabIndex={-1}
ref={ref => this.inputField = ref}
type='text' id='serial-monitor-send'
autoComplete='off'
value={this.state.value}
onChange={this.handleChange} />
<input className="btn" type="submit" value="Submit" />
</form>
</React.Fragment>
}
protected handleChange(event: React.ChangeEvent<HTMLInputElement>) {
this.setState({ value: event.target.value });
}
protected handleSubmit(event: React.FormEvent<HTMLFormElement>) {
this.props.onSend(this.state.value);
this.setState({ value: '' });
event.preventDefault();
}
}
export namespace SerialMonitorOutput {
export interface Props {
lines: string[];
model: MonitorModel;
}
}
export class SerialMonitorOutput extends React.Component<SerialMonitorOutput.Props> {
protected theEnd: HTMLDivElement | null;
render() {
let result = '';
const style: React.CSSProperties = {
whiteSpace: 'pre',
fontFamily: 'monospace',
};
for (const text of this.props.lines) {
result += text;
}
return <React.Fragment>
<div style={style}>{result}</div>
<div style={{ float: "left", clear: "both" }}
ref={(el) => { this.theEnd = el; }}>
</div>
</React.Fragment>;
}
protected scrollToBottom() {
if (this.theEnd) {
this.theEnd.scrollIntoView();
}
}
componentDidMount() {
if (this.props.model.autoscroll) {
this.scrollToBottom();
}
}
componentDidUpdate() {
if (this.props.model.autoscroll) {
this.scrollToBottom();
}
}
}
export interface SelectOption {
label: string;
value: string | number;
}
import * as dateFormat from 'dateformat';
import { postConstruct, injectable, inject } from 'inversify';
import { OptionsType } from 'react-select/src/types';
import { isOSX } from '@theia/core/lib/common/os';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
import { DisposableCollection } from '@theia/core/lib/common/disposable'
import { ReactWidget, Message, Widget, MessageLoop } from '@theia/core/lib/browser/widgets';
import { Board, Port } from '../../common/protocol/boards-service';
import { MonitorConfig } from '../../common/protocol/monitor-service';
import { ArduinoSelect } from '../components/arduino-select';
import { MonitorModel } from './monitor-model';
import { MonitorConnection } from './monitor-connection';
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
@injectable()
export class MonitorWidget extends ReactWidget implements StatefulWidget {
export class MonitorWidget extends ReactWidget {
static readonly ID = 'serial-monitor';
protected lines: string[];
protected tempData: string;
@inject(MonitorModel)
protected readonly monitorModel: MonitorModel;
@inject(MonitorConnection)
protected readonly monitorConnection: MonitorConnection;
@inject(MonitorServiceClientImpl)
protected readonly monitorServiceClient: MonitorServiceClientImpl;
protected widgetHeight: number;
protected continuePreviousConnection: boolean;
/**
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
*/
protected focusNode: HTMLElement | undefined;
/**
* Guard against re-rendering the view after the close was requested.
* See: https://github.com/eclipse-theia/theia/issues/6704
*/
protected closing = false;
protected readonly clearOutputEmitter = new Emitter<void>();
constructor(
@inject(MonitorServiceClientImpl) protected readonly serviceClient: MonitorServiceClientImpl,
@inject(MonitorConnection) protected readonly connection: MonitorConnection,
@inject(MonitorService) protected readonly monitorService: MonitorService,
@inject(BoardsServiceClientImpl) protected readonly boardsServiceClient: BoardsServiceClientImpl,
@inject(MessageService) protected readonly messageService: MessageService,
@inject(BoardsService) protected readonly boardsService: BoardsService,
@inject(MonitorModel) protected readonly model: MonitorModel
) {
constructor() {
super();
this.id = MonitorWidget.ID;
this.title.label = 'Serial Monitor';
this.title.iconClass = 'arduino-serial-monitor-tab-icon';
this.lines = [];
this.tempData = '';
this.title.closable = true;
this.scrollOptions = undefined;
this.toDisposeOnDetach.push(serviceClient.onRead(({ data, connectionId }) => {
this.tempData += data;
if (this.tempData.endsWith('\n')) {
if (this.model.timestamp) {
const nu = new Date();
const h = (100 + nu.getHours()).toString().substr(1)
const min = (100 + nu.getMinutes()).toString().substr(1)
const sec = (100 + nu.getSeconds()).toString().substr(1)
const ms = (1000 + nu.getMilliseconds()).toString().substr(1);
this.tempData = `${h}:${min}:${sec}.${ms} -> ` + this.tempData;
}
this.lines.push(this.tempData);
this.tempData = '';
this.update();
}
}));
// TODO onError
this.toDispose.push(this.clearOutputEmitter);
}
@postConstruct()
protected init(): void {
this.update();
this.toDispose.push(this.monitorConnection.onConnectionChanged(() => this.clearConsole()));
}
clear(): void {
this.lines = [];
clearConsole(): void {
this.clearOutputEmitter.fire(undefined);
this.update();
}
storeState(): MonitorModel.Data {
return this.model.store();
dispose(): void {
super.dispose();
}
restoreState(oldState: MonitorModel.Data): void {
this.model.restore(oldState);
}
protected onAfterAttach(msg: Message) {
protected onAfterAttach(msg: Message): void {
super.onAfterAttach(msg);
this.clear();
this.connect();
this.toDisposeOnDetach.push(
this.boardsServiceClient.onBoardsChanged(async states => {
const currentConnectionConfig = this.connection.connectionConfig;
const connectedBoard = states.newState.boards
.filter(AttachedSerialBoard.is)
.find(board => {
const potentiallyConnected = currentConnectionConfig && currentConnectionConfig.board;
if (AttachedSerialBoard.is(potentiallyConnected)) {
return Board.equals(board, potentiallyConnected) && board.port === potentiallyConnected.port;
}
return false;
});
if (connectedBoard && currentConnectionConfig) {
this.continuePreviousConnection = true;
this.connection.connect(currentConnectionConfig);
}
})
);
this.toDisposeOnDetach.push(
this.boardsServiceClient.onBoardsConfigChanged(async boardConfig => {
this.connect();
})
)
this.toDisposeOnDetach.push(this.connection.onConnectionChanged(() => {
if (!this.continuePreviousConnection) {
this.clear();
} else {
this.continuePreviousConnection = false;
}
}));
this.monitorConnection.autoConnect = true;
}
protected onBeforeDetach(msg: Message) {
super.onBeforeDetach(msg);
this.connection.disconnect();
onCloseRequest(msg: Message): void {
this.closing = true;
this.monitorConnection.autoConnect = false;
if (this.monitorConnection.connected) {
this.monitorConnection.disconnect();
}
super.onCloseRequest(msg);
}
protected onResize(msg: Widget.ResizeMessage) {
protected onUpdateRequest(msg: Message): void {
// TODO: `this.isAttached`
// See: https://github.com/eclipse-theia/theia/issues/6704#issuecomment-562574713
if (!this.closing && this.isAttached) {
super.onUpdateRequest(msg);
}
}
protected onResize(msg: Widget.ResizeMessage): void {
super.onResize(msg);
this.widgetHeight = msg.height;
this.update();
}
protected async connect() {
const config = await this.getConnectionConfig();
if (config) {
this.connection.connect(config);
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
(this.focusNode || this.node).focus();
}
protected async getConnectionConfig(): Promise<ConnectionConfig | undefined> {
const baudRate = this.model.baudRate;
const { boardsConfig } = this.boardsServiceClient;
const { selectedBoard, selectedPort } = boardsConfig;
if (!selectedBoard) {
this.messageService.warn('No boards selected.');
return;
}
const { name } = selectedBoard;
if (!selectedPort) {
this.messageService.warn(`No ports selected for board: '${name}'.`);
return;
}
const attachedBoards = await this.boardsService.getAttachedBoards();
const connectedBoard = attachedBoards.boards.filter(AttachedSerialBoard.is).find(board => BoardsConfig.Config.sameAs(boardsConfig, board));
if (!connectedBoard) {
return;
}
return {
baudRate,
board: selectedBoard,
port: selectedPort.address
}
protected onFocusResolved = (element: HTMLElement | undefined) => {
this.focusNode = element;
requestAnimationFrame(() => MessageLoop.sendMessage(this, Widget.Msg.ActivateRequest));
}
protected getLineEndings(): OptionsType<SelectOption> {
protected get lineEndings(): OptionsType<SelectOption<MonitorModel.EOL>> {
return [
{
label: 'No Line Ending',
value: ''
},
{
label: 'Newline',
label: 'New Line',
value: '\n'
},
{
@@ -290,109 +122,212 @@ export class MonitorWidget extends ReactWidget implements StatefulWidget {
label: 'Both NL & CR',
value: '\r\n'
}
]
];
}
protected getBaudRates(): OptionsType<SelectOption> {
const baudRates = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200];
return baudRates.map<SelectOption>(baudRate => ({ label: baudRate + ' baud', value: baudRate }))
protected get baudRates(): OptionsType<SelectOption<MonitorConfig.BaudRate>> {
const baudRates: Array<MonitorConfig.BaudRate> = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200];
return baudRates.map(baudRate => ({ label: baudRate + ' baud', value: baudRate }));
}
protected render(): React.ReactNode {
const le = this.getLineEndings();
const br = this.getBaudRates();
const leVal = this.model.lineEnding && le.find(val => val.value === this.model.lineEnding);
const brVal = this.model.baudRate && br.find(val => val.value === this.model.baudRate);
return <React.Fragment>
<div className='serial-monitor-container'>
<div className='head'>
<div className='send'>
<SerialMonitorSendField onSend={this.onSend} />
</div>
<div className='config'>
{this.renderSelectField('arduino-serial-monitor-line-endings', le, leVal || le[1], this.onChangeLineEnding)}
{this.renderSelectField('arduino-serial-monitor-baud-rates', br, brVal || br[4], this.onChangeBaudRate)}
</div>
const { baudRates, lineEndings } = this;
const lineEnding = lineEndings.find(item => item.value === this.monitorModel.lineEnding) || lineEndings[1]; // Defaults to `\n`.
const baudRate = baudRates.find(item => item.value === this.monitorModel.baudRate) || baudRates[4]; // Defaults to `9600`.
return <div className='serial-monitor'>
<div className='head'>
<div className='send'>
<SerialMonitorSendInput
monitorConfig={this.monitorConnection.monitorConfig}
resolveFocus={this.onFocusResolved}
onSend={this.onSend} />
</div>
<div id='serial-monitor-output-container'>
<SerialMonitorOutput model={this.model} lines={this.lines} />
<div className='config'>
<div className='select'>
<ArduinoSelect
maxMenuHeight={this.widgetHeight - 40}
options={lineEndings}
defaultValue={lineEnding}
onChange={this.onChangeLineEnding} />
</div>
<div className='select'>
<ArduinoSelect
className='select'
maxMenuHeight={this.widgetHeight - 40}
options={baudRates}
defaultValue={baudRate}
onChange={this.onChangeBaudRate} />
</div>
</div>
</div>
</React.Fragment>;
<div className='body'>
<SerialMonitorOutput
monitorModel={this.monitorModel}
monitorConnection={this.monitorConnection}
clearConsoleEvent={this.clearOutputEmitter.event} />
</div>
</div>;
}
protected readonly onSend = (value: string) => this.doSend(value);
protected async doSend(value: string) {
const { connectionId } = this.connection;
if (connectionId) {
this.monitorService.send(connectionId, value + this.model.lineEnding);
protected async doSend(value: string): Promise<void> {
this.monitorConnection.send(value);
}
protected readonly onChangeLineEnding = (option: SelectOption<MonitorModel.EOL>) => {
this.monitorModel.lineEnding = option.value;
}
protected readonly onChangeBaudRate = async (option: SelectOption<MonitorConfig.BaudRate>) => {
this.monitorModel.baudRate = option.value;
}
}
export namespace SerialMonitorSendInput {
export interface Props {
readonly monitorConfig?: MonitorConfig;
readonly onSend: (text: string) => void;
readonly resolveFocus: (element: HTMLElement | undefined) => void;
}
export interface State {
text: string;
}
}
export class SerialMonitorSendInput extends React.Component<SerialMonitorSendInput.Props, SerialMonitorSendInput.State> {
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
super(props);
this.state = { text: '' };
this.onChange = this.onChange.bind(this);
this.onSend = this.onSend.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}
render(): React.ReactNode {
return <input
ref={this.setRef}
type='text'
className={this.props.monitorConfig ? '' : 'warning'}
placeholder={this.placeholder}
value={this.state.text}
onChange={this.onChange}
onKeyDown={this.onKeyDown} />
}
protected get placeholder(): string {
const { monitorConfig } = this.props;
if (!monitorConfig) {
return 'Not connected. Select a board and a port to connect automatically.'
}
const { board, port } = monitorConfig;
return `Message (${isOSX ? '⌘' : 'Ctrl'}+Enter to send message to '${Board.toString(board, { useFqbn: false })}' on '${Port.toString(port)}')`;
}
protected setRef = (element: HTMLElement | null) => {
if (this.props.resolveFocus) {
this.props.resolveFocus(element || undefined);
}
}
protected readonly onChangeLineEnding = (le: SelectOption) => {
this.model.lineEnding = typeof le.value === 'string' ? le.value : '\n';
protected onChange(event: React.ChangeEvent<HTMLInputElement>): void {
this.setState({ text: event.target.value });
}
protected readonly onChangeBaudRate = async (br: SelectOption) => {
await this.connection.disconnect();
this.model.baudRate = typeof br.value === 'number' ? br.value : 9600;
this.clear();
const config = await this.getConnectionConfig();
if (config) {
await this.connection.connect(config);
}
protected onSend(): void {
this.props.onSend(this.state.text);
this.setState({ text: '' });
}
protected renderSelectField(id: string, options: OptionsType<SelectOption>, defaultVal: SelectOption, onChange: (v: SelectOption) => void): React.ReactNode {
const height = 25;
const selectStyles: Styles = {
control: (provided, state) => ({
...provided,
width: 200,
border: 'none'
}),
dropdownIndicator: (p, s) => ({
...p,
padding: 0
}),
indicatorSeparator: (p, s) => ({
display: 'none'
}),
indicatorsContainer: (p, s) => ({
padding: '0 5px'
}),
menu: (p, s) => ({
...p,
marginTop: 0
})
};
const theme: ThemeConfig = theme => ({
...theme,
borderRadius: 0,
spacing: {
controlHeight: height,
baseUnit: 2,
menuGutter: 4
protected onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
const keyCode = KeyCode.createKeyCode(event.nativeEvent);
if (keyCode) {
const { key, meta, ctrl } = keyCode;
if (key === Key.ENTER && ((isOSX && meta) || (!isOSX && ctrl))) {
this.onSend();
}
});
const DropdownIndicator = (
props: React.Props<typeof components.DropdownIndicator>
) => {
return (
<span className='fa fa-caret-down caret'></span>
);
};
return <Select
options={options}
defaultValue={defaultVal}
onChange={onChange}
components={{ DropdownIndicator }}
theme={theme}
styles={selectStyles}
maxMenuHeight={this.widgetHeight - 40}
classNamePrefix='sms'
className='serial-monitor-select'
id={id}
/>
}
}
}
}
export namespace SerialMonitorOutput {
export interface Props {
readonly monitorModel: MonitorModel;
readonly monitorConnection: MonitorConnection;
readonly clearConsoleEvent: Event<void>;
}
export interface State {
content: string;
timestamp: boolean;
}
}
export class SerialMonitorOutput extends React.Component<SerialMonitorOutput.Props, SerialMonitorOutput.State> {
/**
* Do not touch it. It is used to be able to "follow" the serial monitor log.
*/
protected anchor: HTMLElement | null;
protected toDisposeBeforeUnmount = new DisposableCollection();
constructor(props: Readonly<SerialMonitorOutput.Props>) {
super(props);
this.state = { content: '', timestamp: this.props.monitorModel.timestamp };
}
render(): React.ReactNode {
return <React.Fragment>
<div style={({ whiteSpace: 'pre', fontFamily: 'monospace' })}>
{this.state.content}
</div>
<div style={{ float: 'left', clear: 'both' }} ref={element => { this.anchor = element; }} />
</React.Fragment>;
}
componentDidMount(): void {
this.scrollToBottom();
let chunk = '';
this.toDisposeBeforeUnmount.pushAll([
this.props.monitorConnection.onRead(({ data }) => {
chunk += data;
const eolIndex = chunk.indexOf('\n');
if (eolIndex !== -1) {
const line = chunk.substring(0, eolIndex + 1);
chunk = chunk.slice(eolIndex + 1);
const content = `${this.state.content}${this.state.timestamp ? `${dateFormat(new Date(), 'H:M:ss.l')} -> ` : ''}${line}`;
this.setState({ content });
}
}),
this.props.clearConsoleEvent(() => this.setState({ content: '' })),
this.props.monitorModel.onChange(({ property }) => {
if (property === 'timestamp') {
const { timestamp } = this.props.monitorModel;
this.setState({ timestamp });
}
})
]);
}
componentDidUpdate(): void {
this.scrollToBottom();
}
componentWillUnmount(): void {
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
this.toDisposeBeforeUnmount.dispose();
}
protected scrollToBottom(): void {
if (this.props.monitorModel.autoscroll && this.anchor) {
this.anchor.scrollIntoView();
}
}
}
export interface SelectOption<T> {
readonly label: string;
readonly value: T;
}

View File

@@ -0,0 +1,24 @@
import { injectable } from 'inversify';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { ShellLayoutRestorer } from '@theia/core/lib/browser/shell/shell-layout-restorer';
@injectable()
export class ArduinoShellLayoutRestorer extends ShellLayoutRestorer {
// Workaround for https://github.com/eclipse-theia/theia/issues/6579.
async storeLayoutAsync(app: FrontendApplication): Promise<void> {
if (this.shouldStoreLayout) {
try {
this.logger.info('>>> Storing the layout...');
const layoutData = app.shell.getLayoutData();
const serializedLayoutData = this.deflate(layoutData);
await this.storageService.setData(this.storageKey, serializedLayoutData);
this.logger.info('<<< The layout has been successfully stored.');
} catch (error) {
await this.storageService.setData(this.storageKey, undefined);
this.logger.error('Error during serialization of layout data', error);
}
}
}
}

View File

@@ -0,0 +1,45 @@
.arduino-select__control {
border: var(--theia-layout-color2) var(--theia-border-width) solid !important;
background: var(--theia-layout-color2) !important;
}
.arduino-select__control:hover {
border: var(--theia-layout-color2) var(--theia-border-width) solid !important;
}
.arduino-select__control--is-focused {
box-shadow: none !important;
}
.arduino-select__option--is-selected {
background-color: var(--theia-accent-color3) !important;
color: var(--theia-content-font-color0) !important;
border-color: var(--theia-accent-color3) !important;
}
.arduino-select__option--is-focused {
background-color: var(--theia-accent-color4) !important;
border-color: var(--theia-accent-color3) !important;
}
.arduino-select__menu {
background-color: var(--theia-layout-color2) !important;
border: 1px solid var(--theia-accent-color3) !important;
top: auto !important; /* to align the top of the menu with the bottom of the control */
box-shadow: none !important;
}
.arduino-select__control.arduino-select__control--menu-is-open {
border: 1px solid !important;
border-color: var(--theia-accent-color3) !important;
border-bottom-color: var(--theia-layout-color2) !important; /* if the bottom border color has the same color as the background of the control, we make the border "invisible" */
}
.arduino-select__value-container .arduino-select__single-value {
color: var(--theia-ui-font-color1) !important;
}
.arduino-select__menu-list {
padding-top: 0 !important;
padding-bottom: 0 !important;
}

View File

@@ -35,7 +35,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i{
#select-board-dialog .selectBoardContainer .search input,
#select-board-dialog .selectBoardContainer .list,
#select-board-dialog .selectBoardContainer .list {
background: white; /* TODO find a theia color instead! */
background: var(--theia-layout-color0);
}
#select-board-dialog .selectBoardContainer .body .search input {

View File

@@ -2,4 +2,42 @@
@import './board-select-dialog.css';
@import './main.css';
@import './editor.css';
@import './monitor.css';
@import './monitor.css';
@import './arduino-select.css';
input:focus {
outline-width: 1px;
outline-style: solid;
outline-offset: -1px;
opacity: 1 !important;
outline-color: var(--theia-accent-color3);
}
input.warning:focus {
outline-width: 1px;
outline-style: solid;
outline-offset: -1px;
opacity: 1 !important;
color: var(--theia-warn-font-color0);
background-color: var(--theia-warn-color0);
}
input.warning {
background-color: var(--theia-warn-color0);
}
input.warning::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: var(--theia-warn-font-color0);
background-color: var(--theia-warn-color0);
opacity: 1; /* Firefox */
}
input.warning:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: var(--theia-warn-font-color0);
background-color: var(--theia-warn-color0);
}
input.warning::-ms-input-placeholder { /* Microsoft Edge */
color: var(--theia-warn-font-color0);
background-color: var(--theia-warn-color0);
}

View File

@@ -1,6 +1,6 @@
.library-tab-icon {
-webkit-mask: url('library-tab-icon.svg');
mask: url('library-tab-icon.svg');
-webkit-mask: url('../icons/library-tab-icon.svg');
mask: url('../icons/library-tab-icon.svg');
}
.arduino-list-widget {
@@ -9,7 +9,6 @@
.arduino-list-widget .search-bar {
margin: 0px 10px 10px 15px;
border-color: var(--theia-border-color3);
}
.arduino-list-widget .search-filters {
@@ -61,6 +60,14 @@
background-color: var(--theia-layout-color2);
}
/* 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 .ps__rail-y > .ps__thumb-y {
z-index: 1;
}
.component-list-item {
padding: 10px 10px 10px 15px;
font-size: var(--theia-ui-font-size1);
@@ -108,8 +115,9 @@
color: var(--theia-ui-font-color2);
}
.component-list-item .header .installed {
.component-list-item .header .installed:before {
margin-left: 4px;
display: inline-block;
justify-self: end;
background-color: var(--theia-accent-color1);
padding: 2px 4px 2px 4px;
@@ -117,6 +125,13 @@
font-weight: bold;
max-height: calc(1em + 4px);
color: var(--theia-inverse-ui-font-color0);
content: 'INSTALLED';
}
.component-list-item .header .installed:hover:before {
background-color: var(--theia-inverse-ui-font-color0);
color: var(--theia-accent-color1);
content: 'UNINSTALL';
}
.component-list-item[min-width~="170px"] .footer {

View File

@@ -5,63 +5,46 @@
background-position-x: 19px;
}
.serial-monitor-container {
.serial-monitor {
height: 100%;
display: flex;
flex-direction: column;
}
.serial-monitor-container .head {
.serial-monitor .head {
display: flex;
padding: 5px;
background: var(--theia-layout-color0);
height: 27px;
}
.serial-monitor-container .head .send {
.serial-monitor .head .send {
display: flex;
flex:1;
}
.serial-monitor-container .head .send .btn {
display: flex;
padding: 0 5px;
align-items: center;
color: var(--theia-ui-font-color1);
opacity: 0.7;
}
.serial-monitor-container .head .send .btn:hover {
opacity: 1;
cursor: pointer;
}
.serial-monitor-container .head .send form {
flex: 1;
display: flex;
background: var(--theia-layout-color2);
margin-right: 2px;
}
.serial-monitor-container .head .send input#serial-monitor-send {
color: var(--theia-ui-font-color1);
flex: 1;
.serial-monitor .head .send > input {
line-height: var(--theia-content-line-height);
width: 100%;
}
.serial-monitor-container .head .send input:focus {
outline: none;
.serial-monitor .head .send > input:focus {
border-color: var(--theia-accent-color3);
}
.serial-monitor-container .head .config {
.serial-monitor .head .config {
display: flex;
}
.serial-monitor-container .head .config .serial-monitor-select {
margin-left: 5px;
.serial-monitor .head .config .select {
margin-left: 3px;
}
#serial-monitor-output-container {
.serial-monitor .body {
overflow: auto;
flex: 1;
padding: 6px;
padding: 5px;
}
.p-TabBar-toolbar .item.arduino-monitor {
@@ -78,49 +61,3 @@
.p-TabBar-toolbar .item .clear-all {
background: var(--theia-icon-clear) no-repeat;
}
/* React Select Styles */
.serial-monitor-select .sms__control {
border: var(--theia-border-color1) var(--theia-border-width) solid;
background: var(--theia-layout-color2);
}
.serial-monitor-select .sms__control--is-focused {
border-color: var(--theia-border-color2) !important;
box-shadow: none !important;
}
.sms__control--is-focused:hover {
border-color: var(--theia-border-color2) !important;
}
.serial-monitor-select .sms__option--is-selected {
background-color: var(--theia-ui-button-color-secondary-hover);
color: var(--theia-content-font-color0);
}
.serial-monitor-select .sms__option--is-focused {
background-color: var(--theia-ui-button-color-secondary-hover);
}
.serial-monitor-select .sms__menu {
background-color: var(--theia-layout-color1);
border: 1px solid var(--theia-border-color2);
border-top: none;
box-shadow: none;
}
.serial-monitor-select .sms__control.sms__control--menu-is-open {
border: 1px solid;
border-color: var(--theia-border-color2) !important;
border-bottom: none;
}
.serial-monitor-select .sms__value-container .sms__single-value {
color: var(--theia-ui-font-color1);
}
.sms__menu-list {
padding-top: 0 !important;
padding-bottom: 0 !important;
}

View File

@@ -13,11 +13,11 @@ export class ToolOutputServiceClientImpl implements ToolOutputServiceClient {
protected readonly outputContribution: OutputContribution;
onNewOutput(tool: string, chunk: string): void {
this.outputContribution.openView({ reveal: true }).then(() => {
this.outputContribution.openView({ activate: true }).then(() => {
const channel = this.outputChannelManager.getChannel(`Arduino: ${tool}`);
channel.setVisibility(true);
channel.append(chunk);
})
});
}
}
}

View File

@@ -1,3 +1,4 @@
import { Installable } from './installable';
export interface ArduinoComponent {
readonly name: string;
@@ -6,8 +7,8 @@ export interface ArduinoComponent {
readonly description: string;
readonly moreInfoLink?: string;
readonly availableVersions: string[];
readonly availableVersions: Installable.Version[];
readonly installable: boolean;
readonly installedVersion?: string;
readonly installedVersion?: Installable.Version;
}

View File

@@ -46,10 +46,15 @@ export interface BoardInstalledEvent {
readonly pkg: Readonly<BoardPackage>;
}
export interface BoardUninstalledEvent {
readonly pkg: Readonly<BoardPackage>;
}
export const BoardsServiceClient = Symbol('BoardsServiceClient');
export interface BoardsServiceClient {
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void;
notifyBoardInstalled(event: BoardInstalledEvent): void
notifyBoardUninstalled(event: BoardUninstalledEvent): void
}
export const BoardsServicePath = '/services/boards-service';
@@ -130,7 +135,7 @@ export namespace Port {
}
if (isOSX) {
// Example: `/dev/cu.usbmodem14401`
if (/(tty|cu)\..*/.test(address.substring('/dev/'.length))) {
if (/(tty|cu)\..*/.test(address.substring('/dev/'.length))) {
return [
'/dev/cu.MALS',
'/dev/cu.SOC',
@@ -209,6 +214,11 @@ export namespace Board {
return !!board.fqbn;
}
export function toString(board: Board, options: { useFqbn: boolean } = { useFqbn: true }): string {
const fqbn = options && options.useFqbn && board.fqbn ? ` [${board.fqbn}]` : '';
return `${board.name}${fqbn}`;
}
}
export interface AttachedSerialBoard extends Board {

View File

@@ -1,3 +1,23 @@
export interface Installable<T> {
install(item: T): Promise<void>;
}
const naturalCompare: (left: string, right: string) => number = require('string-natural-compare').caseInsensitive;
import { ArduinoComponent } from './arduino-component';
export interface Installable<T extends ArduinoComponent> {
/**
* If `options.version` is specified, that will be installed. Otherwise, `item.availableVersions[0]`.
*/
install(options: { item: T, version?: Installable.Version }): Promise<void>;
/**
* Uninstalls the given component. It is a NOOP if not installed.
*/
uninstall(options: { item: T }): Promise<void>;
}
export namespace Installable {
export type Version = string;
export namespace Version {
/**
* Most recent version comes first, then the previous versions. (`1.8.1`, `1.6.3`, `1.6.2`, `1.6.1` and so on.)
*/
export const COMPARATOR = (left: Version, right: Version) => naturalCompare(right, left);
}
}

View File

@@ -5,14 +5,9 @@ import { ArduinoComponent } from './arduino-component';
export const LibraryServicePath = '/services/library-service';
export const LibraryService = Symbol('LibraryService');
export interface LibraryService extends Installable<Library>, Searchable<Library> {
install(library: Library): Promise<void>;
install(options: { item: Library, version?: Installable.Version }): Promise<void>;
}
export interface Library extends ArduinoComponent {
readonly builtIn?: boolean;
}
export namespace Library {
// TODO: figure out whether we need a dedicated `version` type.
export type Version = string;
}

View File

@@ -1,14 +1,52 @@
import { JsonRpcServer } from '@theia/core';
import { Board } from './boards-service';
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { Board, Port } from './boards-service';
export interface MonitorError {
export interface Status { }
export interface OK extends Status { }
export interface ErrorStatus extends Status {
readonly message: string;
readonly code: number
}
export namespace Status {
export function isOK(status: Status & { message?: string }): status is OK {
return typeof status.message !== 'string';
}
export const OK: OK = {};
export const NOT_CONNECTED: ErrorStatus = { message: 'Not connected.' };
export const ALREADY_CONNECTED: ErrorStatus = { message: 'Already connected.' };
}
export interface MonitorReadEvent {
readonly connectionId: string;
readonly data: string;
export const MonitorServicePath = '/services/serial-monitor';
export const MonitorService = Symbol('MonitorService');
export interface MonitorService extends JsonRpcServer<MonitorServiceClient> {
connect(config: MonitorConfig): Promise<Status>;
disconnect(): Promise<Status>;
send(data: string | Uint8Array): Promise<Status>;
}
export interface MonitorConfig {
readonly board: Board;
readonly port: Port;
/**
* Defaults to [`SERIAL`](MonitorConfig#ConnectionType#SERIAL).
*/
readonly type?: MonitorConfig.ConnectionType;
/**
* Defaults to `9600`.
*/
readonly baudRate?: MonitorConfig.BaudRate;
}
export namespace MonitorConfig {
export type BaudRate = 300 | 1200 | 2400 | 4800 | 9600 | 19200 | 38400 | 57600 | 115200;
export namespace BaudRate {
export const DEFAULT: BaudRate = 9600;
}
export enum ConnectionType {
SERIAL = 0
}
}
export const MonitorServiceClient = Symbol('MonitorServiceClient');
@@ -17,28 +55,31 @@ export interface MonitorServiceClient {
notifyError(event: MonitorError): void;
}
export const MonitorServicePath = '/services/serial-monitor';
export const MonitorService = Symbol('MonitorService');
export interface MonitorService extends JsonRpcServer<MonitorServiceClient> {
connect(config: ConnectionConfig): Promise<{ connectionId: string }>;
disconnect(connectionId: string): Promise<boolean>;
send(connectionId: string, data: string | Uint8Array): Promise<void>;
getConnectionIds(): Promise<string[]>;
export interface MonitorReadEvent {
readonly data: string;
}
export interface ConnectionConfig {
readonly board: Board;
readonly port: string;
export interface MonitorError {
readonly message: string;
/**
* Defaults to [`SERIAL`](ConnectionType#SERIAL).
* If no `code` is available, clients must reestablish the serial-monitor connection.
*/
readonly type?: ConnectionType;
/**
* Defaults to `9600`.
*/
readonly baudRate?: number;
readonly code: number | undefined;
readonly config: MonitorConfig;
}
export enum ConnectionType {
SERIAL = 0
export namespace MonitorError {
export namespace ErrorCodes {
/**
* The frontend has refreshed the browser, for instance.
*/
export const CLIENT_CANCEL = 1;
/**
* When detaching a physical device when the duplex channel is still opened.
*/
export const DEVICE_NOT_CONFIGURED = 2;
/**
* Another serial monitor was opened on this port. For another electron-instance, Java IDE.
*/
export const DEVICE_BUSY = 3;
}
}

View File

@@ -8,10 +8,11 @@ export interface SketchesService {
getSketches(uri?: string): Promise<Sketch[]>
getSketchFiles(uri: string): Promise<string[]>
/**
* Creates a new sketch folder in the `parentUri` location. If `parentUri` is not specified,
* it falls back to the default `sketchDirUri` from the CLI.
* Creates a new sketch folder in the `parentUri` location.
* Normally, `parentUri` is the client's workspace root, or the default `sketchDirUri` from the CLI.
* Note, `parentUri` and `sketchDirUri` can be the same.
*/
createNewSketch(parentUri?: string): Promise<Sketch>
createNewSketch(parentUri: string): Promise<Sketch>
isSketchFolder(uri: string): Promise<boolean>
}

View File

@@ -38,10 +38,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ArduinoCliContribution).toSelf().inSingletonScope();
bind(CliContribution).toService(ArduinoCliContribution);
// Provides the path of the Ardunio CLI.
// Provides the path of the Arduino CLI.
bind(ArduinoCli).toSelf().inSingletonScope();
// Shared daemonn
// Shared daemon
bind(ArduinoDaemon).toSelf().inSingletonScope();
bind(BackendApplicationContribution).toService(ArduinoDaemon);

View File

@@ -1,7 +1,8 @@
import * as os from 'os';
import * as which from 'which';
import * as semver from 'semver';
import { spawn } from 'child_process';
import { join, delimiter } from 'path';
import { join } from 'path';
import { injectable, inject } from 'inversify';
import { ILogger } from '@theia/core';
import { FileUri } from '@theia/core/lib/node/file-uri';
@@ -13,43 +14,34 @@ export class ArduinoCli {
@inject(ILogger)
protected logger: ILogger;
private execPath: string | undefined;
async getExecPath(): Promise<string> {
const build = join(__dirname, '..', '..', 'build');
return new Promise<string>((resolve, reject) => {
which(`arduino-cli${os.platform() === 'win32' ? '.exe' : ''}`, { path: `${process.env.PATH}${delimiter}${build}` }, (err, path) => {
if (err) {
reject(err);
return;
}
resolve(path);
});
});
if (this.execPath) {
return this.execPath;
}
const version = /\d+\.\d+\.\d+/;
const cli = `arduino-cli${os.platform() === 'win32' ? '.exe' : ''}`;
const buildCli = join(__dirname, '..', '..', 'build', cli);
const buildVersion = await this.spawn(`"${buildCli}"`, ['version']);
const buildShortVersion = (buildVersion.match(version) || [])[0];
this.execPath = buildCli;
const pathCli = await new Promise<string | undefined>(resolve => which(cli, (error, path) => resolve(error ? undefined : path)));
if (!pathCli) {
return buildCli;
}
const pathVersion = await this.spawn(`"${pathCli}"`, ['version']);
const pathShortVersion = (pathVersion.match(version) || [])[0];
if (semver.gt(pathShortVersion, buildShortVersion)) {
this.execPath = pathCli;
return pathCli;
}
return buildCli;
}
async getVersion(): Promise<string> {
const execPath = await this.getExecPath();
return this.spawn(`"${execPath}"`, ['version']);
return new Promise<string>((resolve, reject) => {
const buffers: Buffer[] = [];
const cp = spawn(`"${execPath}"`, ['version'], { windowsHide: true, shell: true });
cp.stdout.on('data', (b: Buffer) => buffers.push(b));
cp.on('error', error => reject(error));
cp.on('exit', (code, signal) => {
if (code === 0) {
const result = Buffer.concat(buffers).toString('utf8').trim()
resolve(result);
return;
}
if (signal) {
reject(new Error(`Process exited with signal: ${signal}`));
return;
}
if (code) {
reject(new Error(`Process exited with exit code: ${code}`));
return;
}
});
});
}
async getDefaultConfig(): Promise<Config> {

View File

@@ -1,15 +1,30 @@
import * as PQueue from 'p-queue';
import { injectable, inject, postConstruct, named } from 'inversify';
import { ILogger } from '@theia/core/lib/common/logger';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { BoardsService, AttachedSerialBoard, BoardPackage, Board, AttachedNetworkBoard, BoardsServiceClient, Port } from '../common/protocol/boards-service';
import { PlatformSearchReq, PlatformSearchResp, PlatformInstallReq, PlatformInstallResp, PlatformListReq, PlatformListResp } from './cli-protocol/commands/core_pb';
import {
PlatformSearchReq,
PlatformSearchResp,
PlatformInstallReq,
PlatformInstallResp,
PlatformListReq,
PlatformListResp,
Platform,
PlatformUninstallReq,
PlatformUninstallResp
} from './cli-protocol/commands/core_pb';
import { CoreClientProvider } from './core-client-provider';
import { BoardListReq, BoardListResp } from './cli-protocol/commands/board_pb';
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
import { Installable } from '../common/protocol/installable';
@injectable()
export class BoardsServiceImpl implements BoardsService {
@inject(ILogger)
protected logger: ILogger;
@inject(ILogger)
@named('discovery')
protected discoveryLogger: ILogger;
@@ -21,14 +36,15 @@ export class BoardsServiceImpl implements BoardsService {
protected readonly toolOutputService: ToolOutputServiceServer;
protected discoveryInitialized = false;
protected discoveryTimer: NodeJS.Timeout | undefined;
protected discoveryTimer: NodeJS.Timer | undefined;
/**
* Poor man's serial discovery:
* Stores the state of the currently discovered, attached boards.
* This state is updated via periodical polls.
* Stores the state of the currently discovered and attached boards.
* This state is updated via periodical polls. If there diff, a change event will be sent out to the frontend.
*/
protected _attachedBoards: { boards: Board[] } = { boards: [] };
protected _availablePorts: { ports: Port[] } = { ports: [] };
protected attachedBoards: { boards: Board[] } = { boards: [] };
protected availablePorts: { ports: Port[] } = { ports: [] };
protected started = new Deferred<void>();
protected client: BoardsServiceClient | undefined;
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
@@ -38,8 +54,8 @@ export class BoardsServiceImpl implements BoardsService {
this.discoveryLogger.trace('Discovering attached boards and available ports...');
this.doGetAttachedBoardsAndAvailablePorts().then(({ boards, ports }) => {
const update = (oldBoards: Board[], newBoards: Board[], oldPorts: Port[], newPorts: Port[], message: string) => {
this._attachedBoards = { boards: newBoards };
this._availablePorts = { ports: newPorts };
this.attachedBoards = { boards: newBoards };
this.availablePorts = { ports: newPorts };
this.discoveryLogger.info(`${message} - Discovered boards: ${JSON.stringify(newBoards)} and available ports: ${JSON.stringify(newPorts)}`);
if (this.client) {
this.client.notifyAttachedBoardsChanged({
@@ -60,6 +76,7 @@ export class BoardsServiceImpl implements BoardsService {
if (!this.discoveryInitialized) {
update([], sortedBoards, [], sortedPorts, 'Initialized attached boards and available ports.');
this.discoveryInitialized = true;
this.started.resolve();
} else {
Promise.all([
this.getAttachedBoards(),
@@ -95,17 +112,24 @@ export class BoardsServiceImpl implements BoardsService {
}
dispose(): void {
this.logger.info('>>> Disposing boards service...');
this.queue.pause();
this.queue.clear();
if (this.discoveryTimer !== undefined) {
clearInterval(this.discoveryTimer);
}
this.logger.info('<<< Disposed boards service.');
this.client = undefined;
}
async getAttachedBoards(): Promise<{ boards: Board[] }> {
return this._attachedBoards;
await this.started.promise;
return this.attachedBoards;
}
async getAvailablePorts(): Promise<{ ports: Port[] }> {
return this._availablePorts;
await this.started.promise;
return this.availablePorts;
}
private async doGetAttachedBoardsAndAvailablePorts(): Promise<{ boards: Board[], ports: Port[] }> {
@@ -207,35 +231,76 @@ export class BoardsServiceImpl implements BoardsService {
const req = new PlatformSearchReq();
req.setSearchArgs(options.query || "");
req.setAllVersions(true);
req.setInstance(instance);
const resp = await new Promise<PlatformSearchResp>((resolve, reject) => client.platformSearch(req, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp)));
let items = resp.getSearchOutputList().map(item => {
const packages = new Map<string, BoardPackage>();
const toPackage = (platform: Platform) => {
let installedVersion: string | undefined;
const matchingPlatform = installedPlatforms.find(ip => ip.getId() === item.getId());
const matchingPlatform = installedPlatforms.find(ip => ip.getId() === platform.getId());
if (!!matchingPlatform) {
installedVersion = matchingPlatform.getInstalled();
}
const result: BoardPackage = {
id: item.getId(),
name: item.getName(),
author: item.getMaintainer(),
availableVersions: [item.getLatest()],
description: item.getBoardsList().map(b => b.getName()).join(", "),
return {
id: platform.getId(),
name: platform.getName(),
author: platform.getMaintainer(),
availableVersions: [platform.getLatest()],
description: platform.getBoardsList().map(b => b.getName()).join(", "),
installable: true,
summary: "Boards included in this package:",
installedVersion,
boards: item.getBoardsList().map(b => <Board>{ name: b.getName(), fqbn: b.getFqbn() }),
moreInfoLink: item.getWebsite()
boards: platform.getBoardsList().map(b => <Board>{ name: b.getName(), fqbn: b.getFqbn() }),
moreInfoLink: platform.getWebsite()
}
return result;
});
}
return { items };
// We must group the cores by ID, and sort platforms by, first the installed version, then version alphabetical order.
// Otherwise we lose the FQBN information.
const groupedById: Map<string, Platform[]> = new Map();
for (const platform of resp.getSearchOutputList()) {
const id = platform.getId();
if (groupedById.has(id)) {
groupedById.get(id)!.push(platform);
} else {
groupedById.set(id, [platform]);
}
}
const installedAwareVersionComparator = (left: Platform, right: Platform) => {
// XXX: we cannot rely on `platform.getInstalled()`, it is always an empty string.
const leftInstalled = !!installedPlatforms.find(ip => ip.getId() === left.getId() && ip.getInstalled() === left.getLatest());
const rightInstalled = !!installedPlatforms.find(ip => ip.getId() === right.getId() && ip.getInstalled() === right.getLatest());
if (leftInstalled && !rightInstalled) {
return -1;
}
if (!leftInstalled && rightInstalled) {
return 1;
}
return Installable.Version.COMPARATOR(right.getLatest(), left.getLatest()); // Higher version comes first.
}
for (const id of groupedById.keys()) {
groupedById.get(id)!.sort(installedAwareVersionComparator);
}
for (const id of groupedById.keys()) {
for (const platform of groupedById.get(id)!) {
const id = platform.getId();
const pkg = packages.get(id);
if (pkg) {
pkg.availableVersions.push(platform.getLatest());
pkg.availableVersions.sort(Installable.Version.COMPARATOR);
} else {
packages.set(id, toPackage(platform));
}
}
}
return { items: [...packages.values()] };
}
async install(pkg: BoardPackage): Promise<void> {
async install(options: { item: BoardPackage, version?: Installable.Version }): Promise<void> {
const pkg = options.item;
const version = !!options.version ? options.version : pkg.availableVersions[0];
const coreClient = await this.coreClientProvider.getClient();
if (!coreClient) {
return;
@@ -248,7 +313,7 @@ export class BoardsServiceImpl implements BoardsService {
req.setInstance(instance);
req.setArchitecture(boardName);
req.setPlatformPackage(platform);
req.setVersion(pkg.availableVersions[0]);
req.setVersion(version);
console.info("Starting board installation", pkg);
const resp = client.platformInstall(req);
@@ -268,4 +333,38 @@ export class BoardsServiceImpl implements BoardsService {
console.info("Board installation done", pkg);
}
async uninstall(options: { item: BoardPackage }): Promise<void> {
const pkg = options.item;
const coreClient = await this.coreClientProvider.getClient();
if (!coreClient) {
return;
}
const { client, instance } = coreClient;
const [platform, boardName] = pkg.id.split(":");
const req = new PlatformUninstallReq();
req.setInstance(instance);
req.setArchitecture(boardName);
req.setPlatformPackage(platform);
console.info("Starting board uninstallation", pkg);
let logged = false;
const resp = client.platformUninstall(req);
resp.on('data', (_: PlatformUninstallResp) => {
if (!logged) {
this.toolOutputService.publishNewOutput("board uninstall", `uninstalling ${pkg.id}\n`)
logged = true;
}
})
await new Promise<void>((resolve, reject) => {
resp.on('end', resolve);
resp.on('error', reject);
});
if (this.client) {
this.client.notifyBoardUninstalled({ pkg });
}
console.info("Board uninstallation done", pkg);
}
}

View File

@@ -2,6 +2,7 @@
// file: commands/board.proto
/* tslint:disable */
/* eslint-disable */
import * as jspb from "google-protobuf";
import * as commands_common_pb from "../commands/common_pb";

View File

@@ -2,6 +2,7 @@
// file: commands/commands.proto
/* tslint:disable */
/* eslint-disable */
import * as grpc from "@grpc/grpc-js";
import * as commands_commands_pb from "../commands/commands_pb";
@@ -35,6 +36,7 @@ interface IArduinoCoreService extends grpc.ServiceDefinition<grpc.UntypedService
libraryInstall: IArduinoCoreService_ILibraryInstall;
libraryUninstall: IArduinoCoreService_ILibraryUninstall;
libraryUpgradeAll: IArduinoCoreService_ILibraryUpgradeAll;
libraryResolveDependencies: IArduinoCoreService_ILibraryResolveDependencies;
librarySearch: IArduinoCoreService_ILibrarySearch;
libraryList: IArduinoCoreService_ILibraryList;
}
@@ -237,6 +239,15 @@ interface IArduinoCoreService_ILibraryUpgradeAll extends grpc.MethodDefinition<c
responseSerialize: grpc.serialize<commands_lib_pb.LibraryUpgradeAllResp>;
responseDeserialize: grpc.deserialize<commands_lib_pb.LibraryUpgradeAllResp>;
}
interface IArduinoCoreService_ILibraryResolveDependencies extends grpc.MethodDefinition<commands_lib_pb.LibraryResolveDependenciesReq, commands_lib_pb.LibraryResolveDependenciesResp> {
path: string; // "/cc.arduino.cli.commands.ArduinoCore/LibraryResolveDependencies"
requestStream: boolean; // false
responseStream: boolean; // false
requestSerialize: grpc.serialize<commands_lib_pb.LibraryResolveDependenciesReq>;
requestDeserialize: grpc.deserialize<commands_lib_pb.LibraryResolveDependenciesReq>;
responseSerialize: grpc.serialize<commands_lib_pb.LibraryResolveDependenciesResp>;
responseDeserialize: grpc.deserialize<commands_lib_pb.LibraryResolveDependenciesResp>;
}
interface IArduinoCoreService_ILibrarySearch extends grpc.MethodDefinition<commands_lib_pb.LibrarySearchReq, commands_lib_pb.LibrarySearchResp> {
path: string; // "/cc.arduino.cli.commands.ArduinoCore/LibrarySearch"
requestStream: boolean; // false
@@ -281,6 +292,7 @@ export interface IArduinoCoreServer {
libraryInstall: grpc.handleServerStreamingCall<commands_lib_pb.LibraryInstallReq, commands_lib_pb.LibraryInstallResp>;
libraryUninstall: grpc.handleServerStreamingCall<commands_lib_pb.LibraryUninstallReq, commands_lib_pb.LibraryUninstallResp>;
libraryUpgradeAll: grpc.handleServerStreamingCall<commands_lib_pb.LibraryUpgradeAllReq, commands_lib_pb.LibraryUpgradeAllResp>;
libraryResolveDependencies: grpc.handleUnaryCall<commands_lib_pb.LibraryResolveDependenciesReq, commands_lib_pb.LibraryResolveDependenciesResp>;
librarySearch: grpc.handleUnaryCall<commands_lib_pb.LibrarySearchReq, commands_lib_pb.LibrarySearchResp>;
libraryList: grpc.handleUnaryCall<commands_lib_pb.LibraryListReq, commands_lib_pb.LibraryListResp>;
}
@@ -338,6 +350,9 @@ export interface IArduinoCoreClient {
libraryUninstall(request: commands_lib_pb.LibraryUninstallReq, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<commands_lib_pb.LibraryUninstallResp>;
libraryUpgradeAll(request: commands_lib_pb.LibraryUpgradeAllReq, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<commands_lib_pb.LibraryUpgradeAllResp>;
libraryUpgradeAll(request: commands_lib_pb.LibraryUpgradeAllReq, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<commands_lib_pb.LibraryUpgradeAllResp>;
libraryResolveDependencies(request: commands_lib_pb.LibraryResolveDependenciesReq, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibraryResolveDependenciesResp) => void): grpc.ClientUnaryCall;
libraryResolveDependencies(request: commands_lib_pb.LibraryResolveDependenciesReq, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibraryResolveDependenciesResp) => void): grpc.ClientUnaryCall;
libraryResolveDependencies(request: commands_lib_pb.LibraryResolveDependenciesReq, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibraryResolveDependenciesResp) => void): grpc.ClientUnaryCall;
librarySearch(request: commands_lib_pb.LibrarySearchReq, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibrarySearchResp) => void): grpc.ClientUnaryCall;
librarySearch(request: commands_lib_pb.LibrarySearchReq, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibrarySearchResp) => void): grpc.ClientUnaryCall;
librarySearch(request: commands_lib_pb.LibrarySearchReq, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibrarySearchResp) => void): grpc.ClientUnaryCall;
@@ -400,6 +415,9 @@ export class ArduinoCoreClient extends grpc.Client implements IArduinoCoreClient
public libraryUninstall(request: commands_lib_pb.LibraryUninstallReq, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<commands_lib_pb.LibraryUninstallResp>;
public libraryUpgradeAll(request: commands_lib_pb.LibraryUpgradeAllReq, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<commands_lib_pb.LibraryUpgradeAllResp>;
public libraryUpgradeAll(request: commands_lib_pb.LibraryUpgradeAllReq, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<commands_lib_pb.LibraryUpgradeAllResp>;
public libraryResolveDependencies(request: commands_lib_pb.LibraryResolveDependenciesReq, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibraryResolveDependenciesResp) => void): grpc.ClientUnaryCall;
public libraryResolveDependencies(request: commands_lib_pb.LibraryResolveDependenciesReq, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibraryResolveDependenciesResp) => void): grpc.ClientUnaryCall;
public libraryResolveDependencies(request: commands_lib_pb.LibraryResolveDependenciesReq, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibraryResolveDependenciesResp) => void): grpc.ClientUnaryCall;
public librarySearch(request: commands_lib_pb.LibrarySearchReq, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibrarySearchResp) => void): grpc.ClientUnaryCall;
public librarySearch(request: commands_lib_pb.LibrarySearchReq, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibrarySearchResp) => void): grpc.ClientUnaryCall;
public librarySearch(request: commands_lib_pb.LibrarySearchReq, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: commands_lib_pb.LibrarySearchResp) => void): grpc.ClientUnaryCall;

View File

@@ -248,6 +248,28 @@ function deserialize_cc_arduino_cli_commands_LibraryListResp(buffer_arg) {
return commands_lib_pb.LibraryListResp.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_LibraryResolveDependenciesReq(arg) {
if (!(arg instanceof commands_lib_pb.LibraryResolveDependenciesReq)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.LibraryResolveDependenciesReq');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_LibraryResolveDependenciesReq(buffer_arg) {
return commands_lib_pb.LibraryResolveDependenciesReq.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_LibraryResolveDependenciesResp(arg) {
if (!(arg instanceof commands_lib_pb.LibraryResolveDependenciesResp)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.LibraryResolveDependenciesResp');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_LibraryResolveDependenciesResp(buffer_arg) {
return commands_lib_pb.LibraryResolveDependenciesResp.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_LibrarySearchReq(arg) {
if (!(arg instanceof commands_lib_pb.LibrarySearchReq)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.LibrarySearchReq');
@@ -810,6 +832,17 @@ var ArduinoCoreService = exports.ArduinoCoreService = {
responseSerialize: serialize_cc_arduino_cli_commands_LibraryUpgradeAllResp,
responseDeserialize: deserialize_cc_arduino_cli_commands_LibraryUpgradeAllResp,
},
libraryResolveDependencies: {
path: '/cc.arduino.cli.commands.ArduinoCore/LibraryResolveDependencies',
requestStream: false,
responseStream: false,
requestType: commands_lib_pb.LibraryResolveDependenciesReq,
responseType: commands_lib_pb.LibraryResolveDependenciesResp,
requestSerialize: serialize_cc_arduino_cli_commands_LibraryResolveDependenciesReq,
requestDeserialize: deserialize_cc_arduino_cli_commands_LibraryResolveDependenciesReq,
responseSerialize: serialize_cc_arduino_cli_commands_LibraryResolveDependenciesResp,
responseDeserialize: deserialize_cc_arduino_cli_commands_LibraryResolveDependenciesResp,
},
librarySearch: {
path: '/cc.arduino.cli.commands.ArduinoCore/LibrarySearch',
requestStream: false,

View File

@@ -2,6 +2,7 @@
// file: commands/commands.proto
/* tslint:disable */
/* eslint-disable */
import * as jspb from "google-protobuf";
import * as commands_common_pb from "../commands/common_pb";

View File

@@ -2,6 +2,7 @@
// file: commands/common.proto
/* tslint:disable */
/* eslint-disable */
import * as jspb from "google-protobuf";

View File

@@ -2,6 +2,7 @@
// file: commands/compile.proto
/* tslint:disable */
/* eslint-disable */
import * as jspb from "google-protobuf";
import * as commands_common_pb from "../commands/common_pb";

View File

@@ -2,6 +2,7 @@
// file: commands/core.proto
/* tslint:disable */
/* eslint-disable */
import * as jspb from "google-protobuf";
import * as commands_common_pb from "../commands/common_pb";
@@ -262,6 +263,9 @@ export class PlatformSearchReq extends jspb.Message {
getSearchArgs(): string;
setSearchArgs(value: string): void;
getAllVersions(): boolean;
setAllVersions(value: boolean): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): PlatformSearchReq.AsObject;
@@ -277,6 +281,7 @@ export namespace PlatformSearchReq {
export type AsObject = {
instance?: commands_common_pb.Instance.AsObject,
searchArgs: string,
allVersions: boolean,
}
}

View File

@@ -1705,7 +1705,8 @@ proto.cc.arduino.cli.commands.PlatformSearchReq.prototype.toObject = function(op
proto.cc.arduino.cli.commands.PlatformSearchReq.toObject = function(includeInstance, msg) {
var f, obj = {
instance: (f = msg.getInstance()) && commands_common_pb.Instance.toObject(includeInstance, f),
searchArgs: jspb.Message.getFieldWithDefault(msg, 2, "")
searchArgs: jspb.Message.getFieldWithDefault(msg, 2, ""),
allVersions: jspb.Message.getFieldWithDefault(msg, 3, false)
};
if (includeInstance) {
@@ -1751,6 +1752,10 @@ proto.cc.arduino.cli.commands.PlatformSearchReq.deserializeBinaryFromReader = fu
var value = /** @type {string} */ (reader.readString());
msg.setSearchArgs(value);
break;
case 3:
var value = /** @type {boolean} */ (reader.readBool());
msg.setAllVersions(value);
break;
default:
reader.skipField();
break;
@@ -1795,6 +1800,13 @@ proto.cc.arduino.cli.commands.PlatformSearchReq.serializeBinaryToWriter = functi
f
);
}
f = message.getAllVersions();
if (f) {
writer.writeBool(
3,
f
);
}
};
@@ -1843,6 +1855,23 @@ proto.cc.arduino.cli.commands.PlatformSearchReq.prototype.setSearchArgs = functi
};
/**
* optional bool all_versions = 3;
* Note that Boolean fields may be set to 0/1 when serialized from a Java server.
* You should avoid comparisons like {@code val === true/false} in those cases.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.PlatformSearchReq.prototype.getAllVersions = function() {
return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 3, false));
};
/** @param {boolean} value */
proto.cc.arduino.cli.commands.PlatformSearchReq.prototype.setAllVersions = function(value) {
jspb.Message.setProto3BooleanField(this, 3, value);
};
/**
* Generated by JsPbCodeGenerator.

View File

@@ -2,6 +2,7 @@
// file: commands/lib.proto
/* tslint:disable */
/* eslint-disable */
import * as jspb from "google-protobuf";
import * as commands_common_pb from "../commands/common_pb";
@@ -236,6 +237,90 @@ export namespace LibraryUpgradeAllResp {
}
}
export class LibraryResolveDependenciesReq extends jspb.Message {
hasInstance(): boolean;
clearInstance(): void;
getInstance(): commands_common_pb.Instance | undefined;
setInstance(value?: commands_common_pb.Instance): void;
getName(): string;
setName(value: string): void;
getVersion(): string;
setVersion(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): LibraryResolveDependenciesReq.AsObject;
static toObject(includeInstance: boolean, msg: LibraryResolveDependenciesReq): LibraryResolveDependenciesReq.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: LibraryResolveDependenciesReq, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): LibraryResolveDependenciesReq;
static deserializeBinaryFromReader(message: LibraryResolveDependenciesReq, reader: jspb.BinaryReader): LibraryResolveDependenciesReq;
}
export namespace LibraryResolveDependenciesReq {
export type AsObject = {
instance?: commands_common_pb.Instance.AsObject,
name: string,
version: string,
}
}
export class LibraryResolveDependenciesResp extends jspb.Message {
clearDependenciesList(): void;
getDependenciesList(): Array<LibraryDependencyStatus>;
setDependenciesList(value: Array<LibraryDependencyStatus>): void;
addDependencies(value?: LibraryDependencyStatus, index?: number): LibraryDependencyStatus;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): LibraryResolveDependenciesResp.AsObject;
static toObject(includeInstance: boolean, msg: LibraryResolveDependenciesResp): LibraryResolveDependenciesResp.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: LibraryResolveDependenciesResp, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): LibraryResolveDependenciesResp;
static deserializeBinaryFromReader(message: LibraryResolveDependenciesResp, reader: jspb.BinaryReader): LibraryResolveDependenciesResp;
}
export namespace LibraryResolveDependenciesResp {
export type AsObject = {
dependenciesList: Array<LibraryDependencyStatus.AsObject>,
}
}
export class LibraryDependencyStatus extends jspb.Message {
getName(): string;
setName(value: string): void;
getVersionrequired(): string;
setVersionrequired(value: string): void;
getVersioninstalled(): string;
setVersioninstalled(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): LibraryDependencyStatus.AsObject;
static toObject(includeInstance: boolean, msg: LibraryDependencyStatus): LibraryDependencyStatus.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: LibraryDependencyStatus, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): LibraryDependencyStatus;
static deserializeBinaryFromReader(message: LibraryDependencyStatus, reader: jspb.BinaryReader): LibraryDependencyStatus;
}
export namespace LibraryDependencyStatus {
export type AsObject = {
name: string,
versionrequired: string,
versioninstalled: string,
}
}
export class LibrarySearchReq extends jspb.Message {
hasInstance(): boolean;

View File

@@ -16,6 +16,7 @@ goog.object.extend(proto, commands_common_pb);
goog.exportSymbol('proto.cc.arduino.cli.commands.DownloadResource', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.InstalledLibrary', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.Library', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryDependencyStatus', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryDownloadReq', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryDownloadResp', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryInstallReq', null, global);
@@ -25,6 +26,8 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryListReq', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryListResp', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryLocation', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryRelease', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibrarySearchReq', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibrarySearchResp', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.LibraryUninstallReq', null, global);
@@ -1555,6 +1558,583 @@ proto.cc.arduino.cli.commands.LibraryUpgradeAllResp.prototype.hasTaskProgress =
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.displayName = 'proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.toObject = function(includeInstance, msg) {
var f, obj = {
instance: (f = msg.getInstance()) && commands_common_pb.Instance.toObject(includeInstance, f),
name: jspb.Message.getFieldWithDefault(msg, 2, ""),
version: jspb.Message.getFieldWithDefault(msg, 3, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq;
return proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = new commands_common_pb.Instance;
reader.readMessage(value,commands_common_pb.Instance.deserializeBinaryFromReader);
msg.setInstance(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setName(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setVersion(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getInstance();
if (f != null) {
writer.writeMessage(
1,
f,
commands_common_pb.Instance.serializeBinaryToWriter
);
}
f = message.getName();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getVersion();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
};
/**
* optional Instance instance = 1;
* @return {?proto.cc.arduino.cli.commands.Instance}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.getInstance = function() {
return /** @type{?proto.cc.arduino.cli.commands.Instance} */ (
jspb.Message.getWrapperField(this, commands_common_pb.Instance, 1));
};
/** @param {?proto.cc.arduino.cli.commands.Instance|undefined} value */
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.setInstance = function(value) {
jspb.Message.setWrapperField(this, 1, value);
};
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.clearInstance = function() {
this.setInstance(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.hasInstance = function() {
return jspb.Message.getField(this, 1) != null;
};
/**
* optional string name = 2;
* @return {string}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.getName = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/** @param {string} value */
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.setName = function(value) {
jspb.Message.setProto3StringField(this, 2, value);
};
/**
* optional string version = 3;
* @return {string}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.getVersion = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
};
/** @param {string} value */
proto.cc.arduino.cli.commands.LibraryResolveDependenciesReq.prototype.setVersion = function(value) {
jspb.Message.setProto3StringField(this, 3, value);
};
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.repeatedFields_, null);
};
goog.inherits(proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.displayName = 'proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp';
}
/**
* List of repeated fields within this message type.
* @private {!Array<number>}
* @const
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.repeatedFields_ = [1];
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.toObject = function(includeInstance, msg) {
var f, obj = {
dependenciesList: jspb.Message.toObjectList(msg.getDependenciesList(),
proto.cc.arduino.cli.commands.LibraryDependencyStatus.toObject, includeInstance)
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp;
return proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = new proto.cc.arduino.cli.commands.LibraryDependencyStatus;
reader.readMessage(value,proto.cc.arduino.cli.commands.LibraryDependencyStatus.deserializeBinaryFromReader);
msg.addDependencies(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getDependenciesList();
if (f.length > 0) {
writer.writeRepeatedMessage(
1,
f,
proto.cc.arduino.cli.commands.LibraryDependencyStatus.serializeBinaryToWriter
);
}
};
/**
* repeated LibraryDependencyStatus dependencies = 1;
* @return {!Array<!proto.cc.arduino.cli.commands.LibraryDependencyStatus>}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.prototype.getDependenciesList = function() {
return /** @type{!Array<!proto.cc.arduino.cli.commands.LibraryDependencyStatus>} */ (
jspb.Message.getRepeatedWrapperField(this, proto.cc.arduino.cli.commands.LibraryDependencyStatus, 1));
};
/** @param {!Array<!proto.cc.arduino.cli.commands.LibraryDependencyStatus>} value */
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.prototype.setDependenciesList = function(value) {
jspb.Message.setRepeatedWrapperField(this, 1, value);
};
/**
* @param {!proto.cc.arduino.cli.commands.LibraryDependencyStatus=} opt_value
* @param {number=} opt_index
* @return {!proto.cc.arduino.cli.commands.LibraryDependencyStatus}
*/
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.prototype.addDependencies = function(opt_value, opt_index) {
return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.cc.arduino.cli.commands.LibraryDependencyStatus, opt_index);
};
proto.cc.arduino.cli.commands.LibraryResolveDependenciesResp.prototype.clearDependenciesList = function() {
this.setDependenciesList([]);
};
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.LibraryDependencyStatus, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.cc.arduino.cli.commands.LibraryDependencyStatus.displayName = 'proto.cc.arduino.cli.commands.LibraryDependencyStatus';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.LibraryDependencyStatus.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.LibraryDependencyStatus} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus.toObject = function(includeInstance, msg) {
var f, obj = {
name: jspb.Message.getFieldWithDefault(msg, 1, ""),
versionrequired: jspb.Message.getFieldWithDefault(msg, 2, ""),
versioninstalled: jspb.Message.getFieldWithDefault(msg, 3, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.LibraryDependencyStatus}
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.LibraryDependencyStatus;
return proto.cc.arduino.cli.commands.LibraryDependencyStatus.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.LibraryDependencyStatus} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.LibraryDependencyStatus}
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setName(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setVersionrequired(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setVersioninstalled(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.LibraryDependencyStatus.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.LibraryDependencyStatus} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getName();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
f = message.getVersionrequired();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getVersioninstalled();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
};
/**
* optional string name = 1;
* @return {string}
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus.prototype.getName = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/** @param {string} value */
proto.cc.arduino.cli.commands.LibraryDependencyStatus.prototype.setName = function(value) {
jspb.Message.setProto3StringField(this, 1, value);
};
/**
* optional string versionRequired = 2;
* @return {string}
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus.prototype.getVersionrequired = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/** @param {string} value */
proto.cc.arduino.cli.commands.LibraryDependencyStatus.prototype.setVersionrequired = function(value) {
jspb.Message.setProto3StringField(this, 2, value);
};
/**
* optional string versionInstalled = 3;
* @return {string}
*/
proto.cc.arduino.cli.commands.LibraryDependencyStatus.prototype.getVersioninstalled = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
};
/** @param {string} value */
proto.cc.arduino.cli.commands.LibraryDependencyStatus.prototype.setVersioninstalled = function(value) {
jspb.Message.setProto3StringField(this, 3, value);
};
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a

View File

@@ -2,6 +2,7 @@
// file: commands/upload.proto
/* tslint:disable */
/* eslint-disable */
import * as jspb from "google-protobuf";
import * as commands_common_pb from "../commands/common_pb";

View File

@@ -2,6 +2,7 @@
// file: monitor/monitor.proto
/* tslint:disable */
/* eslint-disable */
import * as grpc from "@grpc/grpc-js";
import * as monitor_monitor_pb from "../monitor/monitor_pb";

View File

@@ -2,6 +2,7 @@
// file: monitor/monitor.proto
/* tslint:disable */
/* eslint-disable */
import * as jspb from "google-protobuf";
import * as google_protobuf_struct_pb from "google-protobuf/google/protobuf/struct_pb";

View File

@@ -42,7 +42,24 @@ export class CoreClientProviderImpl implements CoreClientProvider {
async getClient(workspaceRootOrResourceUri?: string): Promise<Client | undefined> {
return this.clientRequestQueue.add(() => new Promise<Client | undefined>(async resolve => {
const roots = await this.workspaceServiceExt.roots();
let roots = undefined;
try {
roots = await this.workspaceServiceExt.roots();
} catch (e) {
if (e instanceof Error && e.message === 'Connection got disposed.') {
console.info('The frontend has already disconnected.');
// Ignore it for now: https://github.com/eclipse-theia/theia/issues/6499
// Client has disconnected, and the server still runs the serial board poll.
// The poll requires the client's workspace roots, but the client has disconnected :/
} else {
throw e;
}
}
if (!roots) {
resolve(undefined);
return
}
if (!workspaceRootOrResourceUri) {
resolve(this.getOrCreateClient(roots[0]));
return;

View File

@@ -1,9 +1,20 @@
import { injectable, inject } from 'inversify';
import { Library, LibraryService } from '../common/protocol/library-service';
import { CoreClientProvider } from './core-client-provider';
import { LibrarySearchReq, LibrarySearchResp, LibraryListReq, LibraryListResp, LibraryRelease,
InstalledLibrary, LibraryInstallReq, LibraryInstallResp } from './cli-protocol/commands/lib_pb';
import {
LibrarySearchReq,
LibrarySearchResp,
LibraryListReq,
LibraryListResp,
LibraryRelease,
InstalledLibrary,
LibraryInstallReq,
LibraryInstallResp,
LibraryUninstallReq,
LibraryUninstallResp
} from './cli-protocol/commands/lib_pb';
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
import { Installable } from '../common/protocol/installable';
@injectable()
export class LibraryServiceImpl implements LibraryService {
@@ -43,6 +54,8 @@ export class LibraryServiceImpl implements LibraryService {
.filter(item => !!item.getLatest())
.slice(0, 50)
.map(item => {
// TODO: This seems to contain only the latest item instead of all of the items.
const availableVersions = item.getReleasesMap().getEntryList().map(([key, _]) => key).sort(Installable.Version.COMPARATOR);
let installedVersion: string | undefined;
const installed = installedLibsIdx.get(item.getName());
if (installed) {
@@ -51,14 +64,16 @@ export class LibraryServiceImpl implements LibraryService {
return toLibrary({
name: item.getName(),
installable: true,
installedVersion
}, item.getLatest()!)
installedVersion,
}, item.getLatest()!, availableVersions)
})
return { items };
}
async install(library: Library): Promise<void> {
async install(options: { item: Library, version?: Installable.Version }): Promise<void> {
const library = options.item;
const version = !!options.version ? options.version : library.availableVersions[0];
const coreClient = await this.coreClientProvider.getClient();
if (!coreClient) {
return;
@@ -68,7 +83,7 @@ export class LibraryServiceImpl implements LibraryService {
const req = new LibraryInstallReq();
req.setInstance(instance);
req.setName(library.name);
req.setVersion(library.availableVersions[0]);
req.setVersion(version);
const resp = client.libraryInstall(req);
resp.on('data', (r: LibraryInstallResp) => {
@@ -83,16 +98,43 @@ export class LibraryServiceImpl implements LibraryService {
});
}
async uninstall(options: { item: Library }): Promise<void> {
const library = options.item;
const coreClient = await this.coreClientProvider.getClient();
if (!coreClient) {
return;
}
const { client, instance } = coreClient;
const req = new LibraryUninstallReq();
req.setInstance(instance);
req.setName(library.name);
req.setVersion(library.installedVersion!);
let logged = false;
const resp = client.libraryUninstall(req);
resp.on('data', (_: LibraryUninstallResp) => {
if (!logged) {
this.toolOutputService.publishNewOutput("library uninstall", `uninstalling ${library.name}:${library.installedVersion}%\n`)
logged = true;
}
});
await new Promise<void>((resolve, reject) => {
resp.on('end', resolve);
resp.on('error', reject);
});
}
}
function toLibrary(tpl: Partial<Library>, release: LibraryRelease): Library {
function toLibrary(tpl: Partial<Library>, release: LibraryRelease, availableVersions: string[]): Library {
return {
name: "",
installable: false,
...tpl,
author: release.getAuthor(),
availableVersions: [release.getVersion()],
availableVersions,
description: release.getSentence(),
moreInfoLink: release.getWebsite(),
summary: release.getParagraph()

View File

@@ -1,40 +1,37 @@
import { v4 } from 'uuid';
import * as grpc from '@grpc/grpc-js';
import { ClientDuplexStream } from '@grpc/grpc-js';
import { TextDecoder, TextEncoder } from 'util';
import { injectable, inject, named } from 'inversify';
import { ILogger, Disposable, DisposableCollection } from '@theia/core';
import { MonitorService, MonitorServiceClient, ConnectionConfig, ConnectionType } from '../../common/protocol/monitor-service';
import { StreamingOpenReq, StreamingOpenResp, MonitorConfig } from '../cli-protocol/monitor/monitor_pb';
import { Struct } from 'google-protobuf/google/protobuf/struct_pb';
import { ILogger } from '@theia/core/lib/common/logger';
import { MonitorService, MonitorServiceClient, MonitorConfig, MonitorError, Status } from '../../common/protocol/monitor-service';
import { StreamingOpenReq, StreamingOpenResp, MonitorConfig as GrpcMonitorConfig } from '../cli-protocol/monitor/monitor_pb';
import { MonitorClientProvider } from './monitor-client-provider';
import * as google_protobuf_struct_pb from "google-protobuf/google/protobuf/struct_pb";
import { Board, Port } from '../../common/protocol/boards-service';
export interface MonitorDuplex {
readonly toDispose: Disposable;
readonly duplex: grpc.ClientDuplexStream<StreamingOpenReq, StreamingOpenResp>;
interface ErrorWithCode extends Error {
readonly code: number;
}
type ErrorCode = { code: number };
type MonitorError = Error & ErrorCode;
namespace MonitorError {
export function is(error: Error & Partial<ErrorCode>): error is MonitorError {
namespace ErrorWithCode {
export function toMonitorError(error: Error, config: MonitorConfig): MonitorError {
const { message } = error;
let code = undefined;
if (is(error)) {
// TODO: const `mapping`. Use regex for the `message`.
const mapping = new Map<string, number>();
mapping.set('1 CANCELLED: Cancelled on client', MonitorError.ErrorCodes.CLIENT_CANCEL);
mapping.set('2 UNKNOWN: device not configured', MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED);
mapping.set('2 UNKNOWN: error opening serial monitor: Serial port busy', MonitorError.ErrorCodes.DEVICE_BUSY);
code = mapping.get(message);
}
return {
message,
code,
config
};
}
function is(error: Error & { code?: number }): error is ErrorWithCode {
return typeof error.code === 'number';
}
/**
* The frontend has refreshed the browser, for instance.
*/
export function isClientCancelledError(error: MonitorError): boolean {
return error.code === 1 && error.message === 'Cancelled on client';
}
/**
* When detaching a physical device when the duplex channel is still opened.
*/
export function isDeviceNotConfiguredError(error: MonitorError): boolean {
return error.code === 2 && error.message === 'device not configured';
}
}
@injectable()
@@ -48,124 +45,111 @@ export class MonitorServiceImpl implements MonitorService {
protected readonly monitorClientProvider: MonitorClientProvider;
protected client?: MonitorServiceClient;
protected readonly connections = new Map<string, MonitorDuplex>();
protected connection?: { duplex: ClientDuplexStream<StreamingOpenReq, StreamingOpenResp>, config: MonitorConfig };
setClient(client: MonitorServiceClient | undefined): void {
this.client = client;
}
dispose(): void {
for (const [connectionId, duplex] of this.connections.entries()) {
this.doDisconnect(connectionId, duplex);
this.logger.info('>>> Disposing monitor service...');
if (this.connection) {
this.disconnect();
}
this.logger.info('<<< Disposed monitor service.');
this.client = undefined;
}
async getConnectionIds(): Promise<string[]> {
return Array.from(this.connections.keys());
}
async connect(config: ConnectionConfig): Promise<{ connectionId: string }> {
async connect(config: MonitorConfig): Promise<Status> {
this.logger.info(`>>> Creating serial monitor connection for ${Board.toString(config.board)} on port ${Port.toString(config.port)}...`);
if (this.connection) {
return Status.ALREADY_CONNECTED;
}
const client = await this.monitorClientProvider.client;
const duplex = client.streamingOpen();
const connectionId = v4();
const toDispose = new DisposableCollection(
Disposable.create(() => this.disconnect(connectionId))
);
this.connection = { duplex, config };
duplex.on('error', ((error: Error) => {
if (MonitorError.is(error) && (
MonitorError.isClientCancelledError(error)
|| MonitorError.isDeviceNotConfiguredError(error)
)) {
const monitorError = ErrorWithCode.toMonitorError(error, config);
this.disconnect(monitorError).then(() => {
if (this.client) {
this.client.notifyError(error);
this.client.notifyError(monitorError);
}
}
this.logger.error(`Error occurred for connection ${connectionId}.`, error);
toDispose.dispose();
if (monitorError.code === undefined) {
// Log the original, unexpected error.
this.logger.error(error);
}
});
}).bind(this));
duplex.on('data', ((resp: StreamingOpenResp) => {
if (this.client) {
const raw = resp.getData();
const data = typeof raw === 'string' ? raw : new TextDecoder('utf8').decode(raw);
this.client.notifyRead({ connectionId, data });
this.client.notifyRead({ data });
}
}).bind(this));
const { type, port } = config;
const req = new StreamingOpenReq();
const monitorConfig = new MonitorConfig();
const monitorConfig = new GrpcMonitorConfig();
monitorConfig.setType(this.mapType(type));
monitorConfig.setTarget(port);
monitorConfig.setTarget(port.address);
if (config.baudRate !== undefined) {
const obj = google_protobuf_struct_pb.Struct.fromJavaScript({ 'BaudRate': config.baudRate });
monitorConfig.setAdditionalconfig(obj);
monitorConfig.setAdditionalconfig(Struct.fromJavaScript({ 'BaudRate': config.baudRate }));
}
req.setMonitorconfig(monitorConfig);
return new Promise<{ connectionId: string }>(resolve => {
duplex.write(req, () => {
this.connections.set(connectionId, { toDispose, duplex });
resolve({ connectionId });
});
return new Promise<Status>(resolve => {
if (this.connection) {
this.connection.duplex.write(req, () => {
this.logger.info(`<<< Serial monitor connection created for ${Board.toString(config.board, { useFqbn: false })} on port ${Port.toString(config.port)}.`);
resolve(Status.OK);
});
return;
}
this.disconnect().then(() => resolve(Status.NOT_CONNECTED));
});
}
async disconnect(connectionId: string): Promise<boolean> {
this.logger.info(`>>> Received disconnect request for connection: ${connectionId}`);
const disposable = this.connections.get(connectionId);
if (!disposable) {
this.logger.warn(`<<< No connection was found for ID: ${connectionId}`);
return false;
async disconnect(reason?: MonitorError): Promise<Status> {
if (!this.connection && reason && reason.code === MonitorError.ErrorCodes.CLIENT_CANCEL) {
return Status.OK;
}
const result = await this.doDisconnect(connectionId, disposable);
if (result) {
this.logger.info(`<<< Successfully disconnected from ${connectionId}.`);
} else {
this.logger.info(`<<< Could not disconnected from ${connectionId}.`);
this.logger.info(`>>> Disposing monitor connection...`);
if (!this.connection) {
this.logger.warn(`<<< Not connected. Nothing to dispose.`);
return Status.NOT_CONNECTED;
}
return result;
const { duplex, config } = this.connection;
duplex.cancel();
this.logger.info(`<<< Disposed monitor connection for ${Board.toString(config.board, { useFqbn: false })} on port ${Port.toString(config.port)}.`);
this.connection = undefined;
return Status.OK;
}
protected async doDisconnect(connectionId: string, monitorDuplex: MonitorDuplex): Promise<boolean> {
const { duplex } = monitorDuplex;
this.logger.info(`>>> Disposing monitor connection: ${connectionId}...`);
try {
duplex.cancel();
this.connections.delete(connectionId);
this.logger.info(`<<< Connection disposed: ${connectionId}.`);
return true;
} catch (e) {
this.logger.error(`<<< Error occurred when disposing monitor connection: ${connectionId}. ${e}`);
return false;
async send(data: string): Promise<Status> {
if (!this.connection) {
return Status.NOT_CONNECTED;
}
const req = new StreamingOpenReq();
req.setData(new TextEncoder().encode(data));
return new Promise<Status>(resolve => {
if (this.connection) {
this.connection.duplex.write(req, () => {
resolve(Status.OK);
});
return;
}
this.disconnect().then(() => resolve(Status.NOT_CONNECTED));
});
}
async send(connectionId: string, data: string): Promise<void> {
const duplex = this.duplex(connectionId);
if (duplex) {
const req = new StreamingOpenReq();
req.setData(new TextEncoder().encode(data));
return new Promise<void>(resolve => duplex.duplex.write(req, resolve));
} else {
throw new Error(`No connection with ID: ${connectionId}.`);
}
}
protected mapType(type?: ConnectionType): MonitorConfig.TargetType {
protected mapType(type?: MonitorConfig.ConnectionType): GrpcMonitorConfig.TargetType {
switch (type) {
case ConnectionType.SERIAL: return MonitorConfig.TargetType.SERIAL;
default: return MonitorConfig.TargetType.SERIAL;
case MonitorConfig.ConnectionType.SERIAL: return GrpcMonitorConfig.TargetType.SERIAL;
default: return GrpcMonitorConfig.TargetType.SERIAL;
}
}
protected duplex(connectionId: string): MonitorDuplex | undefined {
const monitorClient = this.connections.get(connectionId);
if (!monitorClient) {
this.logger.warn(`Could not find monitor client for connection ID: ${connectionId}`);
}
return monitorClient;
}
}

View File

@@ -52,14 +52,16 @@ export class SketchesServiceImpl implements SketchesService {
const uris: string[] = [];
const fsPath = FileUri.fsPath(uri);
const stats = fs.lstatSync(fsPath);
if (stats.isDirectory && await this.isSketchFolder(uri)) {
const fileNames = fs.readdirSync(fsPath);
for (const fileName of fileNames) {
const filePath = path.join(fsPath, fileName);
if (ALLOWED_FILE_EXTENSIONS.indexOf(path.extname(filePath)) !== -1
&& fs.existsSync(filePath)
&& fs.lstatSync(filePath).isFile()) {
uris.push(FileUri.create(filePath).toString())
if (stats.isDirectory) {
if (await this.isSketchFolder(uri)) {
const fileNames = fs.readdirSync(fsPath);
for (const fileName of fileNames) {
const filePath = path.join(fsPath, fileName);
if (ALLOWED_FILE_EXTENSIONS.indexOf(path.extname(filePath)) !== -1
&& fs.existsSync(filePath)
&& fs.lstatSync(filePath).isFile()) {
uris.push(FileUri.create(filePath).toString())
}
}
}
return uris;

View File

@@ -28,4 +28,4 @@
"files": [
"../node_modules/@theia/monaco/src/typings/monaco/index.d.ts"
]
}
}

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "browser-app",
"version": "0.0.2",
"version": "0.0.3",
"license": "MIT",
"dependencies": {
"@theia/core": "next",
@@ -18,7 +18,7 @@
"@theia/terminal": "next",
"@theia/workspace": "next",
"@theia/textmate-grammars": "next",
"arduino-ide-extension": "0.0.2"
"arduino-ide-extension": "0.0.3"
},
"devDependencies": {
"@theia/cli": "next"

View File

@@ -1,50 +1,49 @@
{
"private": true,
"name": "electron-app",
"version": "0.0.2",
"license": "MIT",
"dependencies": {
"@theia/core": "next",
"@theia/cpp": "next",
"@theia/editor": "next",
"@theia/electron": "next",
"@theia/file-search": "next",
"@theia/filesystem": "next",
"@theia/languages": "next",
"@theia/messages": "next",
"@theia/monaco": "next",
"@theia/navigator": "next",
"@theia/preferences": "next",
"@theia/process": "next",
"@theia/terminal": "next",
"@theia/workspace": "next",
"@theia/textmate-grammars": "next",
"arduino-ide-extension": "0.0.2"
},
"devDependencies": {
"@theia/cli": "next",
"electron": "^4.2.0"
},
"scripts": {
"prepare": "theia build --mode development",
"start": "theia start",
"watch": "theia build --watch --mode development"
},
"theia": {
"target": "electron",
"frontend": {
"config": {
"applicationName": "Arduino Pro IDE",
"defaultTheme": "arduino-theme",
"preferences": {
"editor.autoSave": "on"
}
}
},
"generator": {
"config": {
"preloadTemplate": "<div class='theia-preload' style='background-color: rgb(237, 241, 242);'></div>"
}
"private": true,
"name": "electron-app",
"version": "0.0.3",
"license": "MIT",
"dependencies": {
"@theia/core": "next",
"@theia/cpp": "next",
"@theia/editor": "next",
"@theia/electron": "next",
"@theia/file-search": "next",
"@theia/filesystem": "next",
"@theia/languages": "next",
"@theia/messages": "next",
"@theia/monaco": "next",
"@theia/navigator": "next",
"@theia/preferences": "next",
"@theia/process": "next",
"@theia/terminal": "next",
"@theia/workspace": "next",
"@theia/textmate-grammars": "next",
"arduino-ide-extension": "0.0.3"
},
"devDependencies": {
"@theia/cli": "next"
},
"scripts": {
"prepare": "theia build --mode development",
"start": "theia start",
"watch": "theia build --watch --mode development"
},
"theia": {
"target": "electron",
"frontend": {
"config": {
"applicationName": "Arduino Pro IDE",
"defaultTheme": "arduino-theme",
"preferences": {
"editor.autoSave": "on"
}
}
},
"generator": {
"config": {
"preloadTemplate": "<div class='theia-preload' style='background-color: rgb(237, 241, 242);'></div>"
}
}
}
}

View File

@@ -6,7 +6,7 @@ All-in-one packager producing the `Arduino Pro IDE` Electron-based application.
The prerequisites are defined [here](https://github.com/theia-ide/theia/blob/master/doc/Developing.md#prerequisites).
### Build:
## Build
To build the Arduino Pro IDE application you have to do the followings:
```bash
yarn --cwd ./electron/packager/ && yarn --cwd ./electron/packager/ package
@@ -14,12 +14,14 @@ yarn --cwd ./electron/packager/ && yarn --cwd ./electron/packager/ package
The packaged application will be under the `./electron/build/dist` folder.
### CI:
## CI
We always build an electron-based application for Windows. Create a PR, and the CI will automatically create the app for Windows. Do you need the builds for macOS and Linux? Start a build manually.
The electron packager runs when:
- the build is manually triggered by the user, or
- on scheduled (CRON) jobs.
### Creating a Release Draft:
## Creating a Release Draft
One can create a GitHub release draft, tag the source, and upload the artifacts to GitHub with Azure.
- Go to the Azure [build](https://dev.azure.com/typefox/Arduino/_build) page.
- Click on `Queue` in the top right corner.
@@ -32,7 +34,7 @@ One can create a GitHub release draft, tag the source, and upload the artifacts
- 🎈🎉
### Publishing the Release Draft:
## Publishing the Release Draft
One has to manually publish the GitHub release.
- Go to the [release page](https://github.com/bcmi-labs/arduino-editor/releases) of the `arduino-editor` repository.
- Select your release draft.

View File

@@ -1,10 +1,9 @@
{
"name": "arduino.Pro.IDE",
"name": "arduino-pro-ide",
"description": "Arduino Pro IDE",
"main": "src-gen/frontend/electron-main.js",
"author": "TypeFox",
"author": "Arduino SA",
"dependencies": {
"google-protobuf": "^3.5.0",
"arduino-ide-extension": "file:../working-copy/arduino-ide-extension"
},
"resolutions": {
@@ -27,14 +26,11 @@
"url": "git+https://github.com/arduino/arduino-pro-ide.git"
},
"// Notes:": [
"The `electronVersion` version was pinned for `@grpc/grpc-js` -> Node.js version constraints.",
"`google-protobuf` was declared as it is not picked up by the `electron-builder` as a runtime dependency.",
"The resolution for `fs-extra` was required due to this: https://spectrum.chat/theia/general/our-theia-electron-builder-app-no-longer-starts~f5cf09a0-6d88-448b-8818-24ad0ec2ee7c"
],
"build": {
"productName": "Arduino Pro IDE",
"appId": "arduino.Pro.IDE",
"electronVersion": "4.2.0",
"appId": "arduino.ProIDE",
"asar": false,
"directories": {
"buildResources": "resources"
@@ -46,7 +42,6 @@
"!node_modules/**/*.spec.js",
"!node_modules/@theia/**/test/*",
"!node_modules/@theia/**/src/*.ts",
"!node_modules/@theia/java/download",
"!node_modules/@theia/**/lib/*browser/*",
"!node_modules/@typefox/monaco-editor-core/*",
"!node_modules/oniguruma/*",

View File

@@ -9,8 +9,8 @@
"cli": "./cli"
},
"keywords": [],
"author": "",
"license": "ISC",
"author": "Arduino SA",
"license": "MIT",
"dependencies": {
"depcheck": "^0.7.1",
"shelljs": "^0.8.3",

View File

@@ -1,25 +1,25 @@
{
"name": "arduino-editor",
"version": "0.0.1",
"description": "Arduino IDE built using Eclipse Theia",
"version": "0.0.3",
"description": "Arduino Pro IDE",
"main": "index.js",
"repository": "https://github.com/bcmi-labs/arduino-editor.git",
"author": "Christian Weichel <christian.weichel@typefox.io>",
"author": "Arduino SA",
"license": "MIT",
"private": true,
"devDependencies": {
"lerna": "^3.13.3"
},
"scripts": {
"prepare": "lerna run prepare",
"rebuild:browser": "theia rebuild:browser",
"rebuild:electron": "theia rebuild:electron",
"start": "yarn --cwd ./browser-app start",
"watch": "lerna run watch --parallel"
"prepare": "lerna run prepare",
"rebuild:browser": "theia rebuild:browser",
"rebuild:electron": "theia rebuild:electron",
"start": "yarn --cwd ./browser-app start",
"watch": "lerna run watch --parallel"
},
"workspaces": [
"arduino-ide-extension",
"electron-app",
"browser-app"
"electron-app",
"browser-app"
]
}

3375
yarn.lock

File diff suppressed because it is too large Load Diff