Compare commits

...

37 Commits

Author SHA1 Message Date
Alberto Iannaccone
439cdfbbff 2.0.0-rc8 (#1105) 2022-06-23 11:33:27 +02:00
Alberto Iannaccone
672fd4e4b0 bump arduino-cli version to 0.24.0 (#1103) 2022-06-23 10:59:07 +02:00
David Simpson
0f1d379e58 reference cli rc1 0.24 in package.json (#1098) 2022-06-22 18:54:30 +02:00
Francesco Spissu
a79c9b4449 Sketchbook tree indentation (#1097) 2022-06-22 18:23:14 +02:00
Akos Kitta
0f8a29a493 Disabled MenuItem roles on macOS.
Closes #969
Upstream-ref: eclipse-theia/theia#11217

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-22 12:17:36 +02:00
David Simpson
a54d7c8f45 #1032 failing upload flag for monitor mgr (#1040)
* 1032 failing upload flag for monitor mgr

* move upload failure fix logic to frontend

* misc corrections

* avoid starting monitor when upload is in progress

* avoid starting monitor when upload is in progress

* prevent monitor side effects on upload (WIP)

* send upload req after notifying mgr

* dispose instead of pause on upld (code not final)

* Revert "dispose instead of pause on upld (code not final)"

This reverts commit 2d5dff2a2d.

* force wait before upload (test)

* always start queued services after uplaod finishes

* test cli with monitor close delay

* clean up unnecessary await(s)

* remove unused dependency

* revert CLI to 0.23

* use master cli for testing, await in upload finish

* remove upload port from pending monitor requests

* fix startQueuedServices

* refinements queued monitors

* clean up monitor mgr state

* fix typo from prev cleanup

* avoid dupl queued monitor services

* variable name changes

* reference latest cli commit in package.json

Co-authored-by: Alberto Iannaccone <a.iannaccone@arduino.cc>
2022-06-22 10:39:14 +02:00
Akos Kitta
84109e416a Fixed widget lookup to eliminate duplicate tabs.
- Removed `@theia/editor-preview`,
 - Patched opener options when repairing layout on start, and
 - Compare widget keys with deepEquals instead of string equal.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-22 10:11:11 +02:00
Francesco Spissu
083337de1c IDE updater dialog colors fix (#1092) 2022-06-21 18:04:28 +02:00
Francesco Spissu
bd6bc135fd Remote sketchbook tooltips (#1088)
* rename Cloud Sketchbook in Remote Sketchbook

* add tooltips for Sync and Account buttons
2022-06-21 18:03:39 +02:00
Akos Kitta
4611381a38 Merged in #1074.
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-21 13:09:35 +02:00
Akos Kitta
d6f4096cd0 Reveal the error location after on failed verify.
Closes #608
Closes #229

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-21 13:09:35 +02:00
David Simpson
a715da3d18 flush on clear output buffer (#1074) 2022-06-20 09:32:10 +02:00
Akos Kitta
94ceefd960 Can enable debug logging of the gRPC calls.
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-17 14:14:47 +02:00
Akos Kitta
27dd120e5d Cleaned up File menu.
Removed:
 - `New File`,
 - `New Window`.

Closes #1014.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-17 13:11:08 +02:00
Francesco Spissu
f5cee97fef Implement survey notification (#1035) 2022-06-17 10:17:42 +02:00
Akos Kitta
a9aac0dbb0 Bound the original handler to this.
Closes #977

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-14 14:58:48 +02:00
David Simpson
4c6243176c Output panel optimisation (#1058)
* test interval for output panel

* create buffer provider

* output panel buffer corrections

* output buffer cleanup

* code cleanup
2022-06-14 13:00:20 +02:00
Akos Kitta
a8047660a6 Restored the Settings UI. Deferred model loading.
Closes #1031

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-13 17:08:35 +02:00
Akos Kitta
7c2843f7fd Relaxed the error handling of the core client init
For example, `malformed custom board options` was incorrectly detected
as loading JSON index file error.

Closes #1036

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-13 17:06:58 +02:00
Alberto Iannaccone
fd5154ae93 2.0.0-rc7 (#1027) 2022-06-09 10:14:56 +02:00
Alberto Iannaccone
726628e20c Fix monitor service id creation (#1025) 2022-06-08 17:21:26 +02:00
Akos Kitta
585a82b51a Added logging when restoring the layout data.
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-08 17:09:57 +02:00
Akos Kitta
5edccb9c35 Avoid opening duplicate editor tabs.
Customized the shell layout restorer:
 - If a resource is about to open in code editor and preview,
do not open the preview.
 - If a resource is about to open in preview only, open a code
editor instead.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-08 17:09:57 +02:00
Akos Kitta
555da878f4 Editor manager should be singleton.
Added some logging when filtering the layout data.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-08 17:09:57 +02:00
Alberto Iannaccone
df8658eff9 Pluggable monitor (#982)
* backend structure WIP

* Scaffold interfaces and classes for pluggable monitors

* Implement MonitorService to handle pluggable monitor lifetime

* Rename WebSocketService to WebSocketProvider and uninjected it

* Moved some interfaces

* Changed upload settings

* Enhance MonitorManager APIs

* Fixed WebSocketChange event signature

* Add monitor proxy functions for the frontend

* Moved settings to MonitorService

* Remove several unnecessary serial monitor classes

* Changed how connection is handled on upload

* Proxied more monitor methods to frontend

* WebSocketProvider is not injectable anymore

* Add generic monitor settings storaging

* More serial classes removal

* Remove unused file

* Changed plotter contribution to use new manager proxy

* Changed MonitorWidget and children to use new monitor proxy

* Updated MonitorWidget to use new monitor proxy

* Fix backend logger bindings

* Delete unnecessary Symbol

* coreClientProvider is now set when constructing MonitorService

* Add missing binding

* Fix `MonitorManagerProxy` DI issue

* fix monitor connection

* delete duplex when connection is closed

* update arduino-cli to 0.22.0

* fix upload when monitor is open

* add MonitorSettingsProvider interface

* monitor settings provider stub

* updated pseudo code

* refactor monitor settings interfaces

* monitor service provider singleton

* add unit tests

* change MonitorService providers to injectable deps

* fix monitor settings client communication

* refactor monitor commands protocol

* use monitor settings provider properly

* add settings to monitor model

* add settings to monitor model

* reset serial monitor when port changes

* fix serial plotter opening

* refine monitor connection settings

* fix hanging web socket connections

* add serial plotter reset command

* send port to web socket clients

* monitor service wait for success serial port open

* fix reset loop

* update serial plotter version

* update arduino-cli version to 0.23.0-rc1 and regenerate grpc protocol

* remove useless plotter protocol file

* localize web socket errors

* clean-up code

* update translation file

* Fix duplicated editor tabs (#1012)

* Save dialog for closing temporary sketch and unsaved files (#893)

* Use normal `OnWillStop` event

* Align `CLOSE` command to rest of app

* Fixed FS path vs encoded URL comparision when handling stop request.

Ref: https://github.com/eclipse-theia/theia/issues/11226
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* Fixed the translations.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* Fixed the translations again.

Removed `electron` from the `nls-extract`. It does not contain app code.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* Aligned the stop handler code to Theia.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

Co-authored-by: Akos Kitta <a.kitta@arduino.cc>

* fix serial monitor send line ending

* refactor monitor-service poll for test/readability

* localize web socket errors

* update translation file

* Fix duplicated editor tabs (#1012)

* i18n:check rerun

* Speed up IDE startup time.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* override coreClientProvider in monitor-service

* cleanup merged code

Co-authored-by: Francesco Stasi <f.stasi@me.com>
Co-authored-by: Silvano Cerza <silvanocerza@gmail.com>
Co-authored-by: Mark Sujew <mark.sujew@typefox.io>
Co-authored-by: David Simpson <45690499+davegarthsimpson@users.noreply.github.com>
Co-authored-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-07 15:51:12 +02:00
Akos Kitta
4c55807392 Speed up IDE startup time.
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-07 13:46:29 +02:00
github-actions[bot]
cb50d3a70d Updated translation files (#974)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-06-07 12:19:36 +02:00
David Simpson
eaf14aa1eb Follow up 944: authentication sessions are not persistent (#1003)
* #944: Fixed auth. sessions not persistent

* 944: Prevent race conditions setting authOptions

* typo correction, duplicate identifier

* prevent block of auth client service on setOptions

* consider windows cred. mgr. password len limit
2022-06-07 11:46:28 +02:00
Akos Kitta
a59e0da2af Use clang-format as the default sketch formatter.
- Bumped `clangd` to `14.0.0`,
 - Can use `.clang-format` from:
   - current sketch folder,
   - `~/.arduinoIDE/.clang-format`,
   - `directories#data/.clang-format`, or
   - falls back to default formatter styles.

Closes #1009
Closes #566

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-07 10:51:45 +02:00
Francesco Spissu
3a3ac6da4e Dark theme implementation (#991) 2022-06-07 10:48:45 +02:00
Akos Kitta
d7809616a4 Fixed LS stops working after OS sleep/wakeup cycle
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-07 10:31:05 +02:00
Mark Sujew
5b486b1480 Save dialog for closing temporary sketch and unsaved files (#893)
* Use normal `OnWillStop` event

* Align `CLOSE` command to rest of app

* Fixed FS path vs encoded URL comparision when handling stop request.

Ref: https://github.com/eclipse-theia/theia/issues/11226
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* Fixed the translations.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* Fixed the translations again.

Removed `electron` from the `nls-extract`. It does not contain app code.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

* Aligned the stop handler code to Theia.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>

Co-authored-by: Akos Kitta <a.kitta@arduino.cc>
2022-06-01 10:55:08 +02:00
Mark Sujew
5fc30bd33e Fix duplicated editor tabs (#1012) 2022-05-31 11:33:07 +02:00
Akos Kitta
522a5c6e01 Relaxed the Node version: ^14.x
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-05-25 10:05:20 +02:00
Mark Sujew
1ae60ec9bc Updated Theia to 1.25.0
Co-authored-by: Mark Sujew <mark.sujew@typefox.io>
Co-authored-by: Akos Kitta <a.kitta@arduino.cc>

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-05-25 10:05:20 +02:00
David Simpson
b8c718ce9e #944: Fixed auth. sessions not persistent (#992) 2022-05-23 09:52:44 +02:00
Akos Kitta
b407d0aee0 #985: Restored the missing inject decorator.
Closes #985.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2022-05-13 09:20:00 +02:00
313 changed files with 24191 additions and 17067 deletions

View File

@@ -78,6 +78,7 @@ jobs:
fi fi
fi fi
npx node-gyp install
yarn --cwd ./electron/packager/ yarn --cwd ./electron/packager/
yarn --cwd ./electron/packager/ package yarn --cwd ./electron/packager/ package

View File

@@ -0,0 +1,49 @@
name: themes-weekly-pull
on:
schedule:
# run every friday at 5AM
- cron: '0 5 * * 5'
workflow_dispatch:
env:
NODE_VERSION: 14.x
jobs:
pull-from-jsonbin:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: yarn
- name: Run themes:pull script
run: yarn run themes:pull
env:
JSONBIN_MASTER_KEY: ${{ secrets.JSONBIN_MASTER_KEY }}
JSONBIN_ID: ${{ secrets.JSONBIN_ID }}
- name: Generate dark tokens
run: npx token-transformer scripts/themes/tokens/arduino-tokens.json scripts/themes/tokens/dark.json core,ide-default,ide-dark,theia core,ide-default,ide-dark
- name: Generate default tokens
run: npx token-transformer scripts/themes/tokens/arduino-tokens.json scripts/themes/tokens/default.json core,ide-default,theia core,ide-default
- name: Run themes:generate script
run: yarn run themes:generate
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
commit-message: Updated themes
title: Update themes
branch: themes/themes-update
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

6
.gitignore vendored
View File

@@ -17,3 +17,9 @@ yarn*.log
plugins plugins
# the config files for the CLI # the config files for the CLI
arduino-ide-extension/data/cli/config arduino-ide-extension/data/cli/config
# the tokens folder for the themes
scripts/themes/tokens
# environment variables
.env
# content trace files for electron
electron-app/traces

View File

@@ -2,5 +2,6 @@
"singleQuote": true, "singleQuote": true,
"tabWidth": 2, "tabWidth": 2,
"useTabs": false, "useTabs": false,
"printWidth": 80 "printWidth": 80,
"endOfLine": "auto"
} }

40
.vscode/launch.json vendored
View File

@@ -1,6 +1,44 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"type": "node",
"request": "launch",
"name": "App (Electron) [Dev]",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd",
},
"cwd": "${workspaceFolder}/electron-app",
"args": [
".",
"--log-level=debug",
"--hostname=localhost",
"--no-cluster",
"--app-project-path=${workspaceRoot}/electron-app",
"--remote-debugging-port=9222",
"--no-app-auto-install",
"--plugins=local-dir:../plugins",
"--hosted-plugin-inspect=9339",
"--nosplash",
"--content-trace",
"--open-devtools"
],
"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",
"${workspaceRoot}/node_modules/@theia/**/*.js"
],
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart",
"outputCapture": "std"
},
{ {
"type": "node", "type": "node",
"request": "launch", "request": "launch",
@@ -10,7 +48,6 @@
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd", "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd",
}, },
"cwd": "${workspaceFolder}/electron-app", "cwd": "${workspaceFolder}/electron-app",
"protocol": "inspector",
"args": [ "args": [
".", ".",
"--log-level=debug", "--log-level=debug",
@@ -78,7 +115,6 @@
{ {
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"protocol": "inspector",
"name": "Run Test [current]", "name": "Run Test [current]",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [ "args": [

View File

@@ -1,6 +1,6 @@
{ {
"name": "arduino-ide-extension", "name": "arduino-ide-extension",
"version": "2.0.0-rc6", "version": "2.0.0-rc8",
"description": "An extension for Theia building the Arduino IDE", "description": "An extension for Theia building the Arduino IDE",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"scripts": { "scripts": {
@@ -21,31 +21,30 @@
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
}, },
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.3.7", "@grpc/grpc-js": "^1.6.7",
"@theia/application-package": "1.22.1", "@theia/application-package": "1.25.0",
"@theia/core": "1.22.1", "@theia/core": "1.25.0",
"@theia/editor": "1.22.1", "@theia/editor": "1.25.0",
"@theia/editor-preview": "1.22.1", "@theia/electron": "1.25.0",
"@theia/electron": "1.22.1", "@theia/filesystem": "1.25.0",
"@theia/filesystem": "1.22.1", "@theia/keymaps": "1.25.0",
"@theia/git": "1.22.1", "@theia/markers": "1.25.0",
"@theia/keymaps": "1.22.1", "@theia/monaco": "1.25.0",
"@theia/markers": "1.22.1", "@theia/navigator": "1.25.0",
"@theia/monaco": "1.22.1", "@theia/outline-view": "1.25.0",
"@theia/navigator": "1.22.1", "@theia/output": "1.25.0",
"@theia/outline-view": "1.22.1", "@theia/preferences": "1.25.0",
"@theia/output": "1.22.1", "@theia/search-in-workspace": "1.25.0",
"@theia/preferences": "1.22.1", "@theia/terminal": "1.25.0",
"@theia/search-in-workspace": "1.22.1", "@theia/workspace": "1.25.0",
"@theia/terminal": "1.22.1",
"@theia/workspace": "1.22.1",
"@tippyjs/react": "^4.2.5", "@tippyjs/react": "^4.2.5",
"@types/atob": "^2.1.2", "@types/atob": "^2.1.2",
"@types/auth0-js": "^9.14.0", "@types/auth0-js": "^9.14.0",
"@types/btoa": "^1.2.3", "@types/btoa": "^1.2.3",
"@types/dateformat": "^3.0.1", "@types/dateformat": "^3.0.1",
"@types/deep-equal": "^1.0.1",
"@types/deepmerge": "^2.2.0", "@types/deepmerge": "^2.2.0",
"@types/glob": "^5.0.35", "@types/glob": "^7.2.0",
"@types/google-protobuf": "^3.7.2", "@types/google-protobuf": "^3.7.2",
"@types/js-yaml": "^3.12.2", "@types/js-yaml": "^3.12.2",
"@types/keytar": "^4.4.0", "@types/keytar": "^4.4.0",
@@ -58,19 +57,18 @@
"@types/temp": "^0.8.34", "@types/temp": "^0.8.34",
"@types/which": "^1.3.1", "@types/which": "^1.3.1",
"ajv": "^6.5.3", "ajv": "^6.5.3",
"arduino-serial-plotter-webapp": "0.0.17", "arduino-serial-plotter-webapp": "0.1.0",
"async-mutex": "^0.3.0", "async-mutex": "^0.3.0",
"atob": "^2.1.2", "atob": "^2.1.2",
"auth0-js": "^9.14.0", "auth0-js": "^9.14.0",
"btoa": "^1.2.1", "btoa": "^1.2.1",
"css-element-queries": "^1.2.0",
"dateformat": "^3.0.3", "dateformat": "^3.0.3",
"deep-equal": "^2.0.5",
"deepmerge": "2.0.1", "deepmerge": "2.0.1",
"electron-updater": "^4.6.5", "electron-updater": "^4.6.5",
"fuzzy": "^0.1.3", "fast-safe-stringify": "^2.1.1",
"glob": "^7.1.6", "glob": "^7.1.6",
"google-protobuf": "^3.11.4", "google-protobuf": "^3.20.1",
"grpc": "^1.24.11",
"hash.js": "^1.1.7", "hash.js": "^1.1.7",
"is-valid-path": "^0.1.1", "is-valid-path": "^0.1.1",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
@@ -91,6 +89,7 @@
"semver": "^7.3.2", "semver": "^7.3.2",
"string-natural-compare": "^2.0.3", "string-natural-compare": "^2.0.3",
"temp": "^0.9.1", "temp": "^0.9.1",
"temp-dir": "^2.0.0",
"tree-kill": "^1.2.1", "tree-kill": "^1.2.1",
"upath": "^1.1.2", "upath": "^1.1.2",
"url": "^0.11.0", "url": "^0.11.0",
@@ -157,13 +156,13 @@
], ],
"arduino": { "arduino": {
"cli": { "cli": {
"version": "0.21.0" "version": "0.24.0"
}, },
"fwuploader": { "fwuploader": {
"version": "2.0.0" "version": "2.2.0"
}, },
"clangd": { "clangd": {
"version": "13.0.0" "version": "14.0.0"
}, },
"languageServer": { "languageServer": {
"version": "0.6.0" "version": "0.6.0"

View File

@@ -4,30 +4,93 @@
const version = '1.9.1'; const version = '1.9.1';
(async () => { (async () => {
const os = require('os');
const { promises: fs } = require('fs');
const path = require('path');
const shell = require('shelljs');
const { v4 } = require('uuid');
const os = require('os'); const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`);
const path = require('path'); if (shell.mkdir('-p', repository).code !== 0) {
const shell = require('shelljs'); shell.exit(1);
const { v4 } = require('uuid'); }
const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`); if (
if (shell.mkdir('-p', repository).code !== 0) { shell.exec(
shell.exit(1); `git clone https://github.com/arduino/arduino-examples.git ${repository}`
process.exit(1); ).code !== 0
) {
shell.exit(1);
}
if (
shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`)
.code !== 0
) {
shell.exit(1);
}
const destination = path.join(__dirname, '..', 'Examples');
shell.mkdir('-p', destination);
shell.cp('-fR', path.join(repository, 'examples', '*'), destination);
const isSketch = async (pathLike) => {
try {
const names = await fs.readdir(pathLike);
const dirName = path.basename(pathLike);
return names.indexOf(`${dirName}.ino`) !== -1;
} catch (e) {
if (e.code === 'ENOTDIR') {
return false;
}
throw e;
} }
};
if (shell.exec(`git clone https://github.com/arduino/arduino-examples.git ${repository}`).code !== 0) { const examples = [];
shell.exit(1); const categories = await fs.readdir(destination);
process.exit(1); const visit = async (pathLike, container) => {
const stat = await fs.lstat(pathLike);
if (stat.isDirectory()) {
if (await isSketch(pathLike)) {
container.sketches.push({
name: path.basename(pathLike),
relativePath: path.relative(destination, pathLike),
});
} else {
const names = await fs.readdir(pathLike);
for (const name of names) {
const childPath = path.join(pathLike, name);
if (await isSketch(childPath)) {
container.sketches.push({
name,
relativePath: path.relative(destination, childPath),
});
} else {
const child = {
label: name,
children: [],
sketches: [],
};
container.children.push(child);
await visit(childPath, child);
}
}
}
} }
};
if (shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`).code !== 0) { for (const category of categories) {
shell.exit(1); const example = {
process.exit(1); label: category,
} children: [],
sketches: [],
const destination = path.join(__dirname, '..', 'Examples'); };
shell.mkdir('-p', destination); await visit(path.join(destination, category), example);
shell.cp('-fR', path.join(repository, 'examples', '*'), destination); examples.push(example);
}
await fs.writeFile(
path.join(destination, 'examples.json'),
JSON.stringify(examples, null, 2),
{ encoding: 'utf8' }
);
shell.echo(`Generated output to ${path.join(destination, 'examples.json')}`);
})(); })();

View File

@@ -66,21 +66,24 @@
build, build,
`arduino-language-server${platform === 'win32' ? '.exe' : ''}` `arduino-language-server${platform === 'win32' ? '.exe' : ''}`
); );
let clangdExecutablePath, lsSuffix, clangdSuffix; let clangdExecutablePath, clangFormatExecutablePath, lsSuffix, clangdSuffix;
switch (platformArch) { switch (platformArch) {
case 'darwin-x64': case 'darwin-x64':
clangdExecutablePath = path.join(build, 'clangd'); clangdExecutablePath = path.join(build, 'clangd');
clangFormatExecutablePath = path.join(build, 'clang-format');
lsSuffix = 'macOS_64bit.tar.gz'; lsSuffix = 'macOS_64bit.tar.gz';
clangdSuffix = 'macOS_64bit'; clangdSuffix = 'macOS_64bit';
break; break;
case 'linux-x64': case 'linux-x64':
clangdExecutablePath = path.join(build, 'clangd'); clangdExecutablePath = path.join(build, 'clangd');
clangFormatExecutablePath = path.join(build, 'clang-format');
lsSuffix = 'Linux_64bit.tar.gz'; lsSuffix = 'Linux_64bit.tar.gz';
clangdSuffix = 'Linux_64bit'; clangdSuffix = 'Linux_64bit';
break; break;
case 'win32-x64': case 'win32-x64':
clangdExecutablePath = path.join(build, 'clangd.exe'); clangdExecutablePath = path.join(build, 'clangd.exe');
clangFormatExecutablePath = path.join(build, 'clang-format.exe');
lsSuffix = 'Windows_64bit.zip'; lsSuffix = 'Windows_64bit.zip';
clangdSuffix = 'Windows_64bit'; clangdSuffix = 'Windows_64bit';
break; break;
@@ -103,4 +106,15 @@
downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, { downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, {
strip: 1, strip: 1,
}); // `strip`: the new clangd (12.x) is zipped into a folder, so we have to strip the outmost folder. }); // `strip`: the new clangd (12.x) is zipped into a folder, so we have to strip the outmost folder.
const clangdFormatUrl = `https://downloads.arduino.cc/tools/clang-format_${clangdVersion}_${clangdSuffix}.tar.bz2`;
downloader.downloadUnzipAll(
clangdFormatUrl,
build,
clangFormatExecutablePath,
force,
{
strip: 1,
}
);
})(); })();

View File

@@ -1,5 +1,9 @@
import { inject, injectable, postConstruct } from 'inversify'; import {
import * as React from 'react'; inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import * as React from '@theia/core/shared/react';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import { import {
BoardsService, BoardsService,
@@ -7,6 +11,7 @@ import {
ExecutableService, ExecutableService,
Sketch, Sketch,
LibraryService, LibraryService,
ArduinoDaemon,
} from '../common/protocol'; } from '../common/protocol';
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
import { import {
@@ -17,9 +22,11 @@ import {
DisposableCollection, DisposableCollection,
} from '@theia/core'; } from '@theia/core';
import { import {
Dialog,
FrontendApplication, FrontendApplication,
FrontendApplicationContribution, FrontendApplicationContribution,
LocalStorageService, LocalStorageService,
OnWillStopAction,
SaveableWidget, SaveableWidget,
StatusBar, StatusBar,
StatusBarAlignment, StatusBarAlignment,
@@ -44,19 +51,12 @@ import {
EditorManager, EditorManager,
EditorOpenerOptions, EditorOpenerOptions,
} from '@theia/editor/lib/browser'; } from '@theia/editor/lib/browser';
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu'; import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
import { FileNavigatorCommands, FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution'; import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
import { OutputContribution } from '@theia/output/lib/browser/output-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 { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution'; import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { FileChangeType } from '@theia/filesystem/lib/browser'; import { FileChangeType } from '@theia/filesystem/lib/browser';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { ConfigService } from '../common/protocol/config-service';
import { ArduinoCommands } from './arduino-commands'; import { ArduinoCommands } from './arduino-commands';
import { BoardsConfig } from './boards/boards-config'; import { BoardsConfig } from './boards/boards-config';
import { BoardsConfigDialog } from './boards/boards-config-dialog'; import { BoardsConfigDialog } from './boards/boards-config-dialog';
@@ -67,12 +67,15 @@ import { ArduinoMenus } from './menu/arduino-menus';
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
import { ArduinoToolbar } from './toolbar/arduino-toolbar'; import { ArduinoToolbar } from './toolbar/arduino-toolbar';
import { ArduinoPreferences } from './arduino-preferences'; import { ArduinoPreferences } from './arduino-preferences';
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl'; import {
CurrentSketch,
SketchesServiceClientImpl,
} from '../common/protocol/sketches-service-client-impl';
import { SaveAsSketch } from './contributions/save-as-sketch'; import { SaveAsSketch } from './contributions/save-as-sketch';
import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution';
import { IDEUpdaterDialog } from './dialogs/ide-updater/ide-updater-dialog'; import { IDEUpdaterDialog } from './dialogs/ide-updater/ide-updater-dialog';
import { IDEUpdater } from '../common/protocol/ide-updater'; import { IDEUpdater } from '../common/protocol/ide-updater';
import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution'; import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
import { HostedPluginEvents } from './hosted-plugin-events';
const INIT_LIBS_AND_PACKAGES = 'initializedLibsAndPackages'; const INIT_LIBS_AND_PACKAGES = 'initializedLibsAndPackages';
export const SKIP_IDE_VERSION = 'skipIDEVersion'; export const SKIP_IDE_VERSION = 'skipIDEVersion';
@@ -84,92 +87,73 @@ export class ArduinoFrontendContribution
TabBarToolbarContribution, TabBarToolbarContribution,
CommandContribution, CommandContribution,
MenuContribution, MenuContribution,
ColorContribution { ColorContribution
{
@inject(ILogger) @inject(ILogger)
protected logger: ILogger; private readonly logger: ILogger;
@inject(MessageService) @inject(MessageService)
protected readonly messageService: MessageService; private readonly messageService: MessageService;
@inject(BoardsService) @inject(BoardsService)
protected readonly boardsService: BoardsService; private readonly boardsService: BoardsService;
@inject(LibraryService) @inject(LibraryService)
protected readonly libraryService: LibraryService; private readonly libraryService: LibraryService;
@inject(BoardsServiceProvider) @inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider; private readonly boardsServiceClientImpl: BoardsServiceProvider;
@inject(EditorManager) @inject(EditorManager)
protected readonly editorManager: EditorManager; private readonly editorManager: EditorManager;
@inject(FileService) @inject(FileService)
protected readonly fileService: FileService; private readonly fileService: FileService;
@inject(SketchesService) @inject(SketchesService)
protected readonly sketchService: SketchesService; private readonly sketchService: SketchesService;
@inject(BoardsConfigDialog) @inject(BoardsConfigDialog)
protected readonly boardsConfigDialog: BoardsConfigDialog; private readonly boardsConfigDialog: BoardsConfigDialog;
@inject(CommandRegistry) @inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry; private readonly commandRegistry: CommandRegistry;
@inject(StatusBar) @inject(StatusBar)
protected readonly statusBar: StatusBar; private readonly statusBar: StatusBar;
@inject(FileNavigatorContribution)
protected readonly fileNavigatorContributions: FileNavigatorContribution;
@inject(OutputContribution)
protected readonly outputContribution: OutputContribution;
@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(SketchbookWidgetContribution)
protected readonly sketchbookWidgetContribution: SketchbookWidgetContribution;
@inject(EditorMode) @inject(EditorMode)
protected readonly editorMode: EditorMode; private readonly editorMode: EditorMode;
@inject(ConfigService) @inject(HostedPluginEvents)
protected readonly configService: ConfigService; private readonly hostedPluginEvents: HostedPluginEvents;
@inject(HostedPluginSupport)
protected hostedPluginSupport: HostedPluginSupport;
@inject(ExecutableService) @inject(ExecutableService)
protected executableService: ExecutableService; private readonly executableService: ExecutableService;
@inject(ArduinoPreferences) @inject(ArduinoPreferences)
protected readonly arduinoPreferences: ArduinoPreferences; private readonly arduinoPreferences: ArduinoPreferences;
@inject(SketchesServiceClientImpl) @inject(SketchesServiceClientImpl)
protected readonly sketchServiceClient: SketchesServiceClientImpl; private readonly sketchServiceClient: SketchesServiceClientImpl;
protected readonly appStateService: FrontendApplicationStateService; @inject(FrontendApplicationStateService)
private readonly appStateService: FrontendApplicationStateService;
@inject(LocalStorageService) @inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService; private readonly localStorageService: LocalStorageService;
@inject(FileSystemFrontendContribution) @inject(FileSystemFrontendContribution)
protected readonly fileSystemFrontendContribution: FileSystemFrontendContribution; private readonly fileSystemFrontendContribution: FileSystemFrontendContribution;
@inject(IDEUpdater) @inject(IDEUpdater)
protected readonly updater: IDEUpdater; private readonly updater: IDEUpdater;
@inject(IDEUpdaterDialog) @inject(IDEUpdaterDialog)
protected readonly updaterDialog: IDEUpdaterDialog; private readonly updaterDialog: IDEUpdaterDialog;
@inject(ArduinoDaemon)
private readonly daemon: ArduinoDaemon;
protected invalidConfigPopup: protected invalidConfigPopup:
| Promise<void | 'No' | 'Yes' | undefined> | Promise<void | 'No' | 'Yes' | undefined>
@@ -240,7 +224,10 @@ export class ArduinoFrontendContribution
updateStatusBar(this.boardsServiceClientImpl.boardsConfig); updateStatusBar(this.boardsServiceClientImpl.boardsConfig);
this.appStateService.reachedState('ready').then(async () => { this.appStateService.reachedState('ready').then(async () => {
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
if (sketch && !(await this.sketchService.isTemp(sketch))) { if (
CurrentSketch.isValid(sketch) &&
!(await this.sketchService.isTemp(sketch))
) {
this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri))); this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
this.toDisposeOnStop.push( this.toDisposeOnStop.push(
this.fileService.onDidFilesChange(async (event) => { this.fileService.onDidFilesChange(async (event) => {
@@ -266,21 +253,6 @@ export class ArduinoFrontendContribution
} }
async onStart(app: FrontendApplication): Promise<void> { async onStart(app: FrontendApplication): Promise<void> {
// Initialize all `pro-mode` widgets. This is a NOOP if in normal mode.
for (const viewContribution of [
this.fileNavigatorContributions,
this.outputContribution,
this.outlineContribution,
this.problemContribution,
this.scmContribution,
this.siwContribution,
this.sketchbookWidgetContribution,
] as Array<FrontendApplicationContribution>) {
if (viewContribution.initializeLayout) {
viewContribution.initializeLayout(app);
}
}
this.updater this.updater
.init( .init(
this.arduinoPreferences.get('arduino.ide.updateChannel'), this.arduinoPreferences.get('arduino.ide.updateChannel'),
@@ -314,6 +286,12 @@ export class ArduinoFrontendContribution
} }
}; };
this.boardsServiceClientImpl.onBoardsConfigChanged(start); this.boardsServiceClientImpl.onBoardsConfigChanged(start);
this.hostedPluginEvents.onPluginsDidStart(() =>
start(this.boardsServiceClientImpl.boardsConfig)
);
this.hostedPluginEvents.onPluginsWillUnload(
() => (this.languageServerFqbn = undefined)
);
this.arduinoPreferences.onPreferenceChanged((event) => { this.arduinoPreferences.onPreferenceChanged((event) => {
if (event.newValue !== event.oldValue) { if (event.newValue !== event.oldValue) {
switch (event.preferenceName) { switch (event.preferenceName) {
@@ -344,16 +322,18 @@ export class ArduinoFrontendContribution
app.shell.leftPanelHandler.removeBottomMenu('settings-menu'); app.shell.leftPanelHandler.removeBottomMenu('settings-menu');
this.fileSystemFrontendContribution.onDidChangeEditorFile(e => { this.fileSystemFrontendContribution.onDidChangeEditorFile(
if (e.type === FileChangeType.DELETED) { ({ type, editor }) => {
const editorWidget = e.editor; if (type === FileChangeType.DELETED) {
if (SaveableWidget.is(editorWidget)) { const editorWidget = editor;
editorWidget.closeWithoutSaving(); if (SaveableWidget.is(editorWidget)) {
} else { editorWidget.closeWithoutSaving();
editorWidget.close(); } else {
editorWidget.close();
}
} }
} }
}); );
} }
onStop(): void { onStop(): void {
@@ -366,9 +346,13 @@ export class ArduinoFrontendContribution
fqbn: string, fqbn: string,
name: string | undefined name: string | undefined
): Promise<void> { ): Promise<void> {
const port = await this.daemon.tryGetPort();
if (!port) {
return;
}
const release = await this.languageServerStartMutex.acquire(); const release = await this.languageServerStartMutex.acquire();
try { try {
await this.hostedPluginSupport.didStart; await this.hostedPluginEvents.didStart;
const details = await this.boardsService.getBoardDetails({ fqbn }); const details = await this.boardsService.getBoardDetails({ fqbn });
if (!details) { if (!details) {
// Core is not installed for the selected board. // Core is not installed for the selected board.
@@ -403,7 +387,7 @@ export class ArduinoFrontendContribution
let currentSketchPath: string | undefined = undefined; let currentSketchPath: string | undefined = undefined;
if (log) { if (log) {
const currentSketch = await this.sketchServiceClient.currentSketch(); const currentSketch = await this.sketchServiceClient.currentSketch();
if (currentSketch) { if (CurrentSketch.isValid(currentSketch)) {
currentSketchPath = await this.fileService.fsPath( currentSketchPath = await this.fileService.fsPath(
new URI(currentSketch.uri) new URI(currentSketch.uri)
); );
@@ -415,8 +399,6 @@ export class ArduinoFrontendContribution
this.fileService.fsPath(new URI(lsUri)), this.fileService.fsPath(new URI(lsUri)),
]); ]);
const config = await this.configService.getConfiguration();
this.languageServerFqbn = await Promise.race([ this.languageServerFqbn = await Promise.race([
new Promise<undefined>((_, reject) => new Promise<undefined>((_, reject) =>
setTimeout( setTimeout(
@@ -428,7 +410,7 @@ export class ArduinoFrontendContribution
'arduino.languageserver.start', 'arduino.languageserver.start',
{ {
lsPath, lsPath,
cliDaemonAddr: `localhost:${config.daemon.port}`, // TODO: verify if this port is coming from the BE cliDaemonAddr: `localhost:${port}`,
clangdPath, clangdPath,
log: currentSketchPath ? currentSketchPath : log, log: currentSketchPath ? currentSketchPath : log,
cliDaemonInstance: '1', cliDaemonInstance: '1',
@@ -494,13 +476,13 @@ export class ArduinoFrontendContribution
EditorCommands.SPLIT_EDITOR_UP, EditorCommands.SPLIT_EDITOR_UP,
EditorCommands.SPLIT_EDITOR_VERTICAL, EditorCommands.SPLIT_EDITOR_VERTICAL,
EditorCommands.SPLIT_EDITOR_HORIZONTAL, EditorCommands.SPLIT_EDITOR_HORIZONTAL,
FileNavigatorCommands.REVEAL_IN_NAVIGATOR FileNavigatorCommands.REVEAL_IN_NAVIGATOR,
]) { ]) {
registry.unregisterCommand(command); registry.unregisterCommand(command);
} }
} }
registerMenus(registry: MenuModelRegistry) { registerMenus(registry: MenuModelRegistry): void {
const menuId = (menuPath: string[]): string => { const menuId = (menuPath: string[]): string => {
const index = menuPath.length - 1; const index = menuPath.length - 1;
const menuId = menuPath[index]; const menuId = menuPath[index];
@@ -569,12 +551,19 @@ export class ArduinoFrontendContribution
uri: string, uri: string,
forceOpen = false, forceOpen = false,
options?: EditorOpenerOptions | undefined options?: EditorOpenerOptions | undefined
): Promise<any> { ): Promise<unknown> {
const widget = this.editorManager.all.find( const widget = this.editorManager.all.find(
(widget) => widget.editor.uri.toString() === uri (widget) => widget.editor.uri.toString() === uri
); );
if (!widget || forceOpen) { if (!widget || forceOpen) {
return this.editorManager.open(new URI(uri), options); return this.editorManager.open(
new URI(uri),
options ?? {
mode: 'reveal',
preview: false,
counter: 0,
}
);
} }
} }
@@ -658,4 +647,57 @@ export class ArduinoFrontendContribution
} }
); );
} }
onWillStop(): OnWillStopAction {
return {
reason: 'temp-sketch',
action: () => {
return this.showTempSketchDialog();
},
};
}
private async showTempSketchDialog(): Promise<boolean> {
const sketch = await this.sketchServiceClient.currentSketch();
if (!CurrentSketch.isValid(sketch)) {
return true;
}
const isTemp = await this.sketchService.isTemp(sketch);
if (!isTemp) {
return true;
}
const messageBoxResult = await remote.dialog.showMessageBox(
remote.getCurrentWindow(),
{
message: nls.localize(
'arduino/sketch/saveTempSketch',
'Save your sketch to open it again later.'
),
title: nls.localize(
'theia/core/quitTitle',
'Are you sure you want to quit?'
),
type: 'question',
buttons: [
Dialog.CANCEL,
nls.localizeByDefault('Save As...'),
nls.localizeByDefault("Don't Save"),
],
}
);
const result = messageBoxResult.response;
if (result === 2) {
return true;
} else if (result === 1) {
return !!(await this.commandRegistry.executeCommand(
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
{
execOnlyIfTemp: false,
openAfterMove: false,
wipeOriginal: true,
}
));
}
return false;
}
} }

View File

@@ -1,5 +1,5 @@
import '../../src/browser/style/index.css'; import '../../src/browser/style/index.css';
import { ContainerModule } from 'inversify'; import { ContainerModule } from '@theia/core/shared/inversify';
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager'; import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
import { CommandContribution } from '@theia/core/lib/common/command'; import { CommandContribution } from '@theia/core/lib/common/command';
import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
@@ -42,15 +42,14 @@ import { FileNavigatorContribution as TheiaFileNavigatorContribution } from '@th
import { KeymapsFrontendContribution } from './theia/keymaps/keymaps-frontend-contribution'; import { KeymapsFrontendContribution } from './theia/keymaps/keymaps-frontend-contribution';
import { KeymapsFrontendContribution as TheiaKeymapsFrontendContribution } from '@theia/keymaps/lib/browser/keymaps-frontend-contribution'; import { KeymapsFrontendContribution as TheiaKeymapsFrontendContribution } from '@theia/keymaps/lib/browser/keymaps-frontend-contribution';
import { ArduinoToolbarContribution } from './toolbar/arduino-toolbar-contribution'; import { ArduinoToolbarContribution } from './toolbar/arduino-toolbar-contribution';
import { EditorPreviewContribution as TheiaEditorPreviewContribution } from '@theia/editor-preview/lib/browser/editor-preview-contribution'; import { EditorContribution as TheiaEditorContribution } from '@theia/editor/lib/browser/editor-contribution';
import { EditorPreviewContribution } from './theia/editor/editor-contribution'; import { EditorContribution } from './theia/editor/editor-contribution';
import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution'; import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
import { MonacoStatusBarContribution } from './theia/monaco/monaco-status-bar-contribution'; import { MonacoStatusBarContribution } from './theia/monaco/monaco-status-bar-contribution';
import { import {
ApplicationShell as TheiaApplicationShell, ApplicationShell as TheiaApplicationShell,
ShellLayoutRestorer as TheiaShellLayoutRestorer, ShellLayoutRestorer as TheiaShellLayoutRestorer,
CommonFrontendContribution as TheiaCommonFrontendContribution, CommonFrontendContribution as TheiaCommonFrontendContribution,
KeybindingRegistry as TheiaKeybindingRegistry,
TabBarRendererFactory, TabBarRendererFactory,
ContextMenuRenderer, ContextMenuRenderer,
createTreeContainer, createTreeContainer,
@@ -69,20 +68,12 @@ import { ScmContribution } from './theia/scm/scm-contribution';
import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspace/search-in-workspace-frontend-contribution'; import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspace/search-in-workspace-frontend-contribution';
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution'; import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
import { SerialServiceClientImpl } from './serial/serial-service-client-impl';
import {
SerialServicePath,
SerialService,
SerialServiceClient,
} from '../common/protocol/serial-service';
import { import {
ConfigService, ConfigService,
ConfigServicePath, ConfigServicePath,
} from '../common/protocol/config-service'; } from '../common/protocol/config-service';
import { MonitorWidget } from './serial/monitor/monitor-widget'; import { MonitorWidget } from './serial/monitor/monitor-widget';
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
import { SerialConnectionManager } from './serial/serial-connection-manager';
import { SerialModel } from './serial/serial-model';
import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator'; import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
import { TabBarDecoratorService } from './theia/core/tab-bar-decorator'; import { TabBarDecoratorService } from './theia/core/tab-bar-decorator';
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser'; import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser';
@@ -130,6 +121,7 @@ import { SaveAsSketch } from './contributions/save-as-sketch';
import { SaveSketch } from './contributions/save-sketch'; import { SaveSketch } from './contributions/save-sketch';
import { VerifySketch } from './contributions/verify-sketch'; import { VerifySketch } from './contributions/verify-sketch';
import { UploadSketch } from './contributions/upload-sketch'; import { UploadSketch } from './contributions/upload-sketch';
import { SurveyNotification } from './contributions/survey-notification';
import { CommonFrontendContribution } from './theia/core/common-frontend-contribution'; import { CommonFrontendContribution } from './theia/core/common-frontend-contribution';
import { EditContributions } from './contributions/edit-contributions'; import { EditContributions } from './contributions/edit-contributions';
import { OpenSketchExternal } from './contributions/open-sketch-external'; import { OpenSketchExternal } from './contributions/open-sketch-external';
@@ -138,7 +130,6 @@ import { PreferencesContribution } from './theia/preferences/preferences-contrib
import { QuitApp } from './contributions/quit-app'; import { QuitApp } from './contributions/quit-app';
import { SketchControl } from './contributions/sketch-control'; import { SketchControl } from './contributions/sketch-control';
import { Settings } from './contributions/settings'; import { Settings } from './contributions/settings';
import { KeybindingRegistry } from './theia/core/keybindings';
import { WorkspaceCommandContribution } from './theia/workspace/workspace-commands'; import { WorkspaceCommandContribution } from './theia/workspace/workspace-commands';
import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler'; import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler';
import { WorkspaceDeleteHandler } from './theia/workspace/workspace-delete-handler'; import { WorkspaceDeleteHandler } from './theia/workspace/workspace-delete-handler';
@@ -160,7 +151,14 @@ import {
OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl,
OutputChannelRegistryMainImpl, OutputChannelRegistryMainImpl,
} from './theia/plugin-ext/output-channel-registry-main'; } from './theia/plugin-ext/output-channel-registry-main';
import { ExecutableService, ExecutableServicePath } from '../common/protocol'; import {
ExecutableService,
ExecutableServicePath,
MonitorManagerProxy,
MonitorManagerProxyClient,
MonitorManagerProxyFactory,
MonitorManagerProxyPath,
} from '../common/protocol';
import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service'; import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
import { ResponseServiceImpl } from './response-service-impl'; import { ResponseServiceImpl } from './response-service-impl';
@@ -275,20 +273,51 @@ import {
IDEUpdaterDialogWidget, IDEUpdaterDialogWidget,
} from './dialogs/ide-updater/ide-updater-dialog'; } from './dialogs/ide-updater/ide-updater-dialog';
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider'; import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider';
import { MonitorModel } from './monitor-model';
const ElementQueries = require('css-element-queries/src/ElementQueries'); import { MonitorManagerProxyClientImpl } from './monitor-manager-proxy-client-impl';
import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser/editor-manager';
import { EditorManager } from './theia/editor/editor-manager';
import { HostedPluginEvents } from './hosted-plugin-events';
import { HostedPluginSupport } from './theia/plugin-ext/hosted-plugin';
import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
import { Formatter, FormatterPath } from '../common/protocol/formatter';
import { Format } from './contributions/format';
import { MonacoFormattingConflictsContribution } from './theia/monaco/monaco-formatting-conflicts';
import { MonacoFormattingConflictsContribution as TheiaMonacoFormattingConflictsContribution } from '@theia/monaco/lib/browser/monaco-formatting-conflicts';
import { DefaultJsonSchemaContribution } from './theia/core/json-schema-store';
import { DefaultJsonSchemaContribution as TheiaDefaultJsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store';
import { EditorNavigationContribution } from './theia/editor/editor-navigation-contribution';
import { EditorNavigationContribution as TheiaEditorNavigationContribution } from '@theia/editor/lib/browser/editor-navigation-contribution';
import { PreferenceTreeGenerator } from './theia/preferences/preference-tree-generator';
import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator';
import { AboutDialog } from './theia/core/about-dialog';
import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog';
import {
SurveyNotificationService,
SurveyNotificationServicePath,
} from '../common/protocol/survey-service';
import { WindowContribution } from './theia/core/window-contribution';
import { WindowContribution as TheiaWindowContribution } from '@theia/core/lib/browser/window-contribution';
import { CoreErrorHandler } from './contributions/core-error-handler';
import { CompilerErrors } from './contributions/compiler-errors';
import { WidgetManager } from './theia/core/widget-manager';
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
MonacoThemingService.register({ MonacoThemingService.register({
id: 'arduino-theme', id: 'arduino-theme',
label: 'Light (Arduino)', label: 'Light (Arduino)',
uiTheme: 'vs', uiTheme: 'vs',
json: require('../../src/browser/data/arduino.color-theme.json'), json: require('../../src/browser/data/default.color-theme.json'),
});
MonacoThemingService.register({
id: 'arduino-theme-dark',
label: 'Dark (Arduino)',
uiTheme: 'vs-dark',
json: require('../../src/browser/data/dark.color-theme.json'),
}); });
export default new ContainerModule((bind, unbind, isBound, rebind) => { export default new ContainerModule((bind, unbind, isBound, rebind) => {
ElementQueries.listen();
ElementQueries.init();
// Commands and toolbar items // Commands and toolbar items
bind(ArduinoFrontendContribution).toSelf().inSingletonScope(); bind(ArduinoFrontendContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(ArduinoFrontendContribution); bind(CommandContribution).toService(ArduinoFrontendContribution);
@@ -405,31 +434,47 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
) )
) )
.inSingletonScope(); .inSingletonScope();
bind(CoreErrorHandler).toSelf().inSingletonScope();
// Serial monitor // Serial monitor
bind(SerialModel).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(SerialModel);
bind(MonitorWidget).toSelf(); bind(MonitorWidget).toSelf();
bind(FrontendApplicationContribution).toService(MonitorModel);
bind(MonitorModel).toSelf().inSingletonScope();
bindViewContribution(bind, MonitorViewContribution); bindViewContribution(bind, MonitorViewContribution);
bind(TabBarToolbarContribution).toService(MonitorViewContribution); bind(TabBarToolbarContribution).toService(MonitorViewContribution);
bind(WidgetFactory).toDynamicValue((context) => ({ bind(WidgetFactory).toDynamicValue((context) => ({
id: MonitorWidget.ID, id: MonitorWidget.ID,
createWidget: () => context.container.get(MonitorWidget), createWidget: () => {
})); return new MonitorWidget(
// Frontend binding for the serial service context.container.get<MonitorModel>(MonitorModel),
bind(SerialService) context.container.get<MonitorManagerProxyClient>(
.toDynamicValue((context) => { MonitorManagerProxyClient
const connection = context.container.get(WebSocketConnectionProvider); ),
const client = context.container.get<SerialServiceClient>( context.container.get<BoardsServiceProvider>(BoardsServiceProvider)
SerialServiceClient
); );
return connection.createProxy(SerialServicePath, client); },
}) }));
.inSingletonScope();
bind(SerialConnectionManager).toSelf().inSingletonScope();
// Serial service client to receive and delegate notifications from the backend. bind(MonitorManagerProxyFactory).toFactory(
bind(SerialServiceClient).to(SerialServiceClientImpl).inSingletonScope(); (context) => () =>
context.container.get<MonitorManagerProxy>(MonitorManagerProxy)
);
bind(MonitorManagerProxy)
.toDynamicValue((context) =>
WebSocketConnectionProvider.createProxy(
context.container,
MonitorManagerProxyPath,
context.container.get(MonitorManagerProxyClient)
)
)
.inSingletonScope();
// Monitor manager proxy client to receive and delegate pluggable monitors
// notifications from the backend
bind(MonitorManagerProxyClient)
.to(MonitorManagerProxyClientImpl)
.inSingletonScope();
bind(WorkspaceService).toSelf().inSingletonScope(); bind(WorkspaceService).toSelf().inSingletonScope();
rebind(TheiaWorkspaceService).toService(WorkspaceService); rebind(TheiaWorkspaceService).toService(WorkspaceService);
@@ -442,6 +487,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(EditorMode).toSelf().inSingletonScope(); bind(EditorMode).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(EditorMode); bind(FrontendApplicationContribution).toService(EditorMode);
// Survey notification
bind(SurveyNotification).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(SurveyNotification);
bind(SurveyNotificationService)
.toDynamicValue((context) => {
return ElectronIpcConnectionProvider.createProxy(
context.container,
SurveyNotificationServicePath
);
})
.inSingletonScope();
// Layout and shell customizations. // Layout and shell customizations.
rebind(TheiaOutlineViewContribution) rebind(TheiaOutlineViewContribution)
.to(OutlineViewContribution) .to(OutlineViewContribution)
@@ -453,9 +511,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(TheiaKeymapsFrontendContribution) rebind(TheiaKeymapsFrontendContribution)
.to(KeymapsFrontendContribution) .to(KeymapsFrontendContribution)
.inSingletonScope(); .inSingletonScope();
rebind(TheiaEditorPreviewContribution) rebind(TheiaEditorContribution).to(EditorContribution).inSingletonScope();
.to(EditorPreviewContribution)
.inSingletonScope();
rebind(TheiaMonacoStatusBarContribution) rebind(TheiaMonacoStatusBarContribution)
.to(MonacoStatusBarContribution) .to(MonacoStatusBarContribution)
.inSingletonScope(); .inSingletonScope();
@@ -477,7 +533,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(TheiaPreferencesContribution) rebind(TheiaPreferencesContribution)
.to(PreferencesContribution) .to(PreferencesContribution)
.inSingletonScope(); .inSingletonScope();
rebind(TheiaKeybindingRegistry).to(KeybindingRegistry).inSingletonScope();
rebind(TheiaWorkspaceCommandContribution) rebind(TheiaWorkspaceCommandContribution)
.to(WorkspaceCommandContribution) .to(WorkspaceCommandContribution)
.inSingletonScope(); .inSingletonScope();
@@ -486,11 +541,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
.inSingletonScope(); .inSingletonScope();
rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope(); rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope();
rebind(TabBarToolbarFactory).toFactory( rebind(TabBarToolbarFactory).toFactory(
({ container: parentContainer }) => () => { ({ container: parentContainer }) =>
const container = parentContainer.createChild(); () => {
container.bind(TabBarToolbar).toSelf().inSingletonScope(); const container = parentContainer.createChild();
return container.get(TabBarToolbar); container.bind(TabBarToolbar).toSelf().inSingletonScope();
} return container.get(TabBarToolbar);
}
); );
bind(OutputWidget).toSelf().inSingletonScope(); bind(OutputWidget).toSelf().inSingletonScope();
rebind(TheiaOutputWidget).toService(OutputWidget); rebind(TheiaOutputWidget).toService(OutputWidget);
@@ -508,6 +564,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(SearchInWorkspaceWidget).toSelf(); bind(SearchInWorkspaceWidget).toSelf();
rebind(TheiaSearchInWorkspaceWidget).toService(SearchInWorkspaceWidget); rebind(TheiaSearchInWorkspaceWidget).toService(SearchInWorkspaceWidget);
// Disabled reference counter in the editor manager to avoid opening the same editor (with different opener options) multiple times.
bind(EditorManager).toSelf().inSingletonScope();
rebind(TheiaEditorManager).to(EditorManager);
// replace search icon // replace search icon
rebind(TheiaSearchInWorkspaceFactory) rebind(TheiaSearchInWorkspaceFactory)
.to(SearchInWorkspaceFactory) .to(SearchInWorkspaceFactory)
@@ -550,6 +610,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(OutputToolbarContribution).toSelf().inSingletonScope(); bind(OutputToolbarContribution).toSelf().inSingletonScope();
rebind(TheiaOutputToolbarContribution).toService(OutputToolbarContribution); rebind(TheiaOutputToolbarContribution).toService(OutputToolbarContribution);
// To remove `New Window` from the `File` menu
bind(WindowContribution).toSelf().inSingletonScope();
rebind(TheiaWindowContribution).toService(WindowContribution);
bind(ArduinoDaemon) bind(ArduinoDaemon)
.toDynamicValue((context) => .toDynamicValue((context) =>
WebSocketConnectionProvider.createProxy( WebSocketConnectionProvider.createProxy(
@@ -559,6 +623,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
) )
.inSingletonScope(); .inSingletonScope();
bind(Formatter)
.toDynamicValue(({ container }) =>
WebSocketConnectionProvider.createProxy(container, FormatterPath)
)
.inSingletonScope();
bind(ArduinoFirmwareUploader) bind(ArduinoFirmwareUploader)
.toDynamicValue((context) => .toDynamicValue((context) =>
WebSocketConnectionProvider.createProxy( WebSocketConnectionProvider.createProxy(
@@ -626,6 +696,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, ArchiveSketch); Contribution.configure(bind, ArchiveSketch);
Contribution.configure(bind, AddZipLibrary); Contribution.configure(bind, AddZipLibrary);
Contribution.configure(bind, PlotterFrontendContribution); Contribution.configure(bind, PlotterFrontendContribution);
Contribution.configure(bind, Format);
Contribution.configure(bind, CompilerErrors);
// Disabled the quick-pick customization from Theia when multiple formatters are available.
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
bind(MonacoFormattingConflictsContribution).toSelf().inSingletonScope();
rebind(TheiaMonacoFormattingConflictsContribution).toService(
MonacoFormattingConflictsContribution
);
bind(ResponseServiceImpl) bind(ResponseServiceImpl)
.toSelf() .toSelf()
@@ -655,15 +734,13 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Enable the dirty indicator on uncloseable widgets. // Enable the dirty indicator on uncloseable widgets.
rebind(TabBarRendererFactory).toFactory((context) => () => { rebind(TabBarRendererFactory).toFactory((context) => () => {
const contextMenuRenderer = context.container.get<ContextMenuRenderer>( const contextMenuRenderer =
ContextMenuRenderer context.container.get<ContextMenuRenderer>(ContextMenuRenderer);
);
const decoratorService = context.container.get<TabBarDecoratorService>( const decoratorService = context.container.get<TabBarDecoratorService>(
TabBarDecoratorService TabBarDecoratorService
); );
const iconThemeService = context.container.get<IconThemeService>( const iconThemeService =
IconThemeService context.container.get<IconThemeService>(IconThemeService);
);
return new TabBarRenderer( return new TabBarRenderer(
contextMenuRenderer, contextMenuRenderer,
decoratorService, decoratorService,
@@ -673,6 +750,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Workaround for https://github.com/eclipse-theia/theia/issues/8722 // Workaround for https://github.com/eclipse-theia/theia/issues/8722
// Do not trigger a save on IDE startup if `"editor.autoSave": "on"` was set as a preference. // Do not trigger a save on IDE startup if `"editor.autoSave": "on"` was set as a preference.
// Note: `"editor.autoSave" was renamed to `"files.autoSave" and `"on"` was replaced with three
// different cases, but we treat `!== 'off'` as auto save enabled. (https://github.com/eclipse-theia/theia/issues/10812)
bind(EditorCommandContribution).toSelf().inSingletonScope(); bind(EditorCommandContribution).toSelf().inSingletonScope();
rebind(TheiaEditorCommandContribution).toService(EditorCommandContribution); rebind(TheiaEditorCommandContribution).toService(EditorCommandContribution);
@@ -680,6 +759,26 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(NavigatorTabBarDecorator).toSelf().inSingletonScope(); bind(NavigatorTabBarDecorator).toSelf().inSingletonScope();
rebind(TheiaNavigatorTabBarDecorator).toService(NavigatorTabBarDecorator); rebind(TheiaNavigatorTabBarDecorator).toService(NavigatorTabBarDecorator);
// Do not fetch the `catalog.json` from Azure on FE load.
bind(DefaultJsonSchemaContribution).toSelf().inSingletonScope();
rebind(TheiaDefaultJsonSchemaContribution).toService(
DefaultJsonSchemaContribution
);
// Do not block the app startup when initializing the editor navigation history.
bind(EditorNavigationContribution).toSelf().inSingletonScope();
rebind(TheiaEditorNavigationContribution).toService(
EditorNavigationContribution
);
// IDE2 does not use the Theia preferences widget, no need to create and sync the underlying tree model.
bind(PreferenceTreeGenerator).toSelf().inSingletonScope();
rebind(TheiaPreferenceTreeGenerator).toService(PreferenceTreeGenerator);
// IDE2 has a custom about dialog, so there is no need to load the Theia extensions on FE load
bind(AboutDialog).toSelf().inSingletonScope();
rebind(TheiaAboutDialog).toService(AboutDialog);
// To avoid running `Save All` when there are no dirty editors before starting the debug session. // To avoid running `Save All` when there are no dirty editors before starting the debug session.
bind(DebugSessionManager).toSelf().inSingletonScope(); bind(DebugSessionManager).toSelf().inSingletonScope();
rebind(TheiaDebugSessionManager).toService(DebugSessionManager); rebind(TheiaDebugSessionManager).toService(DebugSessionManager);
@@ -692,6 +791,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(DebugConfigurationManager).toSelf().inSingletonScope(); bind(DebugConfigurationManager).toSelf().inSingletonScope();
rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager); rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager);
// To avoid duplicate tabs use deepEqual instead of string equal: https://github.com/eclipse-theia/theia/issues/11309
bind(WidgetManager).toSelf().inSingletonScope();
rebind(TheiaWidgetManager).toService(WidgetManager);
// Preferences // Preferences
bindArduinoPreferences(bind); bindArduinoPreferences(bind);
@@ -799,4 +902,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
); );
}) })
.inSingletonScope(); .inSingletonScope();
bind(HostedPluginSupport).toSelf().inSingletonScope();
rebind(TheiaHostedPluginSupport).toService(HostedPluginSupport);
bind(HostedPluginEvents).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(HostedPluginEvents);
}); });

View File

@@ -1,4 +1,4 @@
import { interfaces } from 'inversify'; import { interfaces } from '@theia/core/shared/inversify';
import { import {
createPreferenceProxy, createPreferenceProxy,
PreferenceProxy, PreferenceProxy,
@@ -13,6 +13,32 @@ export enum UpdateChannel {
Stable = 'stable', Stable = 'stable',
Nightly = 'nightly', Nightly = 'nightly',
} }
export const ErrorRevealStrategyLiterals = [
/**
* Scroll vertically as necessary and reveal a line.
*/
'auto',
/**
* Scroll vertically as necessary and reveal a line centered vertically.
*/
'center',
/**
* Scroll vertically as necessary and reveal a line close to the top of the viewport, optimized for viewing a code definition.
*/
'top',
/**
* Scroll vertically as necessary and reveal a line centered vertically only if it lies outside the viewport.
*/
'centerIfOutsideViewport',
] as const;
export type ErrorRevealStrategy = typeof ErrorRevealStrategyLiterals[number];
export namespace ErrorRevealStrategy {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
export function is(arg: any): arg is ErrorRevealStrategy {
return !!arg && ErrorRevealStrategyLiterals.includes(arg);
}
export const Default: ErrorRevealStrategy = 'centerIfOutsideViewport';
}
export const ArduinoConfigSchema: PreferenceSchema = { export const ArduinoConfigSchema: PreferenceSchema = {
type: 'object', type: 'object',
@@ -33,6 +59,23 @@ export const ArduinoConfigSchema: PreferenceSchema = {
), ),
default: false, default: false,
}, },
'arduino.compile.experimental': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/compile.experimental',
'True if the IDE should handle multiple compiler errors. False by default'
),
default: false,
},
'arduino.compile.revealRange': {
enum: [...ErrorRevealStrategyLiterals],
description: nls.localize(
'arduino/preferences/compile.revealRange',
"Adjusts how compiler errors are revealed in the editor after a failed verify/upload. Possible values: 'auto': Scroll vertically as necessary and reveal a line. 'center': Scroll vertically as necessary and reveal a line centered vertically. 'top': Scroll vertically as necessary and reveal a line close to the top of the viewport, optimized for viewing a code definition. 'centerIfOutsideViewport': Scroll vertically as necessary and reveal a line centered vertically only if it lies outside the viewport. The default value is '{0}'.",
ErrorRevealStrategy.Default
),
default: ErrorRevealStrategy.Default,
},
'arduino.compile.warnings': { 'arduino.compile.warnings': {
enum: [...CompilerWarningLiterals], enum: [...CompilerWarningLiterals],
description: nls.localize( description: nls.localize(
@@ -83,7 +126,7 @@ export const ArduinoConfigSchema: PreferenceSchema = {
default: 'https://downloads.arduino.cc/arduino-ide', default: 'https://downloads.arduino.cc/arduino-ide',
description: nls.localize( description: nls.localize(
'arduino/preferences/ide.updateBaseUrl', 'arduino/preferences/ide.updateBaseUrl',
`The base URL where to download updates from. Defaults to 'https://downloads.arduino.cc/arduino-ide'` "The base URL where to download updates from. Defaults to 'https://downloads.arduino.cc/arduino-ide'"
), ),
}, },
'arduino.board.certificates': { 'arduino.board.certificates': {
@@ -174,12 +217,30 @@ export const ArduinoConfigSchema: PreferenceSchema = {
), ),
default: 'https://auth.arduino.cc/login#/register', default: 'https://auth.arduino.cc/login#/register',
}, },
'arduino.survey.notification': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/survey.notification',
'True if users should be notified if a survey is available. True by default.'
),
default: true,
},
'arduino.cli.daemon.debug': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cli.daemonDebug',
"Enable debug logging of the gRPC calls to the Arduino CLI. A restart of the IDE is needed for this setting to take effect. It's false by default."
),
default: false,
},
}, },
}; };
export interface ArduinoConfiguration { export interface ArduinoConfiguration {
'arduino.language.log': boolean; 'arduino.language.log': boolean;
'arduino.compile.verbose': boolean; 'arduino.compile.verbose': boolean;
'arduino.compile.experimental': boolean;
'arduino.compile.revealRange': ErrorRevealStrategy;
'arduino.compile.warnings': CompilerWarnings; 'arduino.compile.warnings': CompilerWarnings;
'arduino.upload.verbose': boolean; 'arduino.upload.verbose': boolean;
'arduino.upload.verify': boolean; 'arduino.upload.verify': boolean;
@@ -198,6 +259,8 @@ export interface ArduinoConfiguration {
'arduino.auth.domain': string; 'arduino.auth.domain': string;
'arduino.auth.audience': string; 'arduino.auth.audience': string;
'arduino.auth.registerUri': string; 'arduino.auth.registerUri': string;
'arduino.survey.notification': boolean;
'arduino.cli.daemon.debug': boolean;
} }
export const ArduinoPreferences = Symbol('ArduinoPreferences'); export const ArduinoPreferences = Symbol('ArduinoPreferences');

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { Emitter } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory'; import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { WindowService } from '@theia/core/lib/browser/window/window-service';
@@ -43,13 +43,15 @@ export class AuthenticationClientService
readonly onSessionDidChange = this.onSessionDidChangeEmitter.event; readonly onSessionDidChange = this.onSessionDidChangeEmitter.event;
onStart(): void { async onStart(): Promise<void> {
this.toDispose.push(this.onSessionDidChangeEmitter); this.toDispose.push(this.onSessionDidChangeEmitter);
this.service.setClient(this); this.service.setClient(this);
this.service this.service
.session() .session()
.then((session) => this.notifySessionDidChange(session)); .then((session) => this.notifySessionDidChange(session));
this.setOptions();
this.setOptions().then(() => this.service.initAuthSession());
this.arduinoPreferences.onPreferenceChanged((event) => { this.arduinoPreferences.onPreferenceChanged((event) => {
if (event.preferenceName.startsWith('arduino.auth.')) { if (event.preferenceName.startsWith('arduino.auth.')) {
this.setOptions(); this.setOptions();
@@ -57,8 +59,8 @@ export class AuthenticationClientService
}); });
} }
setOptions(): void { setOptions(): Promise<void> {
this.service.setOptions({ return this.service.setOptions({
redirectUri: `http://localhost:${serverPort}/callback`, redirectUri: `http://localhost:${serverPort}/callback`,
responseType: 'code', responseType: 'code',
clientID: this.arduinoPreferences['arduino.auth.clientID'], clientID: this.arduinoPreferences['arduino.auth.clientID'],

View File

@@ -1,4 +1,4 @@
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { MessageService } from '@theia/core/lib/common/message-service'; import { MessageService } from '@theia/core/lib/common/message-service';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { import {

View File

@@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { Emitter } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { ReactWidget, Message } from '@theia/core/lib/browser'; import { ReactWidget, Message } from '@theia/core/lib/browser';
import { BoardsService } from '../../common/protocol/boards-service'; import { BoardsService } from '../../common/protocol/boards-service';
@@ -55,12 +55,13 @@ export class BoardsConfigDialogWidget extends ReactWidget {
onConfigChange={this.fireConfigChanged} onConfigChange={this.fireConfigChanged}
onFocusNodeSet={this.setFocusNode} onFocusNodeSet={this.setFocusNode}
onFilteredTextDidChangeEvent={this.onFilterTextDidChangeEmitter.event} onFilteredTextDidChangeEvent={this.onFilterTextDidChangeEmitter.event}
onAppStateDidChange={this.notificationCenter.onAppStateDidChange}
/> />
</div> </div>
); );
} }
protected onActivateRequest(msg: Message): void { protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg); super.onActivateRequest(msg);
if (this.focusNode instanceof HTMLInputElement) { if (this.focusNode instanceof HTMLInputElement) {
this.focusNode.select(); this.focusNode.select();

View File

@@ -1,5 +1,5 @@
import { injectable, inject, postConstruct } from 'inversify'; import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import { Message } from '@phosphor/messaging'; import { Message } from '@theia/core/shared/@phosphor/messaging';
import { DialogProps, Widget, DialogError } from '@theia/core/lib/browser'; import { DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
import { AbstractDialog } from '../theia/dialogs/dialogs'; import { AbstractDialog } from '../theia/dialogs/dialogs';
import { BoardsConfig } from './boards-config'; import { BoardsConfig } from './boards-config';
@@ -26,7 +26,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
constructor( constructor(
@inject(BoardsConfigDialogProps) @inject(BoardsConfigDialogProps)
protected readonly props: BoardsConfigDialogProps protected override readonly props: BoardsConfigDialogProps
) { ) {
super(props); super(props);
@@ -52,7 +52,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
/** /**
* Pass in an empty string if you want to reset the search term. Using `undefined` has no effect. * Pass in an empty string if you want to reset the search term. Using `undefined` has no effect.
*/ */
async open( override async open(
query: string | undefined = undefined query: string | undefined = undefined
): Promise<BoardsConfig.Config | undefined> { ): Promise<BoardsConfig.Config | undefined> {
if (typeof query === 'string') { if (typeof query === 'string') {
@@ -95,7 +95,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
return head; return head;
} }
protected onAfterAttach(msg: Message): void { protected override onAfterAttach(msg: Message): void {
if (this.widget.isAttached) { if (this.widget.isAttached) {
Widget.detach(this.widget); Widget.detach(this.widget);
} }
@@ -110,23 +110,23 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
this.update(); this.update();
} }
protected onUpdateRequest(msg: Message) { protected override onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg); super.onUpdateRequest(msg);
this.widget.update(); this.widget.update();
} }
protected onActivateRequest(msg: Message): void { protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg); super.onActivateRequest(msg);
this.widget.activate(); this.widget.activate();
} }
protected handleEnter(event: KeyboardEvent): boolean | void { protected override handleEnter(event: KeyboardEvent): boolean | void {
if (event.target instanceof HTMLTextAreaElement) { if (event.target instanceof HTMLTextAreaElement) {
return false; return false;
} }
} }
protected isValid(value: BoardsConfig.Config): DialogError { protected override isValid(value: BoardsConfig.Config): DialogError {
if (!value.selectedBoard) { if (!value.selectedBoard) {
if (value.selectedPort) { if (value.selectedPort) {
return nls.localize( return nls.localize(

View File

@@ -1,4 +1,4 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { Event } from '@theia/core/lib/common/event'; import { Event } from '@theia/core/lib/common/event';
import { notEmpty } from '@theia/core/lib/common/objects'; import { notEmpty } from '@theia/core/lib/common/objects';
import { MaybePromise } from '@theia/core/lib/common/types'; import { MaybePromise } from '@theia/core/lib/common/types';
@@ -16,6 +16,7 @@ import {
} from './boards-service-provider'; } from './boards-service-provider';
import { naturalCompare } from '../../common/utils'; import { naturalCompare } from '../../common/utils';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state';
export namespace BoardsConfig { export namespace BoardsConfig {
export interface Config { export interface Config {
@@ -29,6 +30,7 @@ export namespace BoardsConfig {
readonly onConfigChange: (config: Config) => void; readonly onConfigChange: (config: Config) => void;
readonly onFocusNodeSet: (element: HTMLElement | undefined) => void; readonly onFocusNodeSet: (element: HTMLElement | undefined) => void;
readonly onFilteredTextDidChangeEvent: Event<string>; readonly onFilteredTextDidChangeEvent: Event<string>;
readonly onAppStateDidChange: Event<FrontendApplicationState>;
} }
export interface State extends Config { export interface State extends Config {
@@ -47,7 +49,7 @@ export abstract class Item<T> extends React.Component<{
missing?: boolean; missing?: boolean;
details?: string; details?: string;
}> { }> {
render(): React.ReactNode { override render(): React.ReactNode {
const { selected, label, missing, details } = this.props; const { selected, label, missing, details } = this.props;
const classNames = ['item']; const classNames = ['item'];
if (selected) { if (selected) {
@@ -99,14 +101,18 @@ export class BoardsConfig extends React.Component<
}; };
} }
componentDidMount() { override componentDidMount(): void {
this.updateBoards();
this.updatePorts(
this.props.boardsServiceProvider.availableBoards
.map(({ port }) => port)
.filter(notEmpty)
);
this.toDispose.pushAll([ this.toDispose.pushAll([
this.props.onAppStateDidChange((state) => {
if (state === 'ready') {
this.updateBoards();
this.updatePorts(
this.props.boardsServiceProvider.availableBoards
.map(({ port }) => port)
.filter(notEmpty)
);
}
}),
this.props.notificationCenter.onAttachedBoardsChanged((event) => this.props.notificationCenter.onAttachedBoardsChanged((event) =>
this.updatePorts( this.updatePorts(
event.newState.ports, event.newState.ports,
@@ -141,11 +147,11 @@ export class BoardsConfig extends React.Component<
]); ]);
} }
componentWillUnmount(): void { override componentWillUnmount(): void {
this.toDispose.dispose(); this.toDispose.dispose();
} }
protected fireConfigChanged() { protected fireConfigChanged(): void {
const { selectedBoard, selectedPort } = this.state; const { selectedBoard, selectedPort } = this.state;
this.props.onConfigChange({ selectedBoard, selectedPort }); this.props.onConfigChange({ selectedBoard, selectedPort });
} }
@@ -250,7 +256,7 @@ export class BoardsConfig extends React.Component<
this.props.onFocusNodeSet(element || undefined); this.props.onFocusNodeSet(element || undefined);
}; };
render(): React.ReactNode { override render(): React.ReactNode {
return ( return (
<div className="body"> <div className="body">
{this.renderContainer('boards', this.renderBoards.bind(this))} {this.renderContainer('boards', this.renderBoards.bind(this))}

View File

@@ -1,5 +1,5 @@
import * as PQueue from 'p-queue'; import * as PQueue from 'p-queue';
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { CommandRegistry } from '@theia/core/lib/common/command'; import { CommandRegistry } from '@theia/core/lib/common/command';
import { MenuModelRegistry } from '@theia/core/lib/common/menu'; import { MenuModelRegistry } from '@theia/core/lib/common/menu';
import { import {
@@ -13,6 +13,7 @@ import { BoardsDataStore } from './boards-data-store';
import { MainMenuManager } from '../../common/main-menu-manager'; import { MainMenuManager } from '../../common/main-menu-manager';
import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus'; import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
@injectable() @injectable()
export class BoardsDataMenuUpdater implements FrontendApplicationContribution { export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
@@ -31,11 +32,20 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
@inject(BoardsServiceProvider) @inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider; protected readonly boardsServiceClient: BoardsServiceProvider;
@inject(FrontendApplicationStateService)
private readonly appStateService: FrontendApplicationStateService;
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
protected readonly toDisposeOnBoardChange = new DisposableCollection(); protected readonly toDisposeOnBoardChange = new DisposableCollection();
async onStart(): Promise<void> { async onStart(): Promise<void> {
this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard); this.appStateService
.reachedState('ready')
.then(() =>
this.updateMenuActions(
this.boardsServiceClient.boardsConfig.selectedBoard
)
);
this.boardsDataStore.onChanged(() => this.boardsDataStore.onChanged(() =>
this.updateMenuActions( this.updateMenuActions(
this.boardsServiceClient.boardsConfig.selectedBoard this.boardsServiceClient.boardsConfig.selectedBoard

View File

@@ -1,4 +1,4 @@
import { injectable, inject, named } from 'inversify'; import { injectable, inject, named } from '@theia/core/shared/inversify';
import { ILogger } from '@theia/core/lib/common/logger'; import { ILogger } from '@theia/core/lib/common/logger';
import { deepClone } from '@theia/core/lib/common/objects'; import { deepClone } from '@theia/core/lib/common/objects';
import { Event, Emitter } from '@theia/core/lib/common/event'; import { Event, Emitter } from '@theia/core/lib/common/event';

View File

@@ -1,4 +1,4 @@
import { inject, injectable, postConstruct } from 'inversify'; import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import { import {
BoardsPackage, BoardsPackage,
BoardsService, BoardsService,
@@ -30,7 +30,7 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
} }
@postConstruct() @postConstruct()
protected init(): void { protected override init(): void {
super.init(); super.init();
this.toDispose.pushAll([ this.toDispose.pushAll([
this.notificationCenter.onPlatformInstalled(() => this.notificationCenter.onPlatformInstalled(() =>
@@ -42,7 +42,7 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
]); ]);
} }
protected async install({ protected override async install({
item, item,
progressId, progressId,
version, version,
@@ -63,7 +63,7 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
); );
} }
protected async uninstall({ protected override async uninstall({
item, item,
progressId, progressId,
}: { }: {

View File

@@ -1,4 +1,4 @@
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { Emitter } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { ILogger } from '@theia/core/lib/common/logger'; import { ILogger } from '@theia/core/lib/common/logger';
import { CommandService } from '@theia/core/lib/common/command'; import { CommandService } from '@theia/core/lib/common/command';

View File

@@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from '@theia/core/shared/react-dom';
import { CommandRegistry } from '@theia/core/lib/common/command'; import { CommandRegistry } from '@theia/core/lib/common/command';
import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Port } from '../../common/protocol'; import { Port } from '../../common/protocol';
@@ -41,7 +41,7 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
} }
} }
render(): React.ReactNode { override render(): React.ReactNode {
return ReactDOM.createPortal(this.renderNode(), this.dropdownElement); return ReactDOM.createPortal(this.renderNode(), this.dropdownElement);
} }
@@ -130,13 +130,13 @@ export class BoardsToolBarItem extends React.Component<
}); });
} }
componentDidMount() { override componentDidMount(): void {
this.props.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => this.props.boardsServiceClient.onAvailableBoardsChanged((availableBoards) =>
this.setState({ availableBoards }) this.setState({ availableBoards })
); );
} }
componentWillUnmount(): void { override componentWillUnmount(): void {
this.toDispose.dispose(); this.toDispose.dispose();
} }
@@ -161,7 +161,7 @@ export class BoardsToolBarItem extends React.Component<
event.nativeEvent.stopImmediatePropagation(); event.nativeEvent.stopImmediatePropagation();
}; };
render(): React.ReactNode { override render(): React.ReactNode {
const { coords, availableBoards } = this.state; const { coords, availableBoards } = this.state;
const boardsConfig = this.props.boardsServiceClient.boardsConfig; const boardsConfig = this.props.boardsServiceClient.boardsConfig;
const title = BoardsConfig.Config.toString(boardsConfig, { const title = BoardsConfig.Config.toString(boardsConfig, {

View File

@@ -1,4 +1,4 @@
import { injectable } from 'inversify'; import { injectable } from '@theia/core/shared/inversify';
import { BoardsListWidget } from './boards-list-widget'; import { BoardsListWidget } from './boards-list-widget';
import { BoardsPackage } from '../../common/protocol/boards-service'; import { BoardsPackage } from '../../common/protocol/boards-service';
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution'; import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
@@ -18,7 +18,7 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
}); });
} }
async initializeLayout(): Promise<void> { override async initializeLayout(): Promise<void> {
this.openView(); this.openView();
} }
} }

View File

@@ -1,4 +1,4 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
export type ProgressBarProps = { export type ProgressBarProps = {
percent?: number; percent?: number;

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import * as moment from 'moment'; import * as moment from 'moment';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import { isOSX, isWindows } from '@theia/core/lib/common/os'; import { isOSX, isWindows } from '@theia/core/lib/common/os';
@@ -22,13 +22,13 @@ export class About extends Contribution {
@inject(ConfigService) @inject(ConfigService)
protected readonly configService: ConfigService; protected readonly configService: ConfigService;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(About.Commands.ABOUT_APP, { registry.registerCommand(About.Commands.ABOUT_APP, {
execute: () => this.showAbout(), execute: () => this.showAbout(),
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, { registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, {
commandId: About.Commands.ABOUT_APP.id, commandId: About.Commands.ABOUT_APP.id,
label: nls.localize( label: nls.localize(

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
import { import {
@@ -10,19 +10,20 @@ import {
} from './contribution'; } from './contribution';
import { FileDialogService } from '@theia/filesystem/lib/browser'; import { FileDialogService } from '@theia/filesystem/lib/browser';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
@injectable() @injectable()
export class AddFile extends SketchContribution { export class AddFile extends SketchContribution {
@inject(FileDialogService) @inject(FileDialogService)
protected readonly fileDialogService: FileDialogService; protected readonly fileDialogService: FileDialogService;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(AddFile.Commands.ADD_FILE, { registry.registerCommand(AddFile.Commands.ADD_FILE, {
execute: () => this.addFile(), execute: () => this.addFile(),
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
commandId: AddFile.Commands.ADD_FILE.id, commandId: AddFile.Commands.ADD_FILE.id,
label: nls.localize('arduino/contributions/addFile', 'Add File') + '...', label: nls.localize('arduino/contributions/addFile', 'Add File') + '...',
@@ -32,7 +33,7 @@ export class AddFile extends SketchContribution {
protected async addFile(): Promise<void> { protected async addFile(): Promise<void> {
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
if (!sketch) { if (!CurrentSketch.isValid(sketch)) {
return; return;
} }
const toAddUri = await this.fileDialogService.showOpenDialog({ const toAddUri = await this.fileDialogService.showOpenDialog({

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs'; import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
@@ -28,13 +28,13 @@ export class AddZipLibrary extends SketchContribution {
@inject(LibraryService) @inject(LibraryService)
protected readonly libraryService: LibraryService; protected readonly libraryService: LibraryService;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(AddZipLibrary.Commands.ADD_ZIP_LIBRARY, { registry.registerCommand(AddZipLibrary.Commands.ADD_ZIP_LIBRARY, {
execute: () => this.addZipLibrary(), execute: () => this.addZipLibrary(),
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
const includeLibMenuPath = [ const includeLibMenuPath = [
...ArduinoMenus.SKETCH__UTILS_GROUP, ...ArduinoMenus.SKETCH__UTILS_GROUP,
'0_include', '0_include',

View File

@@ -1,4 +1,4 @@
import { injectable } from 'inversify'; import { injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import * as dateFormat from 'dateformat'; import * as dateFormat from 'dateformat';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
@@ -10,16 +10,17 @@ import {
MenuModelRegistry, MenuModelRegistry,
} from './contribution'; } from './contribution';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
@injectable() @injectable()
export class ArchiveSketch extends SketchContribution { export class ArchiveSketch extends SketchContribution {
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, { registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, {
execute: () => this.archiveSketch(), execute: () => this.archiveSketch(),
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id, commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id,
label: nls.localize('arduino/sketch/archiveSketch', 'Archive Sketch'), label: nls.localize('arduino/sketch/archiveSketch', 'Archive Sketch'),
@@ -32,7 +33,7 @@ export class ArchiveSketch extends SketchContribution {
this.sketchServiceClient.currentSketch(), this.sketchServiceClient.currentSketch(),
this.configService.getConfiguration(), this.configService.getConfiguration(),
]); ]);
if (!sketch) { if (!CurrentSketch.isValid(sketch)) {
return; return;
} }
const archiveBasename = `${sketch.name}-${dateFormat( const archiveBasename = `${sketch.name}-${dateFormat(

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import { MenuModelRegistry } from '@theia/core/lib/common/menu'; import { MenuModelRegistry } from '@theia/core/lib/common/menu';
import { import {
@@ -47,7 +47,7 @@ export class BoardSelection extends SketchContribution {
protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection(); protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection();
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, { registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, {
execute: async () => { execute: async () => {
const { selectedBoard, selectedPort } = const { selectedBoard, selectedPort } =
@@ -100,19 +100,20 @@ PID: ${PID}`;
}); });
} }
onStart(): void { override onStart(): void {
this.notificationCenter.onPlatformInstalled(() => this.updateMenus());
this.notificationCenter.onPlatformUninstalled(() => this.updateMenus());
this.boardsServiceProvider.onBoardsConfigChanged(() => this.updateMenus());
this.boardsServiceProvider.onAvailableBoardsChanged(() =>
this.updateMenus()
);
this.boardsServiceProvider.onAvailablePortsChanged(() =>
this.updateMenus()
);
}
override async onReady(): Promise<void> {
this.updateMenus(); this.updateMenus();
this.notificationCenter.onPlatformInstalled(this.updateMenus.bind(this));
this.notificationCenter.onPlatformUninstalled(this.updateMenus.bind(this));
this.boardsServiceProvider.onBoardsConfigChanged(
this.updateMenus.bind(this)
);
this.boardsServiceProvider.onAvailableBoardsChanged(
this.updateMenus.bind(this)
);
this.boardsServiceProvider.onAvailablePortsChanged(
this.updateMenus.bind(this)
);
} }
protected async updateMenus(): Promise<void> { protected async updateMenus(): Promise<void> {

View File

@@ -1,12 +1,9 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { OutputChannelManager } from '@theia/output/lib/browser/output-channel';
import { CoreService } from '../../common/protocol';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
import { BoardsDataStore } from '../boards/boards-data-store'; import { BoardsDataStore } from '../boards/boards-data-store';
import { SerialConnectionManager } from '../serial/serial-connection-manager';
import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { import {
SketchContribution, CoreServiceContribution,
Command, Command,
CommandRegistry, CommandRegistry,
MenuModelRegistry, MenuModelRegistry,
@@ -14,29 +11,20 @@ import {
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
@injectable() @injectable()
export class BurnBootloader extends SketchContribution { export class BurnBootloader extends CoreServiceContribution {
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(SerialConnectionManager)
protected readonly serialConnection: SerialConnectionManager;
@inject(BoardsDataStore) @inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore; protected readonly boardsDataStore: BoardsDataStore;
@inject(BoardsServiceProvider) @inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider; protected readonly boardsServiceClientImpl: BoardsServiceProvider;
@inject(OutputChannelManager) override registerCommands(registry: CommandRegistry): void {
protected readonly outputChannelManager: OutputChannelManager;
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, { registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, {
execute: () => this.burnBootloader(), execute: () => this.burnBootloader(),
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, { registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, {
commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id, commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id,
label: nls.localize( label: nls.localize(
@@ -60,9 +48,15 @@ export class BurnBootloader extends SketchContribution {
this.preferences.get('arduino.upload.verify'), this.preferences.get('arduino.upload.verify'),
this.preferences.get('arduino.upload.verbose'), this.preferences.get('arduino.upload.verbose'),
]); ]);
const board = {
...boardsConfig.selectedBoard,
name: boardsConfig.selectedBoard?.name || '',
fqbn,
};
this.outputChannelManager.getChannel('Arduino').clear(); this.outputChannelManager.getChannel('Arduino').clear();
await this.coreService.burnBootloader({ await this.coreService.burnBootloader({
fqbn, board,
programmer, programmer,
port, port,
verify, verify,
@@ -78,15 +72,7 @@ export class BurnBootloader extends SketchContribution {
} }
); );
} catch (e) { } catch (e) {
let errorMessage = ""; this.handleError(e);
if (typeof e === "string") {
errorMessage = e;
} else {
errorMessage = e.toString();
}
this.messageService.error(errorMessage);
} finally {
await this.serialConnection.reconnectAfterUpload();
} }
} }
} }

View File

@@ -1,12 +1,10 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { toArray } from '@phosphor/algorithm';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor'; import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell'; import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
import { SaveAsSketch } from './save-as-sketch';
import { import {
SketchContribution, SketchContribution,
Command, Command,
@@ -23,90 +21,21 @@ import { nls } from '@theia/core/lib/common';
@injectable() @injectable()
export class Close extends SketchContribution { export class Close extends SketchContribution {
@inject(EditorManager) @inject(EditorManager)
protected readonly editorManager: EditorManager; protected override readonly editorManager: EditorManager;
protected shell: ApplicationShell; protected shell: ApplicationShell;
onStart(app: FrontendApplication): void { override onStart(app: FrontendApplication): void {
this.shell = app.shell; this.shell = app.shell;
} }
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(Close.Commands.CLOSE, { registry.registerCommand(Close.Commands.CLOSE, {
execute: async () => { execute: () => remote.getCurrentWindow().close()
// Close current editor if closeable.
const { currentEditor } = this.editorManager;
if (currentEditor && currentEditor.title.closable) {
currentEditor.close();
return;
}
// Close current widget from the main area if possible.
const { currentWidget } = this.shell;
if (currentWidget) {
const currentWidgetInMain = toArray(
this.shell.mainPanel.widgets()
).find((widget) => widget === currentWidget);
if (currentWidgetInMain && currentWidgetInMain.title.closable) {
return currentWidgetInMain.close();
}
}
// Close the sketch (window).
const sketch = await this.sketchServiceClient.currentSketch();
if (!sketch) {
return;
}
const isTemp = await this.sketchService.isTemp(sketch);
const uri = await this.sketchServiceClient.currentSketchFile();
if (!uri) {
return;
}
if (isTemp && (await this.wasTouched(uri))) {
const { response } = await remote.dialog.showMessageBox({
type: 'question',
buttons: [
nls.localize(
'vscode/abstractTaskService/saveBeforeRun.dontSave',
"Don't Save"
),
nls.localize('vscode/issueMainService/cancel', 'Cancel'),
nls.localize(
'vscode/abstractTaskService/saveBeforeRun.save',
'Save'
),
],
message: nls.localize(
'arduino/common/saveChangesToSketch',
'Do you want to save changes to this sketch before closing?'
),
detail: nls.localize(
'arduino/common/loseChanges',
"If you don't save, your changes will be lost."
),
});
if (response === 1) {
// Cancel
return;
}
if (response === 2) {
// Save
const saved = await this.commandService.executeCommand(
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
{ openAfterMove: false, execOnlyIfTemp: true }
);
if (!saved) {
// If it was not saved, do bail the close.
return;
}
}
}
window.close();
},
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: Close.Commands.CLOSE.id, commandId: Close.Commands.CLOSE.id,
label: nls.localize('vscode/editor.contribution/close', 'Close'), label: nls.localize('vscode/editor.contribution/close', 'Close'),
@@ -114,7 +43,7 @@ export class Close extends SketchContribution {
}); });
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: Close.Commands.CLOSE.id, command: Close.Commands.CLOSE.id,
keybinding: 'CtrlCmd+W', keybinding: 'CtrlCmd+W',

View File

@@ -0,0 +1,656 @@
import {
Command,
CommandRegistry,
Disposable,
DisposableCollection,
Emitter,
MaybePromise,
nls,
notEmpty,
} from '@theia/core';
import { ApplicationShell, FrontendApplication } from '@theia/core/lib/browser';
import URI from '@theia/core/lib/common/uri';
import { inject, injectable } from '@theia/core/shared/inversify';
import {
Location,
Range,
} from '@theia/core/shared/vscode-languageserver-protocol';
import {
EditorWidget,
TextDocumentChangeEvent,
} from '@theia/editor/lib/browser';
import {
EditorDecoration,
TrackedRangeStickiness,
} from '@theia/editor/lib/browser/decorations/editor-decoration';
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import * as monaco from '@theia/monaco-editor-core';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
import { ProtocolToMonacoConverter } from '@theia/monaco/lib/browser/protocol-to-monaco-converter';
import { CoreError } from '../../common/protocol/core-service';
import {
ArduinoPreferences,
ErrorRevealStrategy,
} from '../arduino-preferences';
import { InoSelector } from '../ino-selectors';
import { fullRange } from '../utils/monaco';
import { Contribution } from './contribution';
import { CoreErrorHandler } from './core-error-handler';
interface ErrorDecoration {
/**
* This is the unique ID of the decoration given by `monaco`.
*/
readonly id: string;
/**
* The resource this decoration belongs to.
*/
readonly uri: string;
}
namespace ErrorDecoration {
export function rangeOf(
{ id, uri }: ErrorDecoration,
editorProvider: (uri: string) => Promise<MonacoEditor | undefined>
): Promise<monaco.Range | undefined>;
export function rangeOf(
{ id, uri }: ErrorDecoration,
editorProvider: MonacoEditor
): monaco.Range | undefined;
export function rangeOf(
{ id, uri }: ErrorDecoration,
editorProvider:
| ((uri: string) => Promise<MonacoEditor | undefined>)
| MonacoEditor
): MaybePromise<monaco.Range | undefined> {
if (editorProvider instanceof MonacoEditor) {
const control = editorProvider.getControl();
const model = control.getModel();
if (model) {
return control
.getDecorationsInRange(fullRange(model))
?.find(({ id: candidateId }) => id === candidateId)?.range;
}
return undefined;
}
return editorProvider(uri).then((editor) => {
if (editor) {
return rangeOf({ id, uri }, editor);
}
return undefined;
});
}
// export async function rangeOf(
// { id, uri }: ErrorDecoration,
// editorProvider:
// | ((uri: string) => Promise<MonacoEditor | undefined>)
// | MonacoEditor
// ): Promise<monaco.Range | undefined> {
// const editor =
// editorProvider instanceof MonacoEditor
// ? editorProvider
// : await editorProvider(uri);
// if (editor) {
// const control = editor.getControl();
// const model = control.getModel();
// if (model) {
// return control
// .getDecorationsInRange(fullRange(model))
// ?.find(({ id: candidateId }) => id === candidateId)?.range;
// }
// }
// return undefined;
// }
export function sameAs(
left: ErrorDecoration,
right: ErrorDecoration
): boolean {
return left.id === right.id && left.uri === right.uri;
}
}
@injectable()
export class CompilerErrors
extends Contribution
implements monaco.languages.CodeLensProvider
{
@inject(EditorManager)
private readonly editorManager: EditorManager;
@inject(ProtocolToMonacoConverter)
private readonly p2m: ProtocolToMonacoConverter;
@inject(MonacoToProtocolConverter)
private readonly mp2: MonacoToProtocolConverter;
@inject(CoreErrorHandler)
private readonly coreErrorHandler: CoreErrorHandler;
@inject(ArduinoPreferences)
private readonly preferences: ArduinoPreferences;
private readonly errors: ErrorDecoration[] = [];
private readonly onDidChangeEmitter = new monaco.Emitter<this>();
private readonly currentErrorDidChangEmitter = new Emitter<ErrorDecoration>();
private readonly onCurrentErrorDidChange =
this.currentErrorDidChangEmitter.event;
private readonly toDisposeOnCompilerErrorDidChange =
new DisposableCollection();
private shell: ApplicationShell | undefined;
private revealStrategy = ErrorRevealStrategy.Default;
private currentError: ErrorDecoration | undefined;
private get currentErrorIndex(): number {
const current = this.currentError;
if (!current) {
return -1;
}
return this.errors.findIndex((error) =>
ErrorDecoration.sameAs(error, current)
);
}
override onStart(app: FrontendApplication): void {
this.shell = app.shell;
monaco.languages.registerCodeLensProvider(InoSelector, this);
this.coreErrorHandler.onCompilerErrorsDidChange((errors) =>
this.filter(errors).then(this.handleCompilerErrorsDidChange.bind(this))
);
this.onCurrentErrorDidChange(async (error) => {
const range = await ErrorDecoration.rangeOf(error, (uri) =>
this.monacoEditor(uri)
);
if (!range) {
console.warn(
'compiler-errors',
`Could not find range of decoration: ${error.id}`
);
return;
}
const editor = await this.revealLocationInEditor({
uri: error.uri,
range: this.mp2.asRange(range),
});
if (!editor) {
console.warn(
'compiler-errors',
`Failed to mark error ${error.id} as the current one.`
);
}
});
this.preferences.ready.then(() => {
this.preferences.onPreferenceChanged(({ preferenceName, newValue }) => {
if (preferenceName === 'arduino.compile.revealRange') {
this.revealStrategy = ErrorRevealStrategy.is(newValue)
? newValue
: ErrorRevealStrategy.Default;
}
});
});
}
override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(CompilerErrors.Commands.NEXT_ERROR, {
execute: () => {
const index = this.currentErrorIndex;
if (index < 0) {
console.warn(
'compiler-errors',
`Could not advance to next error. Unknown current error.`
);
return;
}
const nextError =
this.errors[index === this.errors.length - 1 ? 0 : index + 1];
this.markAsCurrentError(nextError);
},
isEnabled: () => !!this.currentError && this.errors.length > 1,
});
registry.registerCommand(CompilerErrors.Commands.PREVIOUS_ERROR, {
execute: () => {
const index = this.currentErrorIndex;
if (index < 0) {
console.warn(
'compiler-errors',
`Could not advance to previous error. Unknown current error.`
);
return;
}
const previousError =
this.errors[index === 0 ? this.errors.length - 1 : index - 1];
this.markAsCurrentError(previousError);
},
isEnabled: () => !!this.currentError && this.errors.length > 1,
});
}
get onDidChange(): monaco.IEvent<this> {
return this.onDidChangeEmitter.event;
}
async provideCodeLenses(
model: monaco.editor.ITextModel,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_token: monaco.CancellationToken
): Promise<monaco.languages.CodeLensList> {
const lenses: monaco.languages.CodeLens[] = [];
if (
this.currentError &&
this.currentError.uri === model.uri.toString() &&
this.errors.length > 1
) {
const range = await ErrorDecoration.rangeOf(this.currentError, (uri) =>
this.monacoEditor(uri)
);
if (range) {
lenses.push(
{
range,
command: {
id: CompilerErrors.Commands.PREVIOUS_ERROR.id,
title: nls.localize(
'arduino/editor/previousError',
'Previous Error'
),
arguments: [this.currentError],
},
},
{
range,
command: {
id: CompilerErrors.Commands.NEXT_ERROR.id,
title: nls.localize('arduino/editor/nextError', 'Next Error'),
arguments: [this.currentError],
},
}
);
}
}
return {
lenses,
dispose: () => {
/* NOOP */
},
};
}
private async handleCompilerErrorsDidChange(
errors: CoreError.Compiler[]
): Promise<void> {
this.toDisposeOnCompilerErrorDidChange.dispose();
const compilerErrorsPerResource = this.groupByResource(
await this.filter(errors)
);
const decorations = await this.decorateEditors(compilerErrorsPerResource);
this.errors.push(...decorations.errors);
this.toDisposeOnCompilerErrorDidChange.pushAll([
Disposable.create(() => (this.errors.length = 0)),
Disposable.create(() => this.onDidChangeEmitter.fire(this)),
...(await Promise.all([
decorations.dispose,
this.trackEditors(
compilerErrorsPerResource,
(editor) =>
editor.editor.onSelectionChanged((selection) =>
this.handleSelectionChange(editor, selection)
),
(editor) =>
editor.onDidDispose(() =>
this.handleEditorDidDispose(editor.editor.uri.toString())
),
(editor) =>
editor.editor.onDocumentContentChanged((event) =>
this.handleDocumentContentChange(editor, event)
)
),
])),
]);
const currentError = this.errors[0];
if (currentError) {
await this.markAsCurrentError(currentError);
}
}
private async filter(
errors: CoreError.Compiler[]
): Promise<CoreError.Compiler[]> {
if (!errors.length) {
return [];
}
await this.preferences.ready;
if (this.preferences['arduino.compile.experimental']) {
return errors;
}
// Always shows maximum one error; hence the code lens navigation is unavailable.
return [errors[0]];
}
private async decorateEditors(
errors: Map<string, CoreError.Compiler[]>
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
const composite = await Promise.all(
[...errors.entries()].map(([uri, errors]) =>
this.decorateEditor(uri, errors)
)
);
return {
dispose: new DisposableCollection(
...composite.map(({ dispose }) => dispose)
),
errors: composite.reduce(
(acc, { errors }) => acc.concat(errors),
[] as ErrorDecoration[]
),
};
}
private async decorateEditor(
uri: string,
errors: CoreError.Compiler[]
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
const editor = await this.editorManager.getByUri(new URI(uri));
if (!editor) {
return { dispose: Disposable.NULL, errors: [] };
}
const oldDecorations = editor.editor.deltaDecorations({
oldDecorations: [],
newDecorations: errors.map((error) =>
this.compilerErrorDecoration(error.location.range)
),
});
return {
dispose: Disposable.create(() => {
if (editor) {
editor.editor.deltaDecorations({
oldDecorations,
newDecorations: [],
});
}
}),
errors: oldDecorations.map((id) => ({ id, uri })),
};
}
private compilerErrorDecoration(range: Range): EditorDecoration {
return {
range,
options: {
isWholeLine: true,
className: 'compiler-error',
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
},
};
}
/**
* Tracks the selection in all editors that have an error. If the editor selection overlaps one of the compiler error's range, mark as current error.
*/
private handleSelectionChange(editor: EditorWidget, selection: Range): void {
const monacoEditor = this.monacoEditor(editor);
if (!monacoEditor) {
return;
}
const uri = monacoEditor.uri.toString();
const monacoSelection = this.p2m.asRange(selection);
console.log(
'compiler-errors',
`Handling selection change in editor ${uri}. New (monaco) selection: ${monacoSelection.toJSON()}`
);
const calculatePriority = (
candidateErrorRange: monaco.Range,
currentSelection: monaco.Range
) => {
console.trace(
'compiler-errors',
`Candidate error range: ${candidateErrorRange.toJSON()}`
);
console.trace(
'compiler-errors',
`Current selection range: ${currentSelection.toJSON()}`
);
if (candidateErrorRange.intersectRanges(currentSelection)) {
console.trace('Intersects.');
return { score: 2 };
}
if (
candidateErrorRange.startLineNumber <=
currentSelection.startLineNumber &&
candidateErrorRange.endLineNumber >= currentSelection.endLineNumber
) {
console.trace('Same line.');
return { score: 1 };
}
console.trace('No match');
return undefined;
};
const error = this.errors
.filter((error) => error.uri === uri)
.map((error) => ({
error,
range: ErrorDecoration.rangeOf(error, monacoEditor),
}))
.map(({ error, range }) => {
if (range) {
const priority = calculatePriority(range, monacoSelection);
if (priority) {
return { ...priority, error };
}
}
return undefined;
})
.filter(notEmpty)
.sort((left, right) => right.score - left.score) // highest first
.map(({ error }) => error)
.shift();
if (error) {
this.markAsCurrentError(error);
} else {
console.info(
'compiler-errors',
`New (monaco) selection ${monacoSelection.toJSON()} does not intersect any error locations. Skipping.`
);
}
}
/**
* This code does not deal with resource deletion, but tracks editor dispose events. It does not matter what was the cause of the editor disposal.
* If editor closes, delete the decorators.
*/
private handleEditorDidDispose(uri: string): void {
let i = this.errors.length;
// `splice` re-indexes the array. It's better to "iterate and modify" from the last element.
while (i--) {
const error = this.errors[i];
if (error.uri === uri) {
this.errors.splice(i, 1);
}
}
this.onDidChangeEmitter.fire(this);
}
/**
* If a document change "destroys" the range of the decoration, the decoration must be removed.
*/
private handleDocumentContentChange(
editor: EditorWidget,
event: TextDocumentChangeEvent
): void {
const monacoEditor = this.monacoEditor(editor);
if (!monacoEditor) {
return;
}
// A decoration location can be "destroyed", hence should be deleted when:
// - deleting range (start != end AND text is empty)
// - inserting text into range (start != end AND text is not empty)
// Filter unrelated delta changes to spare the CPU.
const relevantChanges = event.contentChanges.filter(
({ range: { start, end } }) =>
start.line !== end.line || start.character !== end.character
);
if (!relevantChanges.length) {
return;
}
const resolvedMarkers = this.errors
.filter((error) => error.uri === event.document.uri)
.map((error, index) => {
const range = ErrorDecoration.rangeOf(error, monacoEditor);
if (range) {
return { error, range, index };
}
return undefined;
})
.filter(notEmpty);
const decorationIdsToRemove = relevantChanges
.map(({ range }) => this.p2m.asRange(range))
.map((changeRange) =>
resolvedMarkers.filter(({ range: decorationRange }) =>
changeRange.containsRange(decorationRange)
)
)
.reduce((acc, curr) => acc.concat(curr), [])
.map(({ error, index }) => {
this.errors.splice(index, 1);
return error.id;
});
if (!decorationIdsToRemove.length) {
return;
}
monacoEditor.getControl().deltaDecorations(decorationIdsToRemove, []);
this.onDidChangeEmitter.fire(this);
}
private async trackEditors(
errors: Map<string, CoreError.Compiler[]>,
...track: ((editor: EditorWidget) => Disposable)[]
): Promise<Disposable> {
return new DisposableCollection(
...(await Promise.all(
Array.from(errors.keys()).map(async (uri) => {
const editor = await this.editorManager.getByUri(new URI(uri));
if (!editor) {
return Disposable.NULL;
}
return new DisposableCollection(...track.map((t) => t(editor)));
})
))
);
}
private async markAsCurrentError(error: ErrorDecoration): Promise<void> {
const index = this.errors.findIndex((candidate) =>
ErrorDecoration.sameAs(candidate, error)
);
if (index < 0) {
console.warn(
'compiler-errors',
`Failed to mark error ${
error.id
} as the current one. Error is unknown. Known errors are: ${this.errors.map(
({ id }) => id
)}`
);
return;
}
const newError = this.errors[index];
if (
!this.currentError ||
!ErrorDecoration.sameAs(this.currentError, newError)
) {
this.currentError = this.errors[index];
console.log(
'compiler-errors',
`Current error changed to ${this.currentError.id}`
);
this.currentErrorDidChangEmitter.fire(this.currentError);
this.onDidChangeEmitter.fire(this);
}
}
// The double editor activation logic is required: https://github.com/eclipse-theia/theia/issues/11284
private async revealLocationInEditor(
location: Location
): Promise<EditorWidget | undefined> {
const { uri, range } = location;
const editor = await this.editorManager.getByUri(new URI(uri), {
mode: 'activate',
});
if (editor && this.shell) {
// to avoid flickering, reveal the range here and not with `getByUri`, because it uses `at: 'center'` for the reveal option.
// TODO: check the community reaction whether it is better to set the focus at the error marker. it might cause flickering even if errors are close to each other
editor.editor.revealRange(range, { at: this.revealStrategy });
const activeWidget = await this.shell.activateWidget(editor.id);
if (!activeWidget) {
console.warn(
'compiler-errors',
`editor widget activation has failed. editor widget ${editor.id} expected to be the active one.`
);
return editor;
}
if (editor !== activeWidget) {
console.warn(
'compiler-errors',
`active widget was not the same as previously activated editor. editor widget ID ${editor.id}, active widget ID: ${activeWidget.id}`
);
}
return editor;
}
console.warn(
'compiler-errors',
`could not found editor widget for URI: ${uri}`
);
return undefined;
}
private groupByResource(
errors: CoreError.Compiler[]
): Map<string, CoreError.Compiler[]> {
return errors.reduce((acc, curr) => {
const {
location: { uri },
} = curr;
let errors = acc.get(uri);
if (!errors) {
errors = [];
acc.set(uri, errors);
}
errors.push(curr);
return acc;
}, new Map<string, CoreError.Compiler[]>());
}
private monacoEditor(widget: EditorWidget): MonacoEditor | undefined;
private monacoEditor(uri: string): Promise<MonacoEditor | undefined>;
private monacoEditor(
uriOrWidget: string | EditorWidget
): MaybePromise<MonacoEditor | undefined> {
if (uriOrWidget instanceof EditorWidget) {
const editor = uriOrWidget.editor;
if (editor instanceof MonacoEditor) {
return editor;
}
return undefined;
} else {
return this.editorManager
.getByUri(new URI(uriOrWidget))
.then((editor) => {
if (editor) {
return this.monacoEditor(editor);
}
return undefined;
});
}
}
}
export namespace CompilerErrors {
export namespace Commands {
export const NEXT_ERROR: Command = {
id: 'arduino-editor-next-error',
};
export const PREVIOUS_ERROR: Command = {
id: 'arduino-editor-previous-error',
};
}
}

View File

@@ -1,4 +1,9 @@
import { inject, injectable, interfaces } from 'inversify'; import {
inject,
injectable,
interfaces,
postConstruct,
} from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { ILogger } from '@theia/core/lib/common/logger'; import { ILogger } from '@theia/core/lib/common/logger';
import { Saveable } from '@theia/core/lib/browser/saveable'; import { Saveable } from '@theia/core/lib/browser/saveable';
@@ -9,7 +14,7 @@ import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { MessageService } from '@theia/core/lib/common/message-service'; import { MessageService } from '@theia/core/lib/common/message-service';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import { open, OpenerService } from '@theia/core/lib/browser/opener-service'; import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
import { OutputChannelManager } from '@theia/output/lib/browser/output-channel';
import { import {
MenuModelRegistry, MenuModelRegistry,
MenuContribution, MenuContribution,
@@ -34,14 +39,24 @@ import {
} from '@theia/core/lib/common/command'; } from '@theia/core/lib/common/command';
import { EditorMode } from '../editor-mode'; import { EditorMode } from '../editor-mode';
import { SettingsService } from '../dialogs/settings/settings'; import { SettingsService } from '../dialogs/settings/settings';
import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl'; import {
CurrentSketch,
SketchesServiceClientImpl,
} from '../../common/protocol/sketches-service-client-impl';
import { import {
SketchesService, SketchesService,
ConfigService, ConfigService,
FileSystemExt, FileSystemExt,
Sketch, Sketch,
CoreService,
CoreError,
} from '../../common/protocol'; } from '../../common/protocol';
import { ArduinoPreferences } from '../arduino-preferences'; import { ArduinoPreferences } from '../arduino-preferences';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { CoreErrorHandler } from './core-error-handler';
import { nls } from '@theia/core';
import { OutputChannelManager } from '../theia/output/output-channel';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
export { export {
Command, Command,
@@ -84,15 +99,31 @@ export abstract class Contribution
@inject(SettingsService) @inject(SettingsService)
protected readonly settingsService: SettingsService; protected readonly settingsService: SettingsService;
@inject(FrontendApplicationStateService)
protected readonly appStateService: FrontendApplicationStateService;
@postConstruct()
protected init(): void {
this.appStateService.reachedState('ready').then(() => this.onReady());
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
onStart(app: FrontendApplication): MaybePromise<void> {} onStart(app: FrontendApplication): MaybePromise<void> {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
registerCommands(registry: CommandRegistry): void {} registerCommands(registry: CommandRegistry): void {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
registerMenus(registry: MenuModelRegistry): void {} registerMenus(registry: MenuModelRegistry): void {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
registerKeybindings(registry: KeybindingRegistry): void {} registerKeybindings(registry: KeybindingRegistry): void {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars
registerToolbarItems(registry: TabBarToolbarRegistry): void {} registerToolbarItems(registry: TabBarToolbarRegistry): void {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onReady(): MaybePromise<void> {}
} }
@injectable() @injectable()
@@ -127,7 +158,7 @@ export abstract class SketchContribution extends Contribution {
protected async sourceOverride(): Promise<Record<string, string>> { protected async sourceOverride(): Promise<Record<string, string>> {
const override: Record<string, string> = {}; const override: Record<string, string> = {};
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
if (sketch) { if (CurrentSketch.isValid(sketch)) {
for (const editor of this.editorManager.all) { for (const editor of this.editorManager.all) {
const uri = editor.editor.uri; const uri = editor.editor.uri;
if (Saveable.isDirty(editor) && Sketch.isInSketch(uri, sketch)) { if (Saveable.isDirty(editor) && Sketch.isInSketch(uri, sketch)) {
@@ -139,8 +170,58 @@ export abstract class SketchContribution extends Contribution {
} }
} }
@injectable()
export class CoreServiceContribution extends SketchContribution {
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(CoreErrorHandler)
protected readonly coreErrorHandler: CoreErrorHandler;
@inject(ClipboardService)
private readonly clipboardService: ClipboardService;
protected handleError(error: unknown): void {
this.coreErrorHandler.tryHandle(error);
this.tryToastErrorMessage(error);
}
private tryToastErrorMessage(error: unknown): void {
let message: undefined | string = undefined;
if (CoreError.is(error)) {
message = error.message;
} else if (error instanceof Error) {
message = error.message;
} else if (typeof error === 'string') {
message = error;
} else {
try {
message = JSON.stringify(error);
} catch {}
}
if (message) {
const copyAction = nls.localize(
'arduino/coreContribution/copyError',
'Copy error messages'
);
this.messageService.error(message, copyAction).then(async (action) => {
if (action === copyAction) {
const content = await this.outputChannelManager.contentOfChannel(
'Arduino'
);
if (content) {
this.clipboardService.writeText(content);
}
}
});
} else {
throw error;
}
}
}
export namespace Contribution { export namespace Contribution {
export function configure<T>( export function configure(
bind: interfaces.Bind, bind: interfaces.Bind,
serviceIdentifier: typeof Contribution serviceIdentifier: typeof Contribution
): void { ): void {

View File

@@ -0,0 +1,32 @@
import { Emitter, Event } from '@theia/core';
import { injectable } from '@theia/core/shared/inversify';
import { CoreError } from '../../common/protocol/core-service';
@injectable()
export class CoreErrorHandler {
private readonly compilerErrors: CoreError.Compiler[] = [];
private readonly compilerErrorsDidChangeEmitter = new Emitter<
CoreError.Compiler[]
>();
tryHandle(error: unknown): void {
if (CoreError.is(error)) {
this.compilerErrors.length = 0;
this.compilerErrors.push(...error.data.filter(CoreError.Compiler.is));
this.fireCompilerErrorsDidChange();
}
}
reset(): void {
this.compilerErrors.length = 0;
this.fireCompilerErrorsDidChange();
}
get onCompilerErrorsDidChange(): Event<CoreError.Compiler[]> {
return this.compilerErrorsDidChangeEmitter.event;
}
private fireCompilerErrorsDidChange(): void {
this.compilerErrorsDidChangeEmitter.fire(this.compilerErrors.slice());
}
}

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { Event, Emitter } from '@theia/core/lib/common/event'; import { Event, Emitter } from '@theia/core/lib/common/event';
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
@@ -12,7 +12,8 @@ import {
SketchContribution, SketchContribution,
TabBarToolbarRegistry, TabBarToolbarRegistry,
} from './contribution'; } from './contribution';
import { nls } from '@theia/core/lib/common'; import { MaybePromise, nls } from '@theia/core/lib/common';
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
@injectable() @injectable()
export class Debug extends SketchContribution { export class Debug extends SketchContribution {
@@ -66,7 +67,7 @@ export class Debug extends SketchContribution {
onDidChange: this.onDisabledMessageDidChange as Event<void>, onDidChange: this.onDisabledMessageDidChange as Event<void>,
}; };
onStart(): void { override onStart(): void {
this.onDisabledMessageDidChange( this.onDisabledMessageDidChange(
() => () =>
(this.debugToolbarItem.tooltip = `${ (this.debugToolbarItem.tooltip = `${
@@ -79,55 +80,18 @@ export class Debug extends SketchContribution {
: Debug.Commands.START_DEBUGGING.label : Debug.Commands.START_DEBUGGING.label
}`) }`)
); );
const refreshState = async (
board: Board | undefined = this.boardsServiceProvider.boardsConfig
.selectedBoard
) => {
if (!board) {
this.disabledMessage = nls.localize(
'arduino/common/noBoardSelected',
'No board selected'
);
return;
}
const fqbn = board.fqbn;
if (!fqbn) {
this.disabledMessage = nls.localize(
'arduino/debug/noPlatformInstalledFor',
"Platform is not installed for '{0}'",
board.name
);
return;
}
const details = await this.boardService.getBoardDetails({ fqbn });
if (!details) {
this.disabledMessage = nls.localize(
'arduino/debug/noPlatformInstalledFor',
"Platform is not installed for '{0}'",
board.name
);
return;
}
const { debuggingSupported } = details;
if (!debuggingSupported) {
this.disabledMessage = nls.localize(
'arduino/debug/debuggingNotSupported',
"Debugging is not supported by '{0}'",
board.name
);
} else {
this.disabledMessage = undefined;
}
};
this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) =>
refreshState(selectedBoard) this.refreshState(selectedBoard)
); );
this.notificationCenter.onPlatformInstalled(() => refreshState()); this.notificationCenter.onPlatformInstalled(() => this.refreshState());
this.notificationCenter.onPlatformUninstalled(() => refreshState()); this.notificationCenter.onPlatformUninstalled(() => this.refreshState());
refreshState();
} }
registerCommands(registry: CommandRegistry): void { override onReady(): MaybePromise<void> {
this.refreshState();
}
override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(Debug.Commands.START_DEBUGGING, { registry.registerCommand(Debug.Commands.START_DEBUGGING, {
execute: () => this.startDebug(), execute: () => this.startDebug(),
isVisible: (widget) => isVisible: (widget) =>
@@ -136,10 +100,51 @@ export class Debug extends SketchContribution {
}); });
} }
registerToolbarItems(registry: TabBarToolbarRegistry): void { override registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem(this.debugToolbarItem); registry.registerItem(this.debugToolbarItem);
} }
private async refreshState(
board: Board | undefined = this.boardsServiceProvider.boardsConfig
.selectedBoard
): Promise<void> {
if (!board) {
this.disabledMessage = nls.localize(
'arduino/common/noBoardSelected',
'No board selected'
);
return;
}
const fqbn = board.fqbn;
if (!fqbn) {
this.disabledMessage = nls.localize(
'arduino/debug/noPlatformInstalledFor',
"Platform is not installed for '{0}'",
board.name
);
return;
}
const details = await this.boardService.getBoardDetails({ fqbn });
if (!details) {
this.disabledMessage = nls.localize(
'arduino/debug/noPlatformInstalledFor',
"Platform is not installed for '{0}'",
board.name
);
return;
}
const { debuggingSupported } = details;
if (!debuggingSupported) {
this.disabledMessage = nls.localize(
'arduino/debug/debuggingNotSupported',
"Debugging is not supported by '{0}'",
board.name
);
} else {
this.disabledMessage = undefined;
}
}
protected async startDebug( protected async startDebug(
board: Board | undefined = this.boardsServiceProvider.boardsConfig board: Board | undefined = this.boardsServiceProvider.boardsConfig
.selectedBoard .selectedBoard
@@ -156,7 +161,7 @@ export class Debug extends SketchContribution {
this.sketchServiceClient.currentSketch(), this.sketchServiceClient.currentSketch(),
this.executableService.list(), this.executableService.list(),
]); ]);
if (!sketch) { if (!CurrentSketch.isValid(sketch)) {
return; return;
} }
const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri( const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri(

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution'; import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service'; import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service'; import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service';
@@ -12,6 +12,8 @@ import {
} from './contribution'; } from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import type { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
import type { StandaloneCodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditor';
// TODO: [macOS]: to remove `Start Dictation...` and `Emoji & Symbol` see this thread: https://github.com/electron/electron/issues/8283#issuecomment-269522072 // TODO: [macOS]: to remove `Start Dictation...` and `Emoji & Symbol` see this thread: https://github.com/electron/electron/issues/8283#issuecomment-269522072
// Depends on https://github.com/eclipse-theia/theia/pull/7964 // Depends on https://github.com/eclipse-theia/theia/pull/7964
@@ -26,7 +28,7 @@ export class EditContributions extends Contribution {
@inject(PreferenceService) @inject(PreferenceService)
protected readonly preferences: PreferenceService; protected readonly preferences: PreferenceService;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(EditContributions.Commands.GO_TO_LINE, { registry.registerCommand(EditContributions.Commands.GO_TO_LINE, {
execute: () => this.run('editor.action.gotoLine'), execute: () => this.run('editor.action.gotoLine'),
}); });
@@ -91,7 +93,7 @@ ${value}
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
commandId: CommonCommands.CUT.id, commandId: CommonCommands.CUT.id,
order: '0', order: '0',
@@ -199,7 +201,7 @@ ${value}
}); });
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: EditContributions.Commands.COPY_FOR_FORUM.id, command: EditContributions.Commands.COPY_FOR_FORUM.id,
keybinding: 'CtrlCmd+Shift+C', keybinding: 'CtrlCmd+Shift+C',
@@ -250,10 +252,10 @@ ${value}
}); });
} }
protected async current(): Promise<monaco.editor.ICodeEditor | undefined> { protected async current(): Promise<ICodeEditor | StandaloneCodeEditor | undefined> {
return ( return (
this.codeEditorService.getFocusedCodeEditor() || this.codeEditorService.getFocusedCodeEditor() ||
this.codeEditorService.getActiveCodeEditor() this.codeEditorService.getActiveCodeEditor() || undefined
); );
} }

View File

@@ -1,5 +1,5 @@
import * as PQueue from 'p-queue'; import * as PQueue from 'p-queue';
import { inject, injectable, postConstruct } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { CommandHandler } from '@theia/core/lib/common/command'; import { CommandHandler } from '@theia/core/lib/common/command';
import { import {
MenuPath, MenuPath,
@@ -21,7 +21,7 @@ import {
MenuModelRegistry, MenuModelRegistry,
} from './contribution'; } from './contribution';
import { NotificationCenter } from '../notification-center'; import { NotificationCenter } from '../notification-center';
import { Board, Sketch, SketchContainer } from '../../common/protocol'; import { Board, SketchRef, SketchContainer } from '../../common/protocol';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
@injectable() @injectable()
@@ -43,8 +43,8 @@ export abstract class Examples extends SketchContribution {
protected readonly toDispose = new DisposableCollection(); protected readonly toDispose = new DisposableCollection();
@postConstruct() protected override init(): void {
init(): void { super.init();
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) =>
this.handleBoardChanged(selectedBoard) this.handleBoardChanged(selectedBoard)
); );
@@ -54,7 +54,7 @@ export abstract class Examples extends SketchContribution {
// NOOP // NOOP
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
try { try {
// This is a hack the ensures the desired menu ordering! We cannot use https://github.com/eclipse-theia/theia/pull/8377 due to ATL-222. // This is a hack the ensures the desired menu ordering! We cannot use https://github.com/eclipse-theia/theia/pull/8377 due to ATL-222.
const index = ArduinoMenus.FILE__EXAMPLES_SUBMENU.length - 1; const index = ArduinoMenus.FILE__EXAMPLES_SUBMENU.length - 1;
@@ -82,7 +82,7 @@ export abstract class Examples extends SketchContribution {
registerRecursively( registerRecursively(
sketchContainerOrPlaceholder: sketchContainerOrPlaceholder:
| SketchContainer | SketchContainer
| (Sketch | SketchContainer)[] | (SketchRef | SketchContainer)[]
| string, | string,
menuPath: MenuPath, menuPath: MenuPath,
pushToDispose: DisposableCollection = new DisposableCollection(), pushToDispose: DisposableCollection = new DisposableCollection(),
@@ -100,7 +100,7 @@ export abstract class Examples extends SketchContribution {
) )
); );
} else { } else {
const sketches: Sketch[] = []; const sketches: SketchRef[] = [];
const children: SketchContainer[] = []; const children: SketchContainer[] = [];
let submenuPath = menuPath; let submenuPath = menuPath;
@@ -161,7 +161,7 @@ export abstract class Examples extends SketchContribution {
@injectable() @injectable()
export class BuiltInExamples extends Examples { export class BuiltInExamples extends Examples {
onStart(): void { override async onReady(): Promise<void> {
this.register(); // no `await` this.register(); // no `await`
} }
@@ -201,13 +201,16 @@ export class LibraryExamples extends Examples {
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
onStart(): void { override onStart(): void {
this.register(); // no `await`
this.notificationCenter.onLibraryInstalled(() => this.register()); this.notificationCenter.onLibraryInstalled(() => this.register());
this.notificationCenter.onLibraryUninstalled(() => this.register()); this.notificationCenter.onLibraryUninstalled(() => this.register());
} }
protected handleBoardChanged(board: Board | undefined): void { override async onReady(): Promise<void> {
this.register(); // no `await`
}
protected override handleBoardChanged(board: Board | undefined): void {
this.register(board); this.register(board);
} }

View File

@@ -0,0 +1,79 @@
import { MaybePromise } from '@theia/core';
import { inject, injectable } from '@theia/core/shared/inversify';
import * as monaco from '@theia/monaco-editor-core';
import { Formatter } from '../../common/protocol/formatter';
import { InoSelector } from '../ino-selectors';
import { fullRange } from '../utils/monaco';
import { Contribution, URI } from './contribution';
@injectable()
export class Format
extends Contribution
implements
monaco.languages.DocumentRangeFormattingEditProvider,
monaco.languages.DocumentFormattingEditProvider
{
@inject(Formatter)
private readonly formatter: Formatter;
override onStart(): MaybePromise<void> {
monaco.languages.registerDocumentRangeFormattingEditProvider(
InoSelector,
this
);
monaco.languages.registerDocumentFormattingEditProvider(InoSelector, this);
}
async provideDocumentRangeFormattingEdits(
model: monaco.editor.ITextModel,
range: monaco.Range,
options: monaco.languages.FormattingOptions,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_token: monaco.CancellationToken
): Promise<monaco.languages.TextEdit[]> {
const text = await this.format(model, range, options);
return [{ range, text }];
}
async provideDocumentFormattingEdits(
model: monaco.editor.ITextModel,
options: monaco.languages.FormattingOptions,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_token: monaco.CancellationToken
): Promise<monaco.languages.TextEdit[]> {
const range = fullRange(model);
const text = await this.format(model, range, options);
return [{ range, text }];
}
/**
* From the currently opened workspaces (IDE2 has always one), it calculates all possible
* folder locations where the `.clang-format` file could be.
*/
private formatterConfigFolderUris(model: monaco.editor.ITextModel): string[] {
const editorUri = new URI(model.uri.toString());
return this.workspaceService
.tryGetRoots()
.map(({ resource }) => resource)
.filter((workspaceUri) => workspaceUri.isEqualOrParent(editorUri))
.map((uri) => uri.toString());
}
private format(
model: monaco.editor.ITextModel,
range: monaco.Range,
options: monaco.languages.FormattingOptions
): Promise<string> {
console.info(
`Formatting ${model.uri.toString()} [Range: ${JSON.stringify(
range.toJSON()
)}]`
);
const content = model.getValueInRange(range);
const formatterConfigFolderUris = this.formatterConfigFolderUris(model);
return this.formatter.format({
content,
formatterConfigFolderUris,
options,
});
}
}

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor'; import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { WindowService } from '@theia/core/lib/browser/window/window-service';
@@ -15,6 +15,7 @@ import {
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { IDEUpdaterCommands } from '../ide-updater/ide-updater-commands'; import { IDEUpdaterCommands } from '../ide-updater/ide-updater-commands';
import { ElectronCommands } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'; import { ElectronCommands } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution';
import * as monaco from '@theia/monaco-editor-core';
@injectable() @injectable()
export class Help extends Contribution { export class Help extends Contribution {
@@ -27,7 +28,7 @@ export class Help extends Contribution {
@inject(QuickInputService) @inject(QuickInputService)
protected readonly quickInputService: QuickInputService; protected readonly quickInputService: QuickInputService;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
const open = (url: string) => const open = (url: string) =>
this.windowService.openNewWindow(url, { external: true }); this.windowService.openNewWindow(url, { external: true });
const createOpenHandler = (url: string) => const createOpenHandler = (url: string) =>
@@ -91,7 +92,7 @@ export class Help extends Contribution {
); );
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.unregisterMenuAction({ registry.unregisterMenuAction({
commandId: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id, commandId: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id,
}); });
@@ -135,7 +136,7 @@ export class Help extends Contribution {
}); });
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: Help.Commands.FIND_IN_REFERENCE.id, command: Help.Commands.FIND_IN_REFERENCE.id,
keybinding: 'CtrlCmd+Shift+F', keybinding: 'CtrlCmd+Shift+F',

View File

@@ -1,5 +1,5 @@
import * as PQueue from 'p-queue'; import * as PQueue from 'p-queue';
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor'; import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { EditorManager } from '@theia/editor/lib/browser'; import { EditorManager } from '@theia/editor/lib/browser';
@@ -16,6 +16,8 @@ import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { SketchContribution, Command, CommandRegistry } from './contribution'; import { SketchContribution, Command, CommandRegistry } from './contribution';
import { NotificationCenter } from '../notification-center'; import { NotificationCenter } from '../notification-center';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import * as monaco from '@theia/monaco-editor-core';
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
@injectable() @injectable()
export class IncludeLibrary extends SketchContribution { export class IncludeLibrary extends SketchContribution {
@@ -29,7 +31,7 @@ export class IncludeLibrary extends SketchContribution {
protected readonly mainMenuManager: MainMenuManager; protected readonly mainMenuManager: MainMenuManager;
@inject(EditorManager) @inject(EditorManager)
protected readonly editorManager: EditorManager; protected override readonly editorManager: EditorManager;
@inject(NotificationCenter) @inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter; protected readonly notificationCenter: NotificationCenter;
@@ -43,8 +45,7 @@ export class IncludeLibrary extends SketchContribution {
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
protected readonly toDispose = new DisposableCollection(); protected readonly toDispose = new DisposableCollection();
onStart(): void { override onStart(): void {
this.updateMenuActions();
this.boardsServiceClient.onBoardsConfigChanged(() => this.boardsServiceClient.onBoardsConfigChanged(() =>
this.updateMenuActions() this.updateMenuActions()
); );
@@ -54,7 +55,11 @@ export class IncludeLibrary extends SketchContribution {
); );
} }
registerMenus(registry: MenuModelRegistry): void { override async onReady(): Promise<void> {
this.updateMenuActions();
}
override registerMenus(registry: MenuModelRegistry): void {
// `Include Library` submenu // `Include Library` submenu
const includeLibMenuPath = [ const includeLibMenuPath = [
...ArduinoMenus.SKETCH__UTILS_GROUP, ...ArduinoMenus.SKETCH__UTILS_GROUP,
@@ -77,7 +82,7 @@ export class IncludeLibrary extends SketchContribution {
}); });
} }
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, { registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, {
execute: async (arg) => { execute: async (arg) => {
if (LibraryPackage.is(arg)) { if (LibraryPackage.is(arg)) {
@@ -168,7 +173,7 @@ export class IncludeLibrary extends SketchContribution {
protected async includeLibrary(library: LibraryPackage): Promise<void> { protected async includeLibrary(library: LibraryPackage): Promise<void> {
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
if (!sketch) { if (!CurrentSketch.isValid(sketch)) {
return; return;
} }
// If the current editor is one of the additional files from the sketch, we use that. // If the current editor is one of the additional files from the sketch, we use that.

View File

@@ -1,5 +1,5 @@
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { injectable } from 'inversify'; import { injectable } from '@theia/core/shared/inversify';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
import { import {
@@ -14,7 +14,7 @@ import {
@injectable() @injectable()
export class NewSketch extends SketchContribution { export class NewSketch extends SketchContribution {
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(NewSketch.Commands.NEW_SKETCH, { registry.registerCommand(NewSketch.Commands.NEW_SKETCH, {
execute: () => this.newSketch(), execute: () => this.newSketch(),
}); });
@@ -25,7 +25,7 @@ export class NewSketch extends SketchContribution {
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: NewSketch.Commands.NEW_SKETCH.id, commandId: NewSketch.Commands.NEW_SKETCH.id,
label: nls.localize('arduino/sketch/new', 'New'), label: nls.localize('arduino/sketch/new', 'New'),
@@ -33,14 +33,14 @@ export class NewSketch extends SketchContribution {
}); });
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: NewSketch.Commands.NEW_SKETCH.id, command: NewSketch.Commands.NEW_SKETCH.id,
keybinding: 'CtrlCmd+N', keybinding: 'CtrlCmd+N',
}); });
} }
registerToolbarItems(registry: TabBarToolbarRegistry): void { override registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem({ registry.registerItem({
id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,
command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { WorkspaceServer } from '@theia/workspace/lib/common/workspace-protocol'; import { WorkspaceServer } from '@theia/workspace/lib/common/workspace-protocol';
import { import {
Disposable, Disposable,
@@ -35,18 +35,19 @@ export class OpenRecentSketch extends SketchContribution {
protected toDisposeBeforeRegister = new Map<string, DisposableCollection>(); protected toDisposeBeforeRegister = new Map<string, DisposableCollection>();
onStart(): void { override onStart(): void {
const refreshMenu = (sketches: Sketch[]) => {
this.register(sketches);
this.mainMenuManager.update();
};
this.notificationCenter.onRecentSketchesChanged(({ sketches }) => this.notificationCenter.onRecentSketchesChanged(({ sketches }) =>
refreshMenu(sketches) this.refreshMenu(sketches)
); );
this.sketchService.recentlyOpenedSketches().then(refreshMenu);
} }
registerMenus(registry: MenuModelRegistry): void { override async onReady(): Promise<void> {
this.sketchService
.recentlyOpenedSketches()
.then((sketches) => this.refreshMenu(sketches));
}
override registerMenus(registry: MenuModelRegistry): void {
registry.registerSubmenu( registry.registerSubmenu(
ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, ArduinoMenus.FILE__OPEN_RECENT_SUBMENU,
nls.localize('arduino/sketch/openRecent', 'Open Recent'), nls.localize('arduino/sketch/openRecent', 'Open Recent'),
@@ -54,6 +55,11 @@ export class OpenRecentSketch extends SketchContribution {
); );
} }
private refreshMenu(sketches: Sketch[]): void {
this.register(sketches);
this.mainMenuManager.update();
}
protected register(sketches: Sketch[]): void { protected register(sketches: Sketch[]): void {
const order = 0; const order = 0;
for (const sketch of sketches) { for (const sketch of sketches) {

View File

@@ -1,4 +1,4 @@
import { injectable } from 'inversify'; import { injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
@@ -13,13 +13,13 @@ import { nls } from '@theia/core/lib/common';
@injectable() @injectable()
export class OpenSketchExternal extends SketchContribution { export class OpenSketchExternal extends SketchContribution {
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(OpenSketchExternal.Commands.OPEN_EXTERNAL, { registry.registerCommand(OpenSketchExternal.Commands.OPEN_EXTERNAL, {
execute: () => this.openExternal(), execute: () => this.openExternal(),
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id,
label: nls.localize('arduino/sketch/showFolder', 'Show Sketch Folder'), label: nls.localize('arduino/sketch/showFolder', 'Show Sketch Folder'),
@@ -27,7 +27,7 @@ export class OpenSketchExternal extends SketchContribution {
}); });
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, command: OpenSketchExternal.Commands.OPEN_EXTERNAL.id,
keybinding: 'CtrlCmd+Alt+K', keybinding: 'CtrlCmd+Alt+K',

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import { MaybePromise } from '@theia/core/lib/common/types'; import { MaybePromise } from '@theia/core/lib/common/types';
import { Widget, ContextMenuRenderer } from '@theia/core/lib/browser'; import { Widget, ContextMenuRenderer } from '@theia/core/lib/browser';
@@ -43,7 +43,7 @@ export class OpenSketch extends SketchContribution {
protected readonly toDispose = new DisposableCollection(); protected readonly toDispose = new DisposableCollection();
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, { registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, {
execute: (arg) => execute: (arg) =>
Sketch.is(arg) ? this.openSketch(arg) : this.openSketch(), Sketch.is(arg) ? this.openSketch(arg) : this.openSketch(),
@@ -116,7 +116,7 @@ export class OpenSketch extends SketchContribution {
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: OpenSketch.Commands.OPEN_SKETCH.id, commandId: OpenSketch.Commands.OPEN_SKETCH.id,
label: nls.localize('vscode/workspaceActions/openFileFolder', 'Open...'), label: nls.localize('vscode/workspaceActions/openFileFolder', 'Open...'),
@@ -124,14 +124,14 @@ export class OpenSketch extends SketchContribution {
}); });
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: OpenSketch.Commands.OPEN_SKETCH.id, command: OpenSketch.Commands.OPEN_SKETCH.id,
keybinding: 'CtrlCmd+O', keybinding: 'CtrlCmd+O',
}); });
} }
registerToolbarItems(registry: TabBarToolbarRegistry): void { override registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem({ registry.registerItem({
id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,
command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,

View File

@@ -1,4 +1,4 @@
import { injectable } from 'inversify'; import { injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import { isOSX } from '@theia/core/lib/common/os'; import { isOSX } from '@theia/core/lib/common/os';
import { import {
@@ -13,7 +13,7 @@ import { nls } from '@theia/core/lib/common';
@injectable() @injectable()
export class QuitApp extends Contribution { export class QuitApp extends Contribution {
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
if (!isOSX) { if (!isOSX) {
registry.registerCommand(QuitApp.Commands.QUIT_APP, { registry.registerCommand(QuitApp.Commands.QUIT_APP, {
execute: () => remote.app.quit(), execute: () => remote.app.quit(),
@@ -21,7 +21,7 @@ export class QuitApp extends Contribution {
} }
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
// On macOS we will get the `Quit ${YOUR_APP_NAME}` menu item natively, no need to duplicate it. // On macOS we will get the `Quit ${YOUR_APP_NAME}` menu item natively, no need to duplicate it.
if (!isOSX) { if (!isOSX) {
registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, { registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, {
@@ -32,7 +32,7 @@ export class QuitApp extends Contribution {
} }
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
if (!isOSX) { if (!isOSX) {
registry.registerKeybinding({ registry.registerKeybinding({
command: QuitApp.Commands.QUIT_APP.id, command: QuitApp.Commands.QUIT_APP.id,

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import * as dateFormat from 'dateformat'; import * as dateFormat from 'dateformat';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
@@ -14,6 +14,7 @@ import { nls } from '@theia/core/lib/common';
import { ApplicationShell, NavigatableWidget, Saveable } from '@theia/core/lib/browser'; import { ApplicationShell, NavigatableWidget, Saveable } from '@theia/core/lib/browser';
import { EditorManager } from '@theia/editor/lib/browser'; import { EditorManager } from '@theia/editor/lib/browser';
import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
@injectable() @injectable()
export class SaveAsSketch extends SketchContribution { export class SaveAsSketch extends SketchContribution {
@@ -22,18 +23,18 @@ export class SaveAsSketch extends SketchContribution {
protected readonly applicationShell: ApplicationShell; protected readonly applicationShell: ApplicationShell;
@inject(EditorManager) @inject(EditorManager)
protected readonly editorManager: EditorManager; protected override readonly editorManager: EditorManager;
@inject(WindowService) @inject(WindowService)
protected readonly windowService: WindowService; protected readonly windowService: WindowService;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, { registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, {
execute: (args) => this.saveAs(args), execute: (args) => this.saveAs(args),
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
label: nls.localize('vscode/fileCommands/saveAs', 'Save As...'), label: nls.localize('vscode/fileCommands/saveAs', 'Save As...'),
@@ -41,7 +42,7 @@ export class SaveAsSketch extends SketchContribution {
}); });
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, command: SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
keybinding: 'CtrlCmd+Shift+S', keybinding: 'CtrlCmd+Shift+S',
@@ -59,7 +60,7 @@ export class SaveAsSketch extends SketchContribution {
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
): Promise<boolean> { ): Promise<boolean> {
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
if (!sketch) { if (!CurrentSketch.isValid(sketch)) {
return false; return false;
} }

View File

@@ -1,4 +1,4 @@
import { injectable } from 'inversify'; import { injectable } from '@theia/core/shared/inversify';
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution'; import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
@@ -12,10 +12,11 @@ import {
TabBarToolbarRegistry, TabBarToolbarRegistry,
} from './contribution'; } from './contribution';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
@injectable() @injectable()
export class SaveSketch extends SketchContribution { export class SaveSketch extends SketchContribution {
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH, { registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH, {
execute: () => this.saveSketch(), execute: () => this.saveSketch(),
}); });
@@ -27,7 +28,7 @@ export class SaveSketch extends SketchContribution {
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
commandId: SaveSketch.Commands.SAVE_SKETCH.id, commandId: SaveSketch.Commands.SAVE_SKETCH.id,
label: nls.localize('vscode/fileCommands/save', 'Save'), label: nls.localize('vscode/fileCommands/save', 'Save'),
@@ -35,14 +36,14 @@ export class SaveSketch extends SketchContribution {
}); });
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: SaveSketch.Commands.SAVE_SKETCH.id, command: SaveSketch.Commands.SAVE_SKETCH.id,
keybinding: 'CtrlCmd+S', keybinding: 'CtrlCmd+S',
}); });
} }
registerToolbarItems(registry: TabBarToolbarRegistry): void { override registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem({ registry.registerItem({
id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
@@ -53,7 +54,7 @@ export class SaveSketch extends SketchContribution {
async saveSketch(): Promise<void> { async saveSketch(): Promise<void> {
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
if (!sketch) { if (!CurrentSketch.isValid(sketch)) {
return; return;
} }
const isTemp = await this.sketchService.isTemp(sketch); const isTemp = await this.sketchService.isTemp(sketch);

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { import {
Command, Command,
MenuModelRegistry, MenuModelRegistry,
@@ -18,7 +18,7 @@ export class Settings extends SketchContribution {
protected settingsOpened = false; protected settingsOpened = false;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(Settings.Commands.OPEN, { registry.registerCommand(Settings.Commands.OPEN, {
execute: async () => { execute: async () => {
let settings: Preferences | undefined = undefined; let settings: Preferences | undefined = undefined;
@@ -39,7 +39,7 @@ export class Settings extends SketchContribution {
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.FILE__PREFERENCES_GROUP, { registry.registerMenuAction(ArduinoMenus.FILE__PREFERENCES_GROUP, {
commandId: Settings.Commands.OPEN.id, commandId: Settings.Commands.OPEN.id,
label: label:
@@ -52,7 +52,7 @@ export class Settings extends SketchContribution {
registry.registerSubmenu(ArduinoMenus.FILE__ADVANCED_SUBMENU, 'Advanced'); registry.registerSubmenu(ArduinoMenus.FILE__ADVANCED_SUBMENU, 'Advanced');
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: Settings.Commands.OPEN.id, command: Settings.Commands.OPEN.id,
keybinding: 'CtrlCmd+,', keybinding: 'CtrlCmd+,',

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution'; import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell'; import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import { WorkspaceCommands } from '@theia/workspace/lib/browser'; import { WorkspaceCommands } from '@theia/workspace/lib/browser';
@@ -19,7 +19,10 @@ import {
} from './contribution'; } from './contribution';
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl'; import {
CurrentSketch,
SketchesServiceClientImpl,
} from '../../common/protocol/sketches-service-client-impl';
import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider'; import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
@@ -35,7 +38,7 @@ export class SketchControl extends SketchContribution {
protected readonly contextMenuRenderer: ContextMenuRenderer; protected readonly contextMenuRenderer: ContextMenuRenderer;
@inject(EditorManager) @inject(EditorManager)
protected readonly editorManager: EditorManager; protected override readonly editorManager: EditorManager;
@inject(SketchesServiceClientImpl) @inject(SketchesServiceClientImpl)
protected readonly sketchesServiceClient: SketchesServiceClientImpl; protected readonly sketchesServiceClient: SketchesServiceClientImpl;
@@ -46,7 +49,7 @@ export class SketchControl extends SketchContribution {
protected readonly toDisposeBeforeCreateNewContextMenu = protected readonly toDisposeBeforeCreateNewContextMenu =
new DisposableCollection(); new DisposableCollection();
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand( registry.registerCommand(
SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR, SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR,
{ {
@@ -55,7 +58,7 @@ export class SketchControl extends SketchContribution {
execute: async () => { execute: async () => {
this.toDisposeBeforeCreateNewContextMenu.dispose(); this.toDisposeBeforeCreateNewContextMenu.dispose();
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
if (!sketch) { if (!CurrentSketch.isValid(sketch)) {
return; return;
} }
@@ -70,25 +73,22 @@ export class SketchControl extends SketchContribution {
return; return;
} }
const { mainFileUri, rootFolderFileUris } = const { mainFileUri, rootFolderFileUris } = sketch;
await this.sketchService.loadSketch(sketch.uri);
const uris = [mainFileUri, ...rootFolderFileUris]; const uris = [mainFileUri, ...rootFolderFileUris];
const currentSketch = const parentSketchUri = this.editorManager.currentEditor
await this.sketchesServiceClient.currentSketch();
const parentsketchUri = this.editorManager.currentEditor
?.getResourceUri() ?.getResourceUri()
?.toString(); ?.toString();
const parentsketch = await this.sketchService.getSketchFolder( const parentSketch = await this.sketchService.getSketchFolder(
parentsketchUri || '' parentSketchUri || ''
); );
// if the current file is in the current opened sketch, show extra menus // if the current file is in the current opened sketch, show extra menus
if ( if (
currentSketch && sketch &&
parentsketch && parentSketch &&
parentsketch.uri === currentSketch.uri && parentSketch.uri === sketch.uri &&
this.allowRename(parentsketch.uri) this.allowRename(parentSketch.uri)
) { ) {
this.menuRegistry.registerMenuAction( this.menuRegistry.registerMenuAction(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
@@ -122,10 +122,10 @@ export class SketchControl extends SketchContribution {
} }
if ( if (
currentSketch && sketch &&
parentsketch && parentSketch &&
parentsketch.uri === currentSketch.uri && parentSketch.uri === sketch.uri &&
this.allowDelete(parentsketch.uri) this.allowDelete(parentSketch.uri)
) { ) {
this.menuRegistry.registerMenuAction( this.menuRegistry.registerMenuAction(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
@@ -200,7 +200,7 @@ export class SketchControl extends SketchContribution {
); );
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction( registry.registerMenuAction(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
{ {
@@ -228,7 +228,7 @@ export class SketchControl extends SketchContribution {
); );
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: WorkspaceCommands.NEW_FILE.id, command: WorkspaceCommands.NEW_FILE.id,
keybinding: 'CtrlCmd+Shift+N', keybinding: 'CtrlCmd+Shift+N',
@@ -243,7 +243,7 @@ export class SketchControl extends SketchContribution {
}); });
} }
registerToolbarItems(registry: TabBarToolbarRegistry): void { override registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem({ registry.registerItem({
id: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, id: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id,
command: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, command: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id,

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { CommandHandler } from '@theia/core/lib/common/command'; import { CommandHandler } from '@theia/core/lib/common/command';
import { CommandRegistry, MenuModelRegistry } from './contribution'; import { CommandRegistry, MenuModelRegistry } from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
@@ -12,10 +12,10 @@ import { nls } from '@theia/core/lib/common';
@injectable() @injectable()
export class Sketchbook extends Examples { export class Sketchbook extends Examples {
@inject(CommandRegistry) @inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry; protected override readonly commandRegistry: CommandRegistry;
@inject(MenuModelRegistry) @inject(MenuModelRegistry)
protected readonly menuRegistry: MenuModelRegistry; protected override readonly menuRegistry: MenuModelRegistry;
@inject(MainMenuManager) @inject(MainMenuManager)
protected readonly mainMenuManager: MainMenuManager; protected readonly mainMenuManager: MainMenuManager;
@@ -23,11 +23,7 @@ export class Sketchbook extends Examples {
@inject(NotificationCenter) @inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter; protected readonly notificationCenter: NotificationCenter;
onStart(): void { override onStart(): void {
this.sketchService.getSketches({}).then((container) => {
this.register(container);
this.mainMenuManager.update();
});
this.sketchServiceClient.onSketchbookDidChange(() => { this.sketchServiceClient.onSketchbookDidChange(() => {
this.sketchService.getSketches({}).then((container) => { this.sketchService.getSketches({}).then((container) => {
this.register(container); this.register(container);
@@ -36,7 +32,14 @@ export class Sketchbook extends Examples {
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override async onReady(): Promise<void> {
this.sketchService.getSketches({}).then((container) => {
this.register(container);
this.mainMenuManager.update();
});
}
override registerMenus(registry: MenuModelRegistry): void {
registry.registerSubmenu( registry.registerSubmenu(
ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, ArduinoMenus.FILE__SKETCHBOOK_SUBMENU,
nls.localize('arduino/sketch/sketchbook', 'Sketchbook'), nls.localize('arduino/sketch/sketchbook', 'Sketchbook'),
@@ -53,7 +56,7 @@ export class Sketchbook extends Examples {
); );
} }
protected createHandler(uri: string): CommandHandler { protected override createHandler(uri: string): CommandHandler {
return { return {
execute: async () => { execute: async () => {
const sketch = await this.sketchService.loadSketch(uri); const sketch = await this.sketchService.loadSketch(uri);

View File

@@ -0,0 +1,78 @@
import { MessageService } from '@theia/core';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { inject, injectable } from '@theia/core/shared/inversify';
import { LocalStorageService } from '@theia/core/lib/browser';
import { nls } from '@theia/core/lib/common';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { ArduinoPreferences } from '../arduino-preferences';
import { SurveyNotificationService } from '../../common/protocol/survey-service';
const SURVEY_MESSAGE = nls.localize(
'arduino/survey/surveyMessage',
'Please help us improve by answering this super short survey. We value our community and would like to get to know our supporters a little better.'
);
const DO_NOT_SHOW_AGAIN = nls.localize(
'arduino/survey/dismissSurvey',
"Don't show again"
);
const GO_TO_SURVEY = nls.localize(
'arduino/survey/answerSurvey',
'Answer survey'
);
const SURVEY_BASE_URL = 'https://surveys.hotjar.com/';
const surveyId = '17887b40-e1f0-4bd6-b9f0-a37f229ccd8b';
@injectable()
export class SurveyNotification implements FrontendApplicationContribution {
@inject(MessageService)
private readonly messageService: MessageService;
@inject(LocalStorageService)
private readonly localStorageService: LocalStorageService;
@inject(WindowService)
private readonly windowService: WindowService;
@inject(ArduinoPreferences)
private readonly arduinoPreferences: ArduinoPreferences;
@inject(SurveyNotificationService)
private readonly surveyNotificationService: SurveyNotificationService;
onStart(): void {
this.arduinoPreferences.ready.then(async () => {
if (
(await this.surveyNotificationService.isFirstInstance()) &&
this.arduinoPreferences.get('arduino.survey.notification')
) {
const surveyAnswered = await this.localStorageService.getData(
this.surveyKey(surveyId)
);
if (surveyAnswered !== undefined) {
return;
}
const answer = await this.messageService.info(
SURVEY_MESSAGE,
DO_NOT_SHOW_AGAIN,
GO_TO_SURVEY
);
switch (answer) {
case GO_TO_SURVEY:
this.windowService.openNewWindow(SURVEY_BASE_URL + surveyId, {
external: true,
});
this.localStorageService.setData(this.surveyKey(surveyId), true);
break;
case DO_NOT_SHOW_AGAIN:
this.localStorageService.setData(this.surveyKey(surveyId), false);
break;
}
}
});
}
private surveyKey(id: string): string {
return `answered_survey:${id}`;
}
}

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { import {
Command, Command,
MenuModelRegistry, MenuModelRegistry,
@@ -39,7 +39,7 @@ export class UploadCertificate extends Contribution {
protected dialogOpened = false; protected dialogOpened = false;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(UploadCertificate.Commands.OPEN, { registry.registerCommand(UploadCertificate.Commands.OPEN, {
execute: async () => { execute: async () => {
try { try {
@@ -93,7 +93,7 @@ export class UploadCertificate extends Contribution {
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, { registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, {
commandId: UploadCertificate.Commands.OPEN.id, commandId: UploadCertificate.Commands.OPEN.id,
label: UploadCertificate.Commands.OPEN.label, label: UploadCertificate.Commands.OPEN.label,

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { import {
Command, Command,
MenuModelRegistry, MenuModelRegistry,
@@ -16,7 +16,7 @@ export class UploadFirmware extends Contribution {
protected dialogOpened = false; protected dialogOpened = false;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(UploadFirmware.Commands.OPEN, { registry.registerCommand(UploadFirmware.Commands.OPEN, {
execute: async () => { execute: async () => {
try { try {
@@ -30,7 +30,7 @@ export class UploadFirmware extends Contribution {
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, { registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, {
commandId: UploadFirmware.Commands.OPEN.id, commandId: UploadFirmware.Commands.OPEN.id,
label: UploadFirmware.Commands.OPEN.label, label: UploadFirmware.Commands.OPEN.label,

View File

@@ -1,13 +1,12 @@
import { inject, injectable, postConstruct } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { Emitter } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { BoardUserField, CoreService } from '../../common/protocol'; import { BoardUserField, CoreService } from '../../common/protocol';
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
import { BoardsDataStore } from '../boards/boards-data-store'; import { BoardsDataStore } from '../boards/boards-data-store';
import { SerialConnectionManager } from '../serial/serial-connection-manager';
import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { import {
SketchContribution, CoreServiceContribution,
Command, Command,
CommandRegistry, CommandRegistry,
MenuModelRegistry, MenuModelRegistry,
@@ -16,15 +15,10 @@ import {
} from './contribution'; } from './contribution';
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog'; import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
import { DisposableCollection, nls } from '@theia/core/lib/common'; import { DisposableCollection, nls } from '@theia/core/lib/common';
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
@injectable() @injectable()
export class UploadSketch extends SketchContribution { export class UploadSketch extends CoreServiceContribution {
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(SerialConnectionManager)
protected readonly serialConnection: SerialConnectionManager;
@inject(MenuModelRegistry) @inject(MenuModelRegistry)
protected readonly menuRegistry: MenuModelRegistry; protected readonly menuRegistry: MenuModelRegistry;
@@ -47,8 +41,8 @@ export class UploadSketch extends SketchContribution {
protected readonly menuActionsDisposables = new DisposableCollection(); protected readonly menuActionsDisposables = new DisposableCollection();
@postConstruct() protected override init(): void {
protected init(): void { super.init();
this.boardsServiceClientImpl.onBoardsConfigChanged(async () => { this.boardsServiceClientImpl.onBoardsConfigChanged(async () => {
const userFields = const userFields =
await this.boardsServiceClientImpl.selectedBoardUserFields(); await this.boardsServiceClientImpl.selectedBoardUserFields();
@@ -72,7 +66,7 @@ export class UploadSketch extends SketchContribution {
return fqbn + '|' + address; return fqbn + '|' + address;
} }
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, { registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, {
execute: async () => { execute: async () => {
const key = this.selectedFqbnAddress(); const key = this.selectedFqbnAddress();
@@ -134,7 +128,7 @@ export class UploadSketch extends SketchContribution {
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
this.menuActionsDisposables.dispose(); this.menuActionsDisposables.dispose();
this.menuActionsDisposables.push( this.menuActionsDisposables.push(
@@ -177,7 +171,7 @@ export class UploadSketch extends SketchContribution {
); );
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: UploadSketch.Commands.UPLOAD_SKETCH.id, command: UploadSketch.Commands.UPLOAD_SKETCH.id,
keybinding: 'CtrlCmd+U', keybinding: 'CtrlCmd+U',
@@ -188,7 +182,7 @@ export class UploadSketch extends SketchContribution {
}); });
} }
registerToolbarItems(registry: TabBarToolbarRegistry): void { override registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem({ registry.registerItem({
id: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, id: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id,
command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id,
@@ -204,16 +198,17 @@ export class UploadSketch extends SketchContribution {
return; return;
} }
// toggle the toolbar button and menu item state.
// uploadInProgress will be set to false whether the upload fails or not
this.uploadInProgress = true;
this.onDidChangeEmitter.fire();
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
if (!sketch) { if (!CurrentSketch.isValid(sketch)) {
return; return;
} }
try { try {
// toggle the toolbar button and menu item state.
// uploadInProgress will be set to false whether the upload fails or not
this.uploadInProgress = true;
this.coreErrorHandler.reset();
this.onDidChangeEmitter.fire();
const { boardsConfig } = this.boardsServiceClientImpl; const { boardsConfig } = this.boardsServiceClientImpl;
const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] = const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] =
await Promise.all([ await Promise.all([
@@ -226,8 +221,12 @@ export class UploadSketch extends SketchContribution {
this.sourceOverride(), this.sourceOverride(),
]); ]);
const board = {
...boardsConfig.selectedBoard,
name: boardsConfig.selectedBoard?.name || '',
fqbn,
};
let options: CoreService.Upload.Options | undefined = undefined; let options: CoreService.Upload.Options | undefined = undefined;
const sketchUri = sketch.uri;
const optimizeForDebug = this.editorMode.compileForDebug; const optimizeForDebug = this.editorMode.compileForDebug;
const { selectedPort } = boardsConfig; const { selectedPort } = boardsConfig;
const port = selectedPort; const port = selectedPort;
@@ -246,8 +245,8 @@ export class UploadSketch extends SketchContribution {
if (usingProgrammer) { if (usingProgrammer) {
const programmer = selectedProgrammer; const programmer = selectedProgrammer;
options = { options = {
sketchUri, sketch,
fqbn, board,
optimizeForDebug, optimizeForDebug,
programmer, programmer,
port, port,
@@ -258,8 +257,8 @@ export class UploadSketch extends SketchContribution {
}; };
} else { } else {
options = { options = {
sketchUri, sketch,
fqbn, board,
optimizeForDebug, optimizeForDebug,
port, port,
verbose, verbose,
@@ -279,18 +278,10 @@ export class UploadSketch extends SketchContribution {
{ timeout: 3000 } { timeout: 3000 }
); );
} catch (e) { } catch (e) {
let errorMessage = ''; this.handleError(e);
if (typeof e === 'string') {
errorMessage = e;
} else {
errorMessage = e.toString();
}
this.messageService.error(errorMessage);
} finally { } finally {
this.uploadInProgress = false; this.uploadInProgress = false;
this.onDidChangeEmitter.fire(); this.onDidChangeEmitter.fire();
setTimeout(() => this.serialConnection.reconnectAfterUpload(), 5000);
} }
} }
} }

View File

@@ -1,12 +1,11 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { Emitter } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { CoreService } from '../../common/protocol';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
import { BoardsDataStore } from '../boards/boards-data-store'; import { BoardsDataStore } from '../boards/boards-data-store';
import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { import {
SketchContribution, CoreServiceContribution,
Command, Command,
CommandRegistry, CommandRegistry,
MenuModelRegistry, MenuModelRegistry,
@@ -14,12 +13,10 @@ import {
TabBarToolbarRegistry, TabBarToolbarRegistry,
} from './contribution'; } from './contribution';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
@injectable() @injectable()
export class VerifySketch extends SketchContribution { export class VerifySketch extends CoreServiceContribution {
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(BoardsDataStore) @inject(BoardsDataStore)
protected readonly boardsDataStore: BoardsDataStore; protected readonly boardsDataStore: BoardsDataStore;
@@ -31,7 +28,7 @@ export class VerifySketch extends SketchContribution {
protected verifyInProgress = false; protected verifyInProgress = false;
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, { registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
execute: () => this.verifySketch(), execute: () => this.verifySketch(),
isEnabled: () => !this.verifyInProgress, isEnabled: () => !this.verifyInProgress,
@@ -50,7 +47,7 @@ export class VerifySketch extends SketchContribution {
}); });
} }
registerMenus(registry: MenuModelRegistry): void { override registerMenus(registry: MenuModelRegistry): void {
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
commandId: VerifySketch.Commands.VERIFY_SKETCH.id, commandId: VerifySketch.Commands.VERIFY_SKETCH.id,
label: nls.localize('arduino/sketch/verifyOrCompile', 'Verify/Compile'), label: nls.localize('arduino/sketch/verifyOrCompile', 'Verify/Compile'),
@@ -66,7 +63,7 @@ export class VerifySketch extends SketchContribution {
}); });
} }
registerKeybindings(registry: KeybindingRegistry): void { override registerKeybindings(registry: KeybindingRegistry): void {
registry.registerKeybinding({ registry.registerKeybinding({
command: VerifySketch.Commands.VERIFY_SKETCH.id, command: VerifySketch.Commands.VERIFY_SKETCH.id,
keybinding: 'CtrlCmd+R', keybinding: 'CtrlCmd+R',
@@ -77,7 +74,7 @@ export class VerifySketch extends SketchContribution {
}); });
} }
registerToolbarItems(registry: TabBarToolbarRegistry): void { override registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem({ registry.registerItem({
id: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, id: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id,
command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id,
@@ -95,14 +92,14 @@ export class VerifySketch extends SketchContribution {
// toggle the toolbar button and menu item state. // toggle the toolbar button and menu item state.
// verifyInProgress will be set to false whether the compilation fails or not // verifyInProgress will be set to false whether the compilation fails or not
this.verifyInProgress = true;
this.onDidChangeEmitter.fire();
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
if (!CurrentSketch.isValid(sketch)) {
if (!sketch) {
return; return;
} }
try { try {
this.verifyInProgress = true;
this.coreErrorHandler.reset();
this.onDidChangeEmitter.fire();
const { boardsConfig } = this.boardsServiceClientImpl; const { boardsConfig } = this.boardsServiceClientImpl;
const [fqbn, sourceOverride] = await Promise.all([ const [fqbn, sourceOverride] = await Promise.all([
this.boardsDataStore.appendConfigToFqbn( this.boardsDataStore.appendConfigToFqbn(
@@ -110,12 +107,17 @@ export class VerifySketch extends SketchContribution {
), ),
this.sourceOverride(), this.sourceOverride(),
]); ]);
const board = {
...boardsConfig.selectedBoard,
name: boardsConfig.selectedBoard?.name || '',
fqbn,
};
const verbose = this.preferences.get('arduino.compile.verbose'); const verbose = this.preferences.get('arduino.compile.verbose');
const compilerWarnings = this.preferences.get('arduino.compile.warnings'); const compilerWarnings = this.preferences.get('arduino.compile.warnings');
this.outputChannelManager.getChannel('Arduino').clear(); this.outputChannelManager.getChannel('Arduino').clear();
await this.coreService.compile({ await this.coreService.compile({
sketchUri: sketch.uri, sketch,
fqbn, board,
optimizeForDebug: this.editorMode.compileForDebug, optimizeForDebug: this.editorMode.compileForDebug,
verbose, verbose,
exportBinaries, exportBinaries,
@@ -127,13 +129,7 @@ export class VerifySketch extends SketchContribution {
{ timeout: 3000 } { timeout: 3000 }
); );
} catch (e) { } catch (e) {
let errorMessage = ""; this.handleError(e);
if (typeof e === "string") {
errorMessage = e;
} else {
errorMessage = e.toString();
}
this.messageService.error(errorMessage);
} finally { } finally {
this.verifyInProgress = false; this.verifyInProgress = false;
this.onDidChangeEmitter.fire(); this.onDidChangeEmitter.fire();

View File

@@ -1,4 +1,4 @@
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import * as createPaths from './create-paths'; import * as createPaths from './create-paths';
import { posix } from './create-paths'; import { posix } from './create-paths';
import { AuthenticationClientService } from '../auth/authentication-client-service'; import { AuthenticationClientService } from '../auth/authentication-client-service';
@@ -117,11 +117,11 @@ export class CreateApi {
headers, headers,
}) })
).sketches; ).sketches;
if (partialSketches.length != 0) { if (partialSketches.length !== 0) {
result.sketches = result.sketches.concat(partialSketches); result.sketches = result.sketches.concat(partialSketches);
} }
currentOffset = currentOffset + limit; currentOffset = currentOffset + limit;
} while (partialSketches.length != 0); } while (partialSketches.length !== 0);
result.sketches.forEach((sketch) => this.sketchCache.addSketch(sketch)); result.sketches.forEach((sketch) => this.sketchCache.addSketch(sketch));
return result.sketches; return result.sketches;

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { Event } from '@theia/core/lib/common/event'; import { Event } from '@theia/core/lib/common/event';
import { import {

View File

@@ -0,0 +1,149 @@
{
"name": "Arduino dark",
"type": "dark",
"colors": {
"list.highlightForeground": "#0ca1a6",
"list.activeSelectionForeground": "#dae3e3",
"list.activeSelectionBackground": "#434f54",
"list.inactiveSelectionForeground": "#dae3e3",
"list.inactiveSelectionBackground": "#434f54",
"list.hoverBackground": "#1f272a",
"progressBar.background": "#005c5f",
"editor.background": "#1f272a",
"editor.foreground": "#dae3e3",
"editor.lineHighlightBackground": "#434f5410",
"editor.selectionBackground": "#f1c40f",
"editorCursor.foreground": "#434f54",
"editorWhitespace.foreground": "#bfbfbf",
"editorWidget.background": "#171e21",
"focusBorder": "#dae3e3",
"menubar.selectionBackground": "#ffffff",
"menubar.selectionForeground": "#212121",
"menu.selectionBackground": "#dae3e3",
"menu.selectionForeground": "#212121",
"editorGroupHeader.tabsBackground": "#171e21",
"button.background": "#0ca1a6",
"titleBar.activeBackground": "#171e21",
"titleBar.activeForeground": "#dae3e3",
"terminal.background": "#000000",
"terminal.foreground": "#e0e0e0",
"dropdown.border": "#7fcbcd",
"dropdown.background": "#2c353a",
"dropdown.foreground": "#dae3e3",
"activityBar.background": "#171e21",
"activityBar.foreground": "#dae3e3",
"activityBar.inactiveForeground": "#4e5b61",
"activityBar.activeBorder": "#0ca1a6",
"statusBar.background": "#171e21",
"secondaryButton.background": "#ff000000",
"secondaryButton.foreground": "#dae3e3",
"secondaryButton.hoverBackground": "#434f54",
"arduino.branding.primary": "#0ca1a6",
"arduino.branding.secondary": "#b5c8c9",
"arduino.foreground": "#edf1f1",
"arduino.output.foreground": "#ffffff",
"arduino.output.background": "#000000",
"arduino.toolbar.hoverBackground": "#dae3e3",
"sideBar.background": "#101618",
"input.background": "#000000",
"foreground": "#dae3e3",
"settings.headerForeground": "#dae3e3",
"tree.indentGuidesStroke": "#374146",
"tab.unfocusedActiveForeground": "#dae3e3",
"tab.inactiveBackground": "#171e21"
},
"tokenColors": [
{
"name": "",
"settings": {
"foreground": "#dae3e3"
}
},
{
"name": "Comments",
"scope": "comment",
"settings": {
"foreground": "#7f8c8d"
}
},
{
"name": "Keywords Attributes",
"scope": [
"storage",
"support",
"string.quoted.single.c"
],
"settings": {
"foreground": "#0ca1a6"
}
},
{
"name": "literal",
"scope": [
"meta.function.c",
"entity.name.function",
"meta.function-call.c",
"variable.other"
],
"settings": {
"foreground": "#F39C12"
}
},
{
"name": "punctuation",
"scope": [
"punctuation.section",
"meta.function-call.c",
"meta.block.c",
"meta.function.c",
"variable",
"variable.name"
],
"settings": {
"foreground": "#dae3e3"
}
},
{
"name": "function preprocessor",
"scope": [
"entity.name.function.preprocessor.c",
"meta.preprocessor.macro.c"
],
"settings": {
"foreground": "#569CD6"
}
},
{
"name": "constants",
"scope": [
"string.quoted.double",
"string.quoted.other.lt-gt",
"constant"
],
"settings": {
"foreground": "#7fcbcd"
}
},
{
"name": "meta keywords",
"scope": [
"keyword.control",
"meta.preprocessor.c"
],
"settings": {
"foreground": "#C586C0"
}
},
{
"name": "numeric preprocessor",
"scope": [
"meta.preprocessor.macro.c",
"constant.numeric.preprocessor.c",
"meta.preprocessor.c"
],
"settings": {
"foreground": "#434f54"
}
}
]
}

View File

@@ -0,0 +1,149 @@
{
"name": "Arduino default",
"type": "default",
"colors": {
"list.highlightForeground": "#008184",
"list.activeSelectionForeground": "#4e5b61",
"list.activeSelectionBackground": "#dae3e3",
"list.inactiveSelectionForeground": "#4e5b61",
"list.inactiveSelectionBackground": "#dae3e3",
"list.hoverBackground": "#ecf1f1",
"progressBar.background": "#005c5f",
"editor.background": "#ffffff",
"editor.foreground": "#4e5b61",
"editor.lineHighlightBackground": "#434f5410",
"editor.selectionBackground": "#f1c40f",
"editorCursor.foreground": "#434f54",
"editorWhitespace.foreground": "#bfbfbf",
"editorWidget.background": "#f7f9f9",
"focusBorder": "#7fcbcd",
"menubar.selectionBackground": "#ffffff",
"menubar.selectionForeground": "#212121",
"menu.selectionBackground": "#dae3e3",
"menu.selectionForeground": "#212121",
"editorGroupHeader.tabsBackground": "#ecf1f1",
"button.background": "#7fcbcd",
"titleBar.activeBackground": "#006d70",
"titleBar.activeForeground": "#f7f9f9",
"terminal.background": "#000000",
"terminal.foreground": "#e0e0e0",
"dropdown.border": "#f7f9f9",
"dropdown.background": "#ffffff",
"dropdown.foreground": "#4e5b61",
"activityBar.background": "#ecf1f1",
"activityBar.foreground": "#4e5b61",
"activityBar.inactiveForeground": "#bdc7c7",
"activityBar.activeBorder": "#008184",
"statusBar.background": "#006d70",
"secondaryButton.background": "#ff000000",
"secondaryButton.foreground": "#008184",
"secondaryButton.hoverBackground": "#dae3e3",
"arduino.branding.primary": "#008184",
"arduino.branding.secondary": "#b5c8c9",
"arduino.foreground": "#edf1f1",
"arduino.output.foreground": "#ffffff",
"arduino.output.background": "#000000",
"arduino.toolbar.hoverBackground": "#f7f9f9",
"sideBar.background": "#f7f9f9",
"input.background": "#ffffff",
"foreground": "#4e5b61",
"settings.headerForeground": "#4e5b61",
"tree.indentGuidesStroke": "#dae3e3",
"tab.unfocusedActiveForeground": "#4e5b61",
"tab.inactiveBackground": "#ecf1f1"
},
"tokenColors": [
{
"name": "",
"settings": {
"foreground": "#434f54"
}
},
{
"name": "Comments",
"scope": "comment",
"settings": {
"foreground": "#95a5a6cc"
}
},
{
"name": "Keywords Attributes",
"scope": [
"storage",
"support",
"string.quoted.single.c"
],
"settings": {
"foreground": "#00979D"
}
},
{
"name": "literal",
"scope": [
"meta.function.c",
"entity.name.function",
"meta.function-call.c",
"variable.other"
],
"settings": {
"foreground": "#D35400"
}
},
{
"name": "punctuation",
"scope": [
"punctuation.section",
"meta.function-call.c",
"meta.block.c",
"meta.function.c",
"variable",
"variable.name"
],
"settings": {
"foreground": "#434f54"
}
},
{
"name": "function preprocessor",
"scope": [
"entity.name.function.preprocessor.c",
"meta.preprocessor.macro.c"
],
"settings": {
"foreground": "#9e846d"
}
},
{
"name": "constants",
"scope": [
"string.quoted.double",
"string.quoted.other.lt-gt",
"constant"
],
"settings": {
"foreground": "#005C5F"
}
},
{
"name": "meta keywords",
"scope": [
"keyword.control",
"meta.preprocessor.c"
],
"settings": {
"foreground": "#728E00"
}
},
{
"name": "numeric preprocessor",
"scope": [
"meta.preprocessor.macro.c",
"constant.numeric.preprocessor.c",
"meta.preprocessor.c"
],
"settings": {
"foreground": "#434f54"
}
}
]
}

View File

@@ -1,5 +1,5 @@
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import * as React from 'react'; import * as React from '@theia/core/shared/react';
export const CertificateAddComponent = ({ export const CertificateAddComponent = ({
addCertificate, addCertificate,
@@ -8,9 +8,12 @@ export const CertificateAddComponent = ({
}): React.ReactElement => { }): React.ReactElement => {
const [value, setValue] = React.useState(''); const [value, setValue] = React.useState('');
const handleChange = React.useCallback((event) => { const handleChange = React.useCallback(
setValue(event.target.value); (event: React.ChangeEvent<HTMLInputElement>) => {
}, []); setValue(event.target.value);
},
[]
);
return ( return (
<form <form

View File

@@ -1,4 +1,4 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
export const CertificateListComponent = ({ export const CertificateListComponent = ({
certificates, certificates,

View File

@@ -1,4 +1,4 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import Tippy from '@tippyjs/react'; import Tippy from '@tippyjs/react';
import { AvailableBoard } from '../../boards/boards-service-provider'; import { AvailableBoard } from '../../boards/boards-service-provider';
import { CertificateListComponent } from './certificate-list'; import { CertificateListComponent } from './certificate-list';

View File

@@ -1,9 +1,9 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { inject, injectable, postConstruct } from 'inversify'; import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import { DialogProps } from '@theia/core/lib/browser/dialogs'; import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../../theia/dialogs/dialogs'; import { AbstractDialog } from '../../theia/dialogs/dialogs';
import { Widget } from '@phosphor/widgets'; import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { Message } from '@phosphor/messaging'; import { Message } from '@theia/core/shared/@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { import {
AvailableBoard, AvailableBoard,
@@ -139,7 +139,7 @@ export class UploadCertificateDialog extends AbstractDialog<void> {
constructor( constructor(
@inject(UploadCertificateDialogProps) @inject(UploadCertificateDialogProps)
protected readonly props: UploadCertificateDialogProps protected override readonly props: UploadCertificateDialogProps
) { ) {
super({ super({
title: nls.localize( title: nls.localize(
@@ -155,7 +155,7 @@ export class UploadCertificateDialog extends AbstractDialog<void> {
return; return;
} }
protected onAfterAttach(msg: Message): void { protected override onAfterAttach(msg: Message): void {
if (this.widget.isAttached) { if (this.widget.isAttached) {
Widget.detach(this.widget); Widget.detach(this.widget);
} }
@@ -165,21 +165,21 @@ export class UploadCertificateDialog extends AbstractDialog<void> {
this.update(); this.update();
} }
protected onUpdateRequest(msg: Message): void { protected override onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg); super.onUpdateRequest(msg);
this.widget.update(); this.widget.update();
} }
protected onActivateRequest(msg: Message): void { protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg); super.onActivateRequest(msg);
this.widget.activate(); this.widget.activate();
} }
protected handleEnter(event: KeyboardEvent): boolean | void { protected override handleEnter(event: KeyboardEvent): boolean | void {
return false; return false;
} }
close(): void { override close(): void {
if (this.busy) { if (this.busy) {
return; return;
} }

View File

@@ -1,5 +1,5 @@
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { AvailableBoard } from '../../boards/boards-service-provider'; import { AvailableBoard } from '../../boards/boards-service-provider';
import { ArduinoSelect } from '../../widgets/arduino-select'; import { ArduinoSelect } from '../../widgets/arduino-select';

View File

@@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { Widget } from '@phosphor/widgets'; import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { Message } from '@phosphor/messaging'; import { Message } from '@theia/core/shared/@phosphor/messaging';
import { clipboard } from 'electron'; import { clipboard } from 'electron';
import { ReactWidget, DialogProps } from '@theia/core/lib/browser'; import { ReactWidget, DialogProps } from '@theia/core/lib/browser';
import { AbstractDialog } from '../theia/dialogs/dialogs'; import { AbstractDialog } from '../theia/dialogs/dialogs';
@@ -149,7 +149,7 @@ export class ShareSketchDialog extends AbstractDialog<void> {
constructor( constructor(
@inject(ShareSketchDialogProps) @inject(ShareSketchDialogProps)
protected readonly props: ShareSketchDialogProps protected override readonly props: ShareSketchDialogProps
) { ) {
super({ title: props.title }); super({ title: props.title });
this.contentNode.classList.add('arduino-share-sketch-dialog'); this.contentNode.classList.add('arduino-share-sketch-dialog');
@@ -159,7 +159,7 @@ export class ShareSketchDialog extends AbstractDialog<void> {
get value(): void { get value(): void {
return; return;
} }
protected onAfterAttach(msg: Message): void { protected override onAfterAttach(msg: Message): void {
if (this.widget.isAttached) { if (this.widget.isAttached) {
Widget.detach(this.widget); Widget.detach(this.widget);
} }
@@ -168,12 +168,12 @@ export class ShareSketchDialog extends AbstractDialog<void> {
this.update(); this.update();
} }
protected onUpdateRequest(msg: Message): void { protected override onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg); super.onUpdateRequest(msg);
this.widget.update(); this.widget.update();
} }
protected onActivateRequest(msg: Message): void { protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg); super.onActivateRequest(msg);
this.widget.activate(); this.widget.activate();
} }

View File

@@ -1,5 +1,5 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { Widget } from '@phosphor/widgets'; import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation'; import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
import { import {
ConfirmDialog, ConfirmDialog,
@@ -19,7 +19,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog {
constructor( constructor(
@inject(DoNotAskAgainDialogProps) @inject(DoNotAskAgainDialogProps)
protected readonly props: DoNotAskAgainDialogProps protected override readonly props: DoNotAskAgainDialogProps
) { ) {
super(props); super(props);
this.controlPanel.removeChild(this.errorMessageNode); this.controlPanel.removeChild(this.errorMessageNode);
@@ -42,7 +42,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog {
this.doNotAskAgainCheckbox.type = 'checkbox'; this.doNotAskAgainCheckbox.type = 'checkbox';
} }
protected async accept(): Promise<void> { protected override async accept(): Promise<void> {
if (!this.resolve) { if (!this.resolve) {
return; return;
} }
@@ -65,7 +65,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog {
} }
} }
protected setErrorMessage(error: DialogError): void { protected override setErrorMessage(error: DialogError): void {
if (this.acceptButton) { if (this.acceptButton) {
this.acceptButton.disabled = !DialogError.getResult(error); this.acceptButton.disabled = !DialogError.getResult(error);
} }

View File

@@ -1,5 +1,6 @@
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { Port } from '../../../common/protocol';
import { import {
ArduinoFirmwareUploader, ArduinoFirmwareUploader,
FirmwareInfo, FirmwareInfo,
@@ -20,7 +21,7 @@ export const FirmwareUploaderComponent = ({
availableBoards: AvailableBoard[]; availableBoards: AvailableBoard[];
firmwareUploader: ArduinoFirmwareUploader; firmwareUploader: ArduinoFirmwareUploader;
updatableFqbns: string[]; updatableFqbns: string[];
flashFirmware: (firmware: FirmwareInfo, port: string) => Promise<any>; flashFirmware: (firmware: FirmwareInfo, port: Port) => Promise<any>;
isOpen: any; isOpen: any;
}): React.ReactElement => { }): React.ReactElement => {
// boolean states for buttons // boolean states for buttons
@@ -81,7 +82,7 @@ export const FirmwareUploaderComponent = ({
const installStatus = const installStatus =
!!firmwareToFlash && !!firmwareToFlash &&
!!selectedBoard?.port && !!selectedBoard?.port &&
(await flashFirmware(firmwareToFlash, selectedBoard?.port.address)); (await flashFirmware(firmwareToFlash, selectedBoard?.port));
setInstallFeedback((installStatus && 'ok') || 'fail'); setInstallFeedback((installStatus && 'ok') || 'fail');
} catch { } catch {

View File

@@ -1,9 +1,13 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { inject, injectable, postConstruct } from 'inversify'; import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import { DialogProps } from '@theia/core/lib/browser/dialogs'; import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../../theia/dialogs/dialogs'; import { AbstractDialog } from '../../theia/dialogs/dialogs';
import { Widget } from '@phosphor/widgets'; import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { Message } from '@phosphor/messaging'; import { Message } from '@theia/core/shared/@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { import {
AvailableBoard, AvailableBoard,
@@ -15,6 +19,8 @@ import {
} from '../../../common/protocol/arduino-firmware-uploader'; } from '../../../common/protocol/arduino-firmware-uploader';
import { FirmwareUploaderComponent } from './firmware-uploader-component'; import { FirmwareUploaderComponent } from './firmware-uploader-component';
import { UploadFirmware } from '../../contributions/upload-firmware'; import { UploadFirmware } from '../../contributions/upload-firmware';
import { Port } from '../../../common/protocol';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
@injectable() @injectable()
export class UploadFirmwareDialogWidget extends ReactWidget { export class UploadFirmwareDialogWidget extends ReactWidget {
@@ -24,6 +30,9 @@ export class UploadFirmwareDialogWidget extends ReactWidget {
@inject(ArduinoFirmwareUploader) @inject(ArduinoFirmwareUploader)
protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader; protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader;
@inject(FrontendApplicationStateService)
private readonly appStatusService: FrontendApplicationStateService;
protected updatableFqbns: string[] = []; protected updatableFqbns: string[] = [];
protected availableBoards: AvailableBoard[] = []; protected availableBoards: AvailableBoard[] = [];
protected isOpen = new Object(); protected isOpen = new Object();
@@ -38,7 +47,8 @@ export class UploadFirmwareDialogWidget extends ReactWidget {
@postConstruct() @postConstruct()
protected init(): void { protected init(): void {
this.arduinoFirmwareUploader.updatableBoards().then((fqbns) => { this.appStatusService.reachedState('ready').then(async () => {
const fqbns = await this.arduinoFirmwareUploader.updatableBoards();
this.updatableFqbns = fqbns; this.updatableFqbns = fqbns;
this.update(); this.update();
}); });
@@ -49,14 +59,14 @@ export class UploadFirmwareDialogWidget extends ReactWidget {
}); });
} }
protected flashFirmware(firmware: FirmwareInfo, port: string): Promise<any> { protected flashFirmware(firmware: FirmwareInfo, port: Port): Promise<any> {
this.busyCallback(true); this.busyCallback(true);
return this.arduinoFirmwareUploader return this.arduinoFirmwareUploader
.flash(firmware, port) .flash(firmware, port)
.finally(() => this.busyCallback(false)); .finally(() => this.busyCallback(false));
} }
onCloseRequest(msg: Message): void { protected override onCloseRequest(msg: Message): void {
super.onCloseRequest(msg); super.onCloseRequest(msg);
this.isOpen = new Object(); this.isOpen = new Object();
} }
@@ -88,7 +98,7 @@ export class UploadFirmwareDialog extends AbstractDialog<void> {
constructor( constructor(
@inject(UploadFirmwareDialogProps) @inject(UploadFirmwareDialogProps)
protected readonly props: UploadFirmwareDialogProps protected override readonly props: UploadFirmwareDialogProps
) { ) {
super({ title: UploadFirmware.Commands.OPEN.label || '' }); super({ title: UploadFirmware.Commands.OPEN.label || '' });
this.contentNode.classList.add('firmware-uploader-dialog'); this.contentNode.classList.add('firmware-uploader-dialog');
@@ -99,7 +109,7 @@ export class UploadFirmwareDialog extends AbstractDialog<void> {
return; return;
} }
protected onAfterAttach(msg: Message): void { protected override onAfterAttach(msg: Message): void {
if (this.widget.isAttached) { if (this.widget.isAttached) {
Widget.detach(this.widget); Widget.detach(this.widget);
} }
@@ -109,21 +119,21 @@ export class UploadFirmwareDialog extends AbstractDialog<void> {
this.update(); this.update();
} }
protected onUpdateRequest(msg: Message): void { protected override onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg); super.onUpdateRequest(msg);
this.widget.update(); this.widget.update();
} }
protected onActivateRequest(msg: Message): void { protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg); super.onActivateRequest(msg);
this.widget.activate(); this.widget.activate();
} }
protected handleEnter(event: KeyboardEvent): boolean | void { protected override handleEnter(event: KeyboardEvent): boolean | void {
return false; return false;
} }
close(): void { override close(): void {
if (this.busy) { if (this.busy) {
return; return;
} }

View File

@@ -1,8 +1,8 @@
import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { shell } from 'electron'; import { shell } from 'electron';
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from '@theia/core/shared/react-dom';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater'; import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
import ProgressBar from '../../components/ProgressBar'; import ProgressBar from '../../components/ProgressBar';

View File

@@ -1,9 +1,9 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { DialogProps } from '@theia/core/lib/browser/dialogs'; import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../../theia/dialogs/dialogs'; import { AbstractDialog } from '../../theia/dialogs/dialogs';
import { Widget } from '@phosphor/widgets'; import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { Message } from '@phosphor/messaging'; import { Message } from '@theia/core/shared/@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { nls } from '@theia/core'; import { nls } from '@theia/core';
import { IDEUpdaterComponent } from './ide-updater-component'; import { IDEUpdaterComponent } from './ide-updater-component';
@@ -70,7 +70,7 @@ export class IDEUpdaterDialogWidget extends ReactWidget {
this.close(); this.close();
} }
close(): void { override close(): void {
super.close(); super.close();
this.onClose(); this.onClose();
} }
@@ -122,7 +122,7 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
constructor( constructor(
@inject(IDEUpdaterDialogProps) @inject(IDEUpdaterDialogProps)
protected readonly props: IDEUpdaterDialogProps protected override readonly props: IDEUpdaterDialogProps
) { ) {
super({ super({
title: nls.localize( title: nls.localize(
@@ -138,7 +138,7 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
return this.widget.updateInfo; return this.widget.updateInfo;
} }
protected onAfterAttach(msg: Message): void { protected override onAfterAttach(msg: Message): void {
if (this.widget.isAttached) { if (this.widget.isAttached) {
Widget.detach(this.widget); Widget.detach(this.widget);
} }
@@ -147,7 +147,7 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
this.update(); this.update();
} }
async open( override async open(
data: UpdateInfo | undefined = undefined data: UpdateInfo | undefined = undefined
): Promise<UpdateInfo | undefined> { ): Promise<UpdateInfo | undefined> {
if (data && data.version) { if (data && data.version) {
@@ -156,17 +156,17 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
} }
} }
protected onUpdateRequest(msg: Message): void { protected override onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg); super.onUpdateRequest(msg);
this.widget.update(); this.widget.update();
} }
protected onActivateRequest(msg: Message): void { protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg); super.onActivateRequest(msg);
this.widget.activate(); this.widget.activate();
} }
close(): void { override close(): void {
this.widget.dispose(); this.widget.dispose();
super.close(); super.close();
} }

View File

@@ -1,4 +1,4 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import 'react-tabs/style/react-tabs.css'; import 'react-tabs/style/react-tabs.css';
import { Disable } from 'react-disable'; import { Disable } from 'react-disable';
@@ -17,7 +17,10 @@ import {
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { Settings, SettingsService } from './settings'; import { Settings, SettingsService } from './settings';
import { AdditionalUrlsDialog } from './settings-dialog'; import { AdditionalUrlsDialog } from './settings-dialog';
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization'; import {
AsyncLocalizationProvider,
LanguageInfo,
} from '@theia/core/lib/common/i18n/localization';
export class SettingsComponent extends React.Component< export class SettingsComponent extends React.Component<
SettingsComponent.Props, SettingsComponent.Props,
@@ -29,7 +32,7 @@ export class SettingsComponent extends React.Component<
super(props); super(props);
} }
componentDidUpdate( override componentDidUpdate(
_: SettingsComponent.Props, _: SettingsComponent.Props,
prevState: SettingsComponent.State prevState: SettingsComponent.State
): void { ): void {
@@ -46,7 +49,7 @@ export class SettingsComponent extends React.Component<
} }
} }
componentDidMount(): void { override componentDidMount(): void {
this.props.settingsService this.props.settingsService
.settings() .settings()
.then((settings) => .then((settings) =>
@@ -64,11 +67,11 @@ export class SettingsComponent extends React.Component<
]); ]);
} }
componentWillUnmount(): void { override componentWillUnmount(): void {
this.toDispose.dispose(); this.toDispose.dispose();
} }
render(): React.ReactNode { override render(): React.ReactNode {
if (!this.state) { if (!this.state) {
return <div />; return <div />;
} }
@@ -213,11 +216,9 @@ export class SettingsComponent extends React.Component<
value={this.state.currentLanguage} value={this.state.currentLanguage}
onChange={this.languageDidChange} onChange={this.languageDidChange}
> >
{this.state.languages.map((label) => ( {this.state.languages.map((label) =>
<option key={label} value={label}> this.toSelectOptions(label)
{label} )}
</option>
))}
</select> </select>
<span style={{ marginLeft: '5px' }}> <span style={{ marginLeft: '5px' }}>
( (
@@ -275,7 +276,7 @@ export class SettingsComponent extends React.Component<
<label className="flex-line"> <label className="flex-line">
<input <input
type="checkbox" type="checkbox"
checked={this.state.autoSave === 'on'} checked={this.state.autoSave !== 'off'}
onChange={this.autoSaveDidChange} onChange={this.autoSaveDidChange}
/> />
{nls.localize( {nls.localize(
@@ -314,6 +315,24 @@ export class SettingsComponent extends React.Component<
); );
} }
private toSelectOptions(language: string | LanguageInfo): JSX.Element {
const plain = typeof language === 'string';
const key = plain ? language : language.languageId;
const value = plain ? language : language.languageId;
const label = plain
? language === 'en'
? 'English'
: language
: language.localizedLanguageName ||
language.languageName ||
language.languageId;
return (
<option key={key} value={value}>
{label}
</option>
);
}
protected renderNetwork(): React.ReactNode { protected renderNetwork(): React.ReactNode {
return ( return (
<div className="content noselect"> <div className="content noselect">
@@ -549,7 +568,9 @@ export class SettingsComponent extends React.Component<
protected autoSaveDidChange = ( protected autoSaveDidChange = (
event: React.ChangeEvent<HTMLInputElement> event: React.ChangeEvent<HTMLInputElement>
): void => { ): void => {
this.setState({ autoSave: event.target.checked ? 'on' : 'off' }); this.setState({
autoSave: event.target.checked ? Settings.AutoSave.DEFAULT_ON : 'off',
});
}; };
protected quickSuggestionsOtherDidChange = ( protected quickSuggestionsOtherDidChange = (

View File

@@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { injectable, inject, postConstruct } from 'inversify'; import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import { Widget } from '@phosphor/widgets'; import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { Message } from '@phosphor/messaging'; import { Message } from '@theia/core/shared/@phosphor/messaging';
import { DialogError, ReactWidget } from '@theia/core/lib/browser'; import { DialogError, ReactWidget } from '@theia/core/lib/browser';
import { AbstractDialog, DialogProps } from '@theia/core/lib/browser'; import { AbstractDialog, DialogProps } from '@theia/core/lib/browser';
import { Settings, SettingsService } from './settings'; import { Settings, SettingsService } from './settings';
@@ -56,7 +56,7 @@ export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
constructor( constructor(
@inject(SettingsDialogProps) @inject(SettingsDialogProps)
protected readonly props: SettingsDialogProps protected override readonly props: SettingsDialogProps
) { ) {
super(props); super(props);
this.contentNode.classList.add('arduino-settings-dialog'); this.contentNode.classList.add('arduino-settings-dialog');
@@ -73,7 +73,7 @@ export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
); );
} }
protected async isValid(settings: Promise<Settings>): Promise<DialogError> { protected override async isValid(settings: Promise<Settings>): Promise<DialogError> {
const result = await this.settingsService.validate(settings); const result = await this.settingsService.validate(settings);
if (typeof result === 'string') { if (typeof result === 'string') {
return result; return result;
@@ -85,7 +85,7 @@ export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
return this.settingsService.settings(); return this.settingsService.settings();
} }
protected onAfterAttach(msg: Message): void { protected override onAfterAttach(msg: Message): void {
if (this.widget.isAttached) { if (this.widget.isAttached) {
Widget.detach(this.widget); Widget.detach(this.widget);
} }
@@ -97,12 +97,12 @@ export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
this.update(); this.update();
} }
protected onUpdateRequest(msg: Message): void { protected override onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg); super.onUpdateRequest(msg);
this.widget.update(); this.widget.update();
} }
protected onActivateRequest(msg: Message): void { protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg); super.onActivateRequest(msg);
// calling settingsService.reset() in order to reload the settings from the preferenceService // calling settingsService.reset() in order to reload the settings from the preferenceService
@@ -172,17 +172,17 @@ export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
return AdditionalUrls.parse(this.textArea.value, 'newline'); return AdditionalUrls.parse(this.textArea.value, 'newline');
} }
protected onAfterAttach(message: Message): void { protected override onAfterAttach(message: Message): void {
super.onAfterAttach(message); super.onAfterAttach(message);
this.addUpdateListener(this.textArea, 'input'); this.addUpdateListener(this.textArea, 'input');
} }
protected onActivateRequest(message: Message): void { protected override onActivateRequest(message: Message): void {
super.onActivateRequest(message); super.onActivateRequest(message);
this.textArea.focus(); this.textArea.focus();
} }
protected handleEnter(event: KeyboardEvent): boolean | void { protected override handleEnter(event: KeyboardEvent): boolean | void {
if (event.target instanceof HTMLInputElement) { if (event.target instanceof HTMLInputElement) {
return super.handleEnter(event); return super.handleEnter(event);
} }

View File

@@ -1,4 +1,8 @@
import { injectable, inject, postConstruct } from 'inversify'; import {
injectable,
inject,
postConstruct,
} from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { Emitter } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { Deferred, timeout } from '@theia/core/lib/common/promise-util'; import { Deferred, timeout } from '@theia/core/lib/common/promise-util';
@@ -16,12 +20,15 @@ import {
Network, Network,
} from '../../../common/protocol'; } from '../../../common/protocol';
import { CommandService, nls } from '@theia/core/lib/common'; import { CommandService, nls } from '@theia/core/lib/common';
import { AsyncLocalizationProvider } from '@theia/core/lib/common/i18n/localization'; import {
AsyncLocalizationProvider,
LanguageInfo,
} from '@theia/core/lib/common/i18n/localization';
import { ElectronCommands } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'; import { ElectronCommands } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution';
export const EDITOR_SETTING = 'editor'; export const EDITOR_SETTING = 'editor';
export const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`; export const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`;
export const AUTO_SAVE_SETTING = `${EDITOR_SETTING}.autoSave`; export const AUTO_SAVE_SETTING = `files.autoSave`;
export const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`; export const QUICK_SUGGESTIONS_SETTING = `${EDITOR_SETTING}.quickSuggestions`;
export const ARDUINO_SETTING = 'arduino'; export const ARDUINO_SETTING = 'arduino';
export const WINDOW_SETTING = `${ARDUINO_SETTING}.window`; export const WINDOW_SETTING = `${ARDUINO_SETTING}.window`;
@@ -39,10 +46,10 @@ export const SHOW_ALL_FILES_SETTING = `${SKETCHBOOK_SETTING}.showAllFiles`;
export interface Settings { export interface Settings {
editorFontSize: number; // `editor.fontSize` editorFontSize: number; // `editor.fontSize`
themeId: string; // `workbench.colorTheme` themeId: string; // `workbench.colorTheme`
autoSave: 'on' | 'off'; // `editor.autoSave` autoSave: Settings.AutoSave; // `files.autoSave`
quickSuggestions: Record<'other' | 'comments' | 'strings', boolean>; // `editor.quickSuggestions` quickSuggestions: Record<'other' | 'comments' | 'strings', boolean>; // `editor.quickSuggestions`
languages: string[]; // `languages from the plugins` languages: (string | LanguageInfo)[]; // `languages from the plugins`
currentLanguage: string; currentLanguage: string;
autoScaleInterface: boolean; // `arduino.window.autoScale` autoScaleInterface: boolean; // `arduino.window.autoScale`
@@ -61,6 +68,14 @@ export namespace Settings {
export function belongsToCli<K extends keyof Settings>(key: K): boolean { export function belongsToCli<K extends keyof Settings>(key: K): boolean {
return key === 'sketchbookPath' || key === 'additionalUrls'; return key === 'sketchbookPath' || key === 'additionalUrls';
} }
export type AutoSave =
| 'off'
| 'afterDelay'
| 'onFocusChange'
| 'onWindowChange';
export namespace AutoSave {
export const DEFAULT_ON: AutoSave = 'afterDelay'; // https://github.com/eclipse-theia/theia/issues/10812
}
} }
@injectable() @injectable()
@@ -126,7 +141,10 @@ export class SettingsService {
'workbench.colorTheme', 'workbench.colorTheme',
'arduino-theme' 'arduino-theme'
), ),
this.preferenceService.get<'on' | 'off'>(AUTO_SAVE_SETTING, 'on'), this.preferenceService.get<Settings.AutoSave>(
AUTO_SAVE_SETTING,
Settings.AutoSave.DEFAULT_ON
),
this.preferenceService.get< this.preferenceService.get<
Record<'other' | 'comments' | 'strings', boolean> Record<'other' | 'comments' | 'strings', boolean>
>(QUICK_SUGGESTIONS_SETTING, { >(QUICK_SUGGESTIONS_SETTING, {
@@ -262,7 +280,7 @@ export class SettingsService {
await this.savePreference('editor.fontSize', editorFontSize); await this.savePreference('editor.fontSize', editorFontSize);
await this.savePreference('workbench.colorTheme', themeId); await this.savePreference('workbench.colorTheme', themeId);
await this.savePreference('editor.autoSave', autoSave); await this.savePreference(AUTO_SAVE_SETTING, autoSave);
await this.savePreference('editor.quickSuggestions', quickSuggestions); await this.savePreference('editor.quickSuggestions', quickSuggestions);
await this.savePreference(AUTO_SCALE_SETTING, autoScaleInterface); await this.savePreference(AUTO_SCALE_SETTING, autoScaleInterface);
await this.savePreference(ZOOM_LEVEL_SETTING, interfaceScale); await this.savePreference(ZOOM_LEVEL_SETTING, interfaceScale);

View File

@@ -1,4 +1,4 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { BoardUserField } from '../../../common/protocol'; import { BoardUserField } from '../../../common/protocol';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';

View File

@@ -1,12 +1,12 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { import {
AbstractDialog, AbstractDialog,
DialogProps, DialogProps,
ReactWidget, ReactWidget,
} from '@theia/core/lib/browser'; } from '@theia/core/lib/browser';
import { Widget } from '@phosphor/widgets'; import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { Message } from '@phosphor/messaging'; import { Message } from '@theia/core/shared/@phosphor/messaging';
import { UploadSketch } from '../../contributions/upload-sketch'; import { UploadSketch } from '../../contributions/upload-sketch';
import { UserFieldsComponent } from './user-fields-component'; import { UserFieldsComponent } from './user-fields-component';
import { BoardUserField } from '../../../common/protocol'; import { BoardUserField } from '../../../common/protocol';
@@ -61,7 +61,7 @@ export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
constructor( constructor(
@inject(UserFieldsDialogProps) @inject(UserFieldsDialogProps)
protected readonly props: UserFieldsDialogProps protected override readonly props: UserFieldsDialogProps
) { ) {
super({ super({
title: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label || '', title: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label || '',
@@ -83,7 +83,7 @@ export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
return this.widget.currentUserFields; return this.widget.currentUserFields;
} }
protected onAfterAttach(msg: Message): void { protected override onAfterAttach(msg: Message): void {
if (this.widget.isAttached) { if (this.widget.isAttached) {
Widget.detach(this.widget); Widget.detach(this.widget);
} }
@@ -92,17 +92,17 @@ export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
this.update(); this.update();
} }
protected onUpdateRequest(msg: Message): void { protected override onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg); super.onUpdateRequest(msg);
this.widget.update(); this.widget.update();
} }
protected onActivateRequest(msg: Message): void { protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg); super.onActivateRequest(msg);
this.widget.activate(); this.widget.activate();
} }
protected async accept(): Promise<void> { protected override async accept(): Promise<void> {
// If the user presses enter and at least // If the user presses enter and at least
// a field is empty don't accept the input // a field is empty don't accept the input
for (const field of this.value) { for (const field of this.value) {
@@ -113,7 +113,7 @@ export class UserFieldsDialog extends AbstractDialog<BoardUserField[]> {
return super.accept(); return super.accept();
} }
close(): void { override close(): void {
this.widget.resetUserFieldsValue(); this.widget.resetUserFieldsValue();
this.widget.close(); this.widget.close();
super.close(); super.close();

View File

@@ -1,4 +1,4 @@
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { import {
FrontendApplicationContribution, FrontendApplicationContribution,
FrontendApplication, FrontendApplication,

View File

@@ -0,0 +1,74 @@
import { DisposableCollection, Emitter, Event } from '@theia/core';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { inject, injectable } from '@theia/core/shared/inversify';
import { HostedPluginSupport } from './theia/plugin-ext/hosted-plugin';
/**
* Frontend contribution to watch VS Code extension start/stop events from Theia.
*
* In Theia, there are no events when a VS Code extension is loaded, started, unloaded, and stopped.
* Currently, it's possible to `@inject` the `HostedPluginSupport` service from Theia and `await`
* for the `didStart` promise to resolve. But if the OS goes to sleep, the VS Code extensions will
* be unloaded and loaded and started again when the OS awakes. Theia reloads the VS Code extensions
* after the OS awake event, but the `didStart` promise was already resolved, so IDE2 cannot restart the LS.
* This service is meant to work around the limitation of Theia and fire an event every time the VS Code extensions
* loaded and started.
*/
@injectable()
export class HostedPluginEvents implements FrontendApplicationContribution {
@inject(HostedPluginSupport)
private readonly hostedPluginSupport: HostedPluginSupport;
private firstStart = true;
private readonly onPluginsDidStartEmitter = new Emitter<void>();
private readonly onPluginsWillUnloadEmitter = new Emitter<void>();
private readonly toDispose = new DisposableCollection(
this.onPluginsDidStartEmitter,
this.onPluginsWillUnloadEmitter
);
onStart(): void {
this.hostedPluginSupport.onDidLoad(() => {
// Fire the first event, when `didStart` resolves.
if (!this.firstStart) {
console.debug('HostedPluginEvents', "Received 'onDidLoad' event.");
this.onPluginsDidStartEmitter.fire();
} else {
console.debug(
'HostedPluginEvents',
"Received 'onDidLoad' event before the first start. Skipping."
);
}
});
this.hostedPluginSupport.didStart.then(() => {
console.debug('HostedPluginEvents', "Hosted plugins 'didStart'.");
if (!this.firstStart) {
throw new Error(
'Unexpectedly received a `didStart` event after the first start.'
);
}
this.firstStart = false;
this.onPluginsDidStartEmitter.fire();
});
this.hostedPluginSupport.onDidCloseConnection(() => {
console.debug('HostedPluginEvents', "Received 'onDidCloseConnection'.");
this.onPluginsWillUnloadEmitter.fire();
});
}
onStop(): void {
this.toDispose.dispose();
}
get onPluginsDidStart(): Event<void> {
return this.onPluginsDidStartEmitter.event;
}
get onPluginsWillUnload(): Event<void> {
return this.onPluginsWillUnloadEmitter.event;
}
get didStart(): Promise<void> {
return this.hostedPluginSupport.didStart;
}
}

View File

@@ -5,7 +5,7 @@ import {
MessageService, MessageService,
nls, nls,
} from '@theia/core'; } from '@theia/core';
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { IDEUpdater, UpdateInfo } from '../../common/protocol/ide-updater'; import { IDEUpdater, UpdateInfo } from '../../common/protocol/ide-updater';
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog'; import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';

View File

@@ -0,0 +1,13 @@
import * as monaco from '@theia/monaco-editor-core';
/**
* Exclusive "ino" document selector for monaco.
*/
export const InoSelector = selectorOf('ino', 'c', 'cpp', 'h', 'hpp', 'pde');
function selectorOf(
...languageId: string[]
): monaco.languages.LanguageSelector {
return languageId.map((language) => ({
language,
exclusive: true, // <-- this should make sure the custom formatter has higher precedence over the LS formatter.
}));
}

View File

@@ -1,5 +1,5 @@
import { injectable, postConstruct, inject } from 'inversify'; import { injectable, postConstruct, inject } from '@theia/core/shared/inversify';
import { Message } from '@phosphor/messaging'; import { Message } from '@theia/core/shared/@phosphor/messaging';
import { addEventListener } from '@theia/core/lib/browser/widgets/widget'; import { addEventListener } from '@theia/core/lib/browser/widgets/widget';
import { DialogProps } from '@theia/core/lib/browser/dialogs'; import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../theia/dialogs/dialogs'; import { AbstractDialog } from '../theia/dialogs/dialogs';
@@ -38,7 +38,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
} }
@postConstruct() @postConstruct()
protected init(): void { protected override init(): void {
super.init(); super.init();
this.toDispose.pushAll([ this.toDispose.pushAll([
this.notificationCenter.onLibraryInstalled(() => this.refresh(undefined)), this.notificationCenter.onLibraryInstalled(() => this.refresh(undefined)),
@@ -48,7 +48,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
]); ]);
} }
protected async install({ protected override async install({
item, item,
progressId, progressId,
version, version,
@@ -158,7 +158,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
} }
} }
protected async uninstall({ protected override async uninstall({
item, item,
progressId, progressId,
}: { }: {
@@ -199,7 +199,7 @@ class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {
}); });
} }
protected onCloseRequest(message: Message): void { protected override onCloseRequest(message: Message): void {
super.onCloseRequest(message); super.onCloseRequest(message);
this.accept(); this.accept();
} }
@@ -217,7 +217,7 @@ class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {
return message; return message;
} }
protected handleEnter(event: KeyboardEvent): boolean | void { protected override handleEnter(event: KeyboardEvent): boolean | void {
this.response = 0; this.response = 0;
super.handleEnter(event); super.handleEnter(event);
} }

View File

@@ -1,4 +1,4 @@
import { injectable } from 'inversify'; import { injectable } from '@theia/core/shared/inversify';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { MenuModelRegistry } from '@theia/core'; import { MenuModelRegistry } from '@theia/core';
@@ -28,7 +28,7 @@ export class LibraryListWidgetFrontendContribution
this.openView(); this.openView();
} }
registerMenus(menus: MenuModelRegistry): void { override registerMenus(menus: MenuModelRegistry): void {
if (this.toggleCommand) { if (this.toggleCommand) {
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: this.toggleCommand.id, commandId: this.toggleCommand.id,

View File

@@ -1,4 +1,4 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { URI as Uri } from 'vscode-uri'; import { URI as Uri } from 'vscode-uri';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { Deferred } from '@theia/core/lib/common/promise-util'; import { Deferred } from '@theia/core/lib/common/promise-util';

View File

@@ -0,0 +1,199 @@
import {
CommandRegistry,
Disposable,
Emitter,
MessageService,
nls,
} from '@theia/core';
import { inject, injectable } from '@theia/core/shared/inversify';
import { Board, Port } from '../common/protocol';
import {
Monitor,
MonitorManagerProxyClient,
MonitorManagerProxyFactory,
} from '../common/protocol/monitor-service';
import {
PluggableMonitorSettings,
MonitorSettings,
} from '../node/monitor-settings/monitor-settings-provider';
import { BoardsConfig } from './boards/boards-config';
import { BoardsServiceProvider } from './boards/boards-service-provider';
@injectable()
export class MonitorManagerProxyClientImpl
implements MonitorManagerProxyClient
{
// When pluggable monitor messages are received from the backend
// this event is triggered.
// Ideally a frontend component is connected to this event
// to update the UI.
protected readonly onMessagesReceivedEmitter = new Emitter<{
messages: string[];
}>();
readonly onMessagesReceived = this.onMessagesReceivedEmitter.event;
protected readonly onMonitorSettingsDidChangeEmitter =
new Emitter<MonitorSettings>();
readonly onMonitorSettingsDidChange =
this.onMonitorSettingsDidChangeEmitter.event;
protected readonly onMonitorShouldResetEmitter = new Emitter();
readonly onMonitorShouldReset = this.onMonitorShouldResetEmitter.event;
// WebSocket used to handle pluggable monitor communication between
// frontend and backend.
private webSocket?: WebSocket;
private wsPort?: number;
private lastConnectedBoard: BoardsConfig.Config;
private onBoardsConfigChanged: Disposable | undefined;
getWebSocketPort(): number | undefined {
return this.wsPort;
}
constructor(
@inject(MessageService)
protected messageService: MessageService,
// This is necessary to call the backend methods from the frontend
@inject(MonitorManagerProxyFactory)
protected server: MonitorManagerProxyFactory,
@inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry,
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider
) {}
/**
* Connects a localhost WebSocket using the specified port.
* @param addressPort port of the WebSocket
*/
async connect(addressPort: number): Promise<void> {
if (!!this.webSocket) {
if (this.wsPort === addressPort) return;
else this.disconnect();
}
try {
this.webSocket = new WebSocket(`ws://localhost:${addressPort}`);
} catch {
this.messageService.error(
nls.localize(
'arduino/monitor/unableToConnectToWebSocket',
'Unable to connect to websocket'
)
);
return;
}
this.webSocket.onmessage = (message) => {
const parsedMessage = JSON.parse(message.data);
if (Array.isArray(parsedMessage))
this.onMessagesReceivedEmitter.fire({ messages: parsedMessage });
else if (
parsedMessage.command ===
Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE
) {
this.onMonitorSettingsDidChangeEmitter.fire(parsedMessage.data);
}
};
this.wsPort = addressPort;
}
/**
* Disconnects the WebSocket if connected.
*/
disconnect(): void {
if (!this.webSocket) return;
this.onBoardsConfigChanged?.dispose();
this.onBoardsConfigChanged = undefined;
try {
this.webSocket?.close();
this.webSocket = undefined;
} catch {
this.messageService.error(
nls.localize(
'arduino/monitor/unableToCloseWebSocket',
'Unable to close websocket'
)
);
}
}
async isWSConnected(): Promise<boolean> {
return !!this.webSocket;
}
async startMonitor(settings?: PluggableMonitorSettings): Promise<void> {
this.lastConnectedBoard = {
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort,
};
if (!this.onBoardsConfigChanged) {
this.onBoardsConfigChanged =
this.boardsServiceProvider.onBoardsConfigChanged(
async ({ selectedBoard, selectedPort }) => {
if (
typeof selectedBoard === 'undefined' ||
typeof selectedPort === 'undefined'
)
return;
// a board is plugged and it's different from the old connected board
if (
selectedBoard?.fqbn !==
this.lastConnectedBoard?.selectedBoard?.fqbn ||
selectedPort?.id !== this.lastConnectedBoard?.selectedPort?.id
) {
this.onMonitorShouldResetEmitter.fire(null);
this.lastConnectedBoard = {
selectedBoard: selectedBoard,
selectedPort: selectedPort,
};
} else {
// a board is plugged and it's the same as prev, rerun "this.startMonitor" to
// recreate the listener callback
this.startMonitor();
}
}
);
}
const { selectedBoard, selectedPort } =
this.boardsServiceProvider.boardsConfig;
if (!selectedBoard || !selectedBoard.fqbn || !selectedPort) return;
await this.server().startMonitor(selectedBoard, selectedPort, settings);
}
getCurrentSettings(board: Board, port: Port): Promise<MonitorSettings> {
return this.server().getCurrentSettings(board, port);
}
send(message: string): void {
if (!this.webSocket) {
return;
}
this.webSocket.send(
JSON.stringify({
command: Monitor.ClientCommand.SEND_MESSAGE,
data: message,
})
);
}
changeSettings(settings: MonitorSettings): void {
if (!this.webSocket) {
return;
}
this.webSocket.send(
JSON.stringify({
command: Monitor.ClientCommand.CHANGE_SETTINGS,
data: settings,
})
);
}
}

View File

@@ -0,0 +1,278 @@
import { Emitter, Event } from '@theia/core';
import {
FrontendApplicationContribution,
LocalStorageService,
} from '@theia/core/lib/browser';
import { inject, injectable } from '@theia/core/shared/inversify';
import { MonitorManagerProxyClient } from '../common/protocol';
import { isNullOrUndefined } from '../common/utils';
import { MonitorSettings } from '../node/monitor-settings/monitor-settings-provider';
@injectable()
export class MonitorModel implements FrontendApplicationContribution {
protected static STORAGE_ID = 'arduino-monitor-model';
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
@inject(MonitorManagerProxyClient)
protected readonly monitorManagerProxy: MonitorManagerProxyClient;
protected readonly onChangeEmitter: Emitter<
MonitorModel.State.Change<keyof MonitorModel.State>
>;
protected _autoscroll: boolean;
protected _timestamp: boolean;
protected _lineEnding: MonitorModel.EOL;
protected _interpolate: boolean;
protected _darkTheme: boolean;
protected _wsPort: number;
protected _serialPort: string;
protected _connected: boolean;
constructor() {
this._autoscroll = true;
this._timestamp = false;
this._interpolate = false;
this._lineEnding = MonitorModel.EOL.DEFAULT;
this._darkTheme = false;
this._wsPort = 0;
this._serialPort = '';
this._connected = true;
this.onChangeEmitter = new Emitter<
MonitorModel.State.Change<keyof MonitorModel.State>
>();
}
onStart(): void {
this.localStorageService
.getData<MonitorModel.State>(MonitorModel.STORAGE_ID)
.then(this.restoreState.bind(this));
this.monitorManagerProxy.onMonitorSettingsDidChange(
this.onMonitorSettingsDidChange.bind(this)
);
}
get onChange(): Event<MonitorModel.State.Change<keyof MonitorModel.State>> {
return this.onChangeEmitter.event;
}
protected restoreState(state: MonitorModel.State): void {
if (!state) {
return;
}
this._autoscroll = state.autoscroll;
this._timestamp = state.timestamp;
this._lineEnding = state.lineEnding;
this._interpolate = state.interpolate;
this._serialPort = state.serialPort;
}
protected async storeState(): Promise<void> {
return this.localStorageService.setData(MonitorModel.STORAGE_ID, {
autoscroll: this._autoscroll,
timestamp: this._timestamp,
lineEnding: this._lineEnding,
interpolate: this._interpolate,
serialPort: this._serialPort,
});
}
get autoscroll(): boolean {
return this._autoscroll;
}
set autoscroll(autoscroll: boolean) {
if (autoscroll === this._autoscroll) return;
this._autoscroll = autoscroll;
this.monitorManagerProxy.changeSettings({
monitorUISettings: { autoscroll },
});
this.storeState().then(() => {
this.onChangeEmitter.fire({
property: 'autoscroll',
value: this._autoscroll,
});
});
}
toggleAutoscroll(): void {
this.autoscroll = !this._autoscroll;
}
get timestamp(): boolean {
return this._timestamp;
}
set timestamp(timestamp: boolean) {
if (timestamp === this._timestamp) return;
this._timestamp = timestamp;
this.monitorManagerProxy.changeSettings({
monitorUISettings: { timestamp },
});
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'timestamp',
value: this._timestamp,
})
);
}
toggleTimestamp(): void {
this.timestamp = !this._timestamp;
}
get lineEnding(): MonitorModel.EOL {
return this._lineEnding;
}
set lineEnding(lineEnding: MonitorModel.EOL) {
if (lineEnding === this._lineEnding) return;
this._lineEnding = lineEnding;
this.monitorManagerProxy.changeSettings({
monitorUISettings: { lineEnding },
});
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'lineEnding',
value: this._lineEnding,
})
);
}
get interpolate(): boolean {
return this._interpolate;
}
set interpolate(interpolate: boolean) {
if (interpolate === this._interpolate) return;
this._interpolate = interpolate;
this.monitorManagerProxy.changeSettings({
monitorUISettings: { interpolate },
});
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'interpolate',
value: this._interpolate,
})
);
}
get darkTheme(): boolean {
return this._darkTheme;
}
set darkTheme(darkTheme: boolean) {
if (darkTheme === this._darkTheme) return;
this._darkTheme = darkTheme;
this.monitorManagerProxy.changeSettings({
monitorUISettings: { darkTheme },
});
this.onChangeEmitter.fire({
property: 'darkTheme',
value: this._darkTheme,
});
}
get wsPort(): number {
return this._wsPort;
}
set wsPort(wsPort: number) {
if (wsPort === this._wsPort) return;
this._wsPort = wsPort;
this.monitorManagerProxy.changeSettings({
monitorUISettings: { wsPort },
});
this.onChangeEmitter.fire({
property: 'wsPort',
value: this._wsPort,
});
}
get serialPort(): string {
return this._serialPort;
}
set serialPort(serialPort: string) {
if (serialPort === this._serialPort) return;
this._serialPort = serialPort;
this.monitorManagerProxy.changeSettings({
monitorUISettings: { serialPort },
});
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'serialPort',
value: this._serialPort,
})
);
}
get connected(): boolean {
return this._connected;
}
set connected(connected: boolean) {
if (connected === this._connected) return;
this._connected = connected;
this.monitorManagerProxy.changeSettings({
monitorUISettings: { connected },
});
this.onChangeEmitter.fire({
property: 'connected',
value: this._connected,
});
}
protected onMonitorSettingsDidChange = (settings: MonitorSettings): void => {
const { monitorUISettings } = settings;
if (!monitorUISettings) return;
const {
autoscroll,
interpolate,
lineEnding,
timestamp,
darkTheme,
wsPort,
serialPort,
connected,
} = monitorUISettings;
if (!isNullOrUndefined(autoscroll)) this.autoscroll = autoscroll;
if (!isNullOrUndefined(interpolate)) this.interpolate = interpolate;
if (!isNullOrUndefined(lineEnding)) this.lineEnding = lineEnding;
if (!isNullOrUndefined(timestamp)) this.timestamp = timestamp;
if (!isNullOrUndefined(darkTheme)) this.darkTheme = darkTheme;
if (!isNullOrUndefined(wsPort)) this.wsPort = wsPort;
if (!isNullOrUndefined(serialPort)) this.serialPort = serialPort;
if (!isNullOrUndefined(connected)) this.connected = connected;
};
}
// TODO: Move this to /common
export namespace MonitorModel {
export interface State {
autoscroll: boolean;
timestamp: boolean;
lineEnding: EOL;
interpolate: boolean;
darkTheme: boolean;
wsPort: number;
serialPort: string;
connected: boolean;
}
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

@@ -1,4 +1,8 @@
import { inject, injectable, postConstruct } from 'inversify'; import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import { Emitter } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory'; import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { DisposableCollection } from '@theia/core/lib/common/disposable';
@@ -14,6 +18,10 @@ import {
Config, Config,
Sketch, Sketch,
} from '../common/protocol'; } from '../common/protocol';
import {
FrontendApplicationStateService,
FrontendApplicationState,
} from '@theia/core/lib/browser/frontend-application-state';
@injectable() @injectable()
export class NotificationCenter export class NotificationCenter
@@ -22,8 +30,11 @@ export class NotificationCenter
@inject(NotificationServiceServer) @inject(NotificationServiceServer)
protected readonly server: JsonRpcProxy<NotificationServiceServer>; protected readonly server: JsonRpcProxy<NotificationServiceServer>;
@inject(FrontendApplicationStateService)
private readonly appStateService: FrontendApplicationStateService;
protected readonly indexUpdatedEmitter = new Emitter<void>(); protected readonly indexUpdatedEmitter = new Emitter<void>();
protected readonly daemonStartedEmitter = new Emitter<void>(); protected readonly daemonStartedEmitter = new Emitter<string>();
protected readonly daemonStoppedEmitter = new Emitter<void>(); protected readonly daemonStoppedEmitter = new Emitter<void>();
protected readonly configChangedEmitter = new Emitter<{ protected readonly configChangedEmitter = new Emitter<{
config: Config | undefined; config: Config | undefined;
@@ -45,6 +56,8 @@ export class NotificationCenter
protected readonly recentSketchesChangedEmitter = new Emitter<{ protected readonly recentSketchesChangedEmitter = new Emitter<{
sketches: Sketch[]; sketches: Sketch[];
}>(); }>();
private readonly onAppStateDidChangeEmitter =
new Emitter<FrontendApplicationState>();
protected readonly toDispose = new DisposableCollection( protected readonly toDispose = new DisposableCollection(
this.indexUpdatedEmitter, this.indexUpdatedEmitter,
@@ -68,10 +81,16 @@ export class NotificationCenter
readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event; readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event;
readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event; readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event;
readonly onRecentSketchesChanged = this.recentSketchesChangedEmitter.event; readonly onRecentSketchesChanged = this.recentSketchesChangedEmitter.event;
readonly onAppStateDidChange = this.onAppStateDidChangeEmitter.event;
@postConstruct() @postConstruct()
protected init(): void { protected init(): void {
this.server.setClient(this); this.server.setClient(this);
this.toDispose.push(
this.appStateService.onStateChanged((state) =>
this.onAppStateDidChangeEmitter.fire(state)
)
);
} }
onStop(): void { onStop(): void {
@@ -82,8 +101,8 @@ export class NotificationCenter
this.indexUpdatedEmitter.fire(); this.indexUpdatedEmitter.fire();
} }
notifyDaemonStarted(): void { notifyDaemonStarted(port: string): void {
this.daemonStartedEmitter.fire(); this.daemonStartedEmitter.fire(port);
} }
notifyDaemonStopped(): void { notifyDaemonStopped(): void {

View File

@@ -1,7 +1,9 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import { Emitter } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { OutputContribution } from '@theia/output/lib/browser/output-contribution'; import {
import { OutputChannelManager } from '@theia/output/lib/browser/output-channel'; OutputChannelManager,
OutputChannelSeverity,
} from '@theia/output/lib/browser/output-channel';
import { import {
OutputMessage, OutputMessage,
ProgressMessage, ProgressMessage,
@@ -10,13 +12,10 @@ import {
@injectable() @injectable()
export class ResponseServiceImpl implements ResponseServiceArduino { export class ResponseServiceImpl implements ResponseServiceArduino {
@inject(OutputContribution)
protected outputContribution: OutputContribution;
@inject(OutputChannelManager) @inject(OutputChannelManager)
protected outputChannelManager: OutputChannelManager; private readonly outputChannelManager: OutputChannelManager;
protected readonly progressDidChangeEmitter = new Emitter<ProgressMessage>(); private readonly progressDidChangeEmitter = new Emitter<ProgressMessage>();
readonly onProgressDidChange = this.progressDidChangeEmitter.event; readonly onProgressDidChange = this.progressDidChangeEmitter.event;
@@ -25,13 +24,22 @@ export class ResponseServiceImpl implements ResponseServiceArduino {
} }
appendToOutput(message: OutputMessage): void { appendToOutput(message: OutputMessage): void {
const { chunk } = message; const { chunk, severity } = message;
const channel = this.outputChannelManager.getChannel('Arduino'); const channel = this.outputChannelManager.getChannel('Arduino');
channel.show({ preserveFocus: true }); channel.show({ preserveFocus: true });
channel.append(chunk); channel.append(chunk, mapSeverity(severity));
} }
reportProgress(progress: ProgressMessage): void { reportProgress(progress: ProgressMessage): void {
this.progressDidChangeEmitter.fire(progress); this.progressDidChangeEmitter.fire(progress);
} }
} }
function mapSeverity(severity?: OutputMessage.Severity): OutputChannelSeverity {
if (severity === OutputMessage.Severity.Error) {
return OutputChannelSeverity.Error;
} else if (severity === OutputMessage.Severity.Warning) {
return OutputChannelSeverity.Warning;
}
return OutputChannelSeverity.Info;
}

View File

@@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { AbstractViewContribution, codicon } from '@theia/core/lib/browser'; import { AbstractViewContribution, codicon } from '@theia/core/lib/browser';
import { MonitorWidget } from './monitor-widget'; import { MonitorWidget } from './monitor-widget';
import { MenuModelRegistry, Command, CommandRegistry } from '@theia/core'; import { MenuModelRegistry, Command, CommandRegistry } from '@theia/core';
@@ -8,9 +8,10 @@ import {
TabBarToolbarRegistry, TabBarToolbarRegistry,
} from '@theia/core/lib/browser/shell/tab-bar-toolbar'; } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { ArduinoToolbar } from '../../toolbar/arduino-toolbar'; import { ArduinoToolbar } from '../../toolbar/arduino-toolbar';
import { SerialModel } from '../serial-model';
import { ArduinoMenus } from '../../menu/arduino-menus'; import { ArduinoMenus } from '../../menu/arduino-menus';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { MonitorModel } from '../../monitor-model';
import { MonitorManagerProxyClient } from '../../../common/protocol';
export namespace SerialMonitor { export namespace SerialMonitor {
export namespace Commands { export namespace Commands {
@@ -47,10 +48,15 @@ export class MonitorViewContribution
static readonly TOGGLE_SERIAL_MONITOR = MonitorWidget.ID + ':toggle'; static readonly TOGGLE_SERIAL_MONITOR = MonitorWidget.ID + ':toggle';
static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR = static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR =
MonitorWidget.ID + ':toggle-toolbar'; MonitorWidget.ID + ':toggle-toolbar';
static readonly RESET_SERIAL_MONITOR = MonitorWidget.ID + ':reset';
@inject(SerialModel) protected readonly model: SerialModel; constructor(
@inject(MonitorModel)
protected readonly model: MonitorModel,
constructor() { @inject(MonitorManagerProxyClient)
protected readonly monitorManagerProxy: MonitorManagerProxyClient
) {
super({ super({
widgetId: MonitorWidget.ID, widgetId: MonitorWidget.ID,
widgetName: MonitorWidget.LABEL, widgetName: MonitorWidget.LABEL,
@@ -60,9 +66,10 @@ export class MonitorViewContribution
toggleCommandId: MonitorViewContribution.TOGGLE_SERIAL_MONITOR, toggleCommandId: MonitorViewContribution.TOGGLE_SERIAL_MONITOR,
toggleKeybinding: 'CtrlCmd+Shift+M', toggleKeybinding: 'CtrlCmd+Shift+M',
}); });
this.monitorManagerProxy.onMonitorShouldReset(() => this.reset());
} }
registerMenus(menus: MenuModelRegistry): void { override registerMenus(menus: MenuModelRegistry): void {
if (this.toggleCommand) { if (this.toggleCommand) {
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: this.toggleCommand.id, commandId: this.toggleCommand.id,
@@ -95,7 +102,7 @@ export class MonitorViewContribution
}); });
} }
registerCommands(commands: CommandRegistry): void { override registerCommands(commands: CommandRegistry): void {
commands.registerCommand(SerialMonitor.Commands.CLEAR_OUTPUT, { commands.registerCommand(SerialMonitor.Commands.CLEAR_OUTPUT, {
isEnabled: (widget) => widget instanceof MonitorWidget, isEnabled: (widget) => widget instanceof MonitorWidget,
isVisible: (widget) => widget instanceof MonitorWidget, isVisible: (widget) => widget instanceof MonitorWidget,
@@ -118,6 +125,10 @@ export class MonitorViewContribution
} }
); );
} }
commands.registerCommand(
{ id: MonitorViewContribution.RESET_SERIAL_MONITOR },
{ execute: () => this.reset() }
);
} }
protected async toggle(): Promise<void> { protected async toggle(): Promise<void> {
@@ -129,6 +140,14 @@ export class MonitorViewContribution
} }
} }
protected async reset(): Promise<void> {
const widget = this.tryGetWidget();
if (widget) {
widget.dispose();
await this.openView({ activate: true, reveal: true });
}
}
protected renderAutoScrollButton(): React.ReactNode { protected renderAutoScrollButton(): React.ReactNode {
return ( return (
<React.Fragment key="autoscroll-toolbar-item"> <React.Fragment key="autoscroll-toolbar-item">

View File

@@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { postConstruct, injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { OptionsType } from 'react-select/src/types'; import { OptionsType } from 'react-select/src/types';
import { Emitter } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { Disposable } from '@theia/core/lib/common/disposable'; import { Disposable } from '@theia/core/lib/common/disposable';
@@ -9,14 +9,14 @@ import {
Widget, Widget,
MessageLoop, MessageLoop,
} from '@theia/core/lib/browser/widgets'; } from '@theia/core/lib/browser/widgets';
import { SerialConfig } from '../../../common/protocol/serial-service';
import { ArduinoSelect } from '../../widgets/arduino-select'; import { ArduinoSelect } from '../../widgets/arduino-select';
import { SerialModel } from '../serial-model';
import { SerialConnectionManager } from '../serial-connection-manager';
import { SerialMonitorSendInput } from './serial-monitor-send-input'; import { SerialMonitorSendInput } from './serial-monitor-send-input';
import { SerialMonitorOutput } from './serial-monitor-send-output'; import { SerialMonitorOutput } from './serial-monitor-send-output';
import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { MonitorManagerProxyClient } from '../../../common/protocol';
import { MonitorModel } from '../../monitor-model';
import { MonitorSettings } from '../../../node/monitor-settings/monitor-settings-provider';
@injectable() @injectable()
export class MonitorWidget extends ReactWidget { export class MonitorWidget extends ReactWidget {
@@ -26,14 +26,7 @@ export class MonitorWidget extends ReactWidget {
); );
static readonly ID = 'serial-monitor'; static readonly ID = 'serial-monitor';
@inject(SerialModel) protected settings: MonitorSettings = {};
protected readonly serialModel: SerialModel;
@inject(SerialConnectionManager)
protected readonly serialConnection: SerialConnectionManager;
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider;
protected widgetHeight: number; protected widgetHeight: number;
@@ -48,7 +41,16 @@ export class MonitorWidget extends ReactWidget {
protected closing = false; protected closing = false;
protected readonly clearOutputEmitter = new Emitter<void>(); protected readonly clearOutputEmitter = new Emitter<void>();
constructor() { constructor(
@inject(MonitorModel)
protected readonly monitorModel: MonitorModel,
@inject(MonitorManagerProxyClient)
protected readonly monitorManagerProxy: MonitorManagerProxyClient,
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider
) {
super(); super();
this.id = MonitorWidget.ID; this.id = MonitorWidget.ID;
this.title.label = MonitorWidget.LABEL; this.title.label = MonitorWidget.LABEL;
@@ -57,17 +59,30 @@ export class MonitorWidget extends ReactWidget {
this.scrollOptions = undefined; this.scrollOptions = undefined;
this.toDispose.push(this.clearOutputEmitter); this.toDispose.push(this.clearOutputEmitter);
this.toDispose.push( this.toDispose.push(
Disposable.create(() => this.serialConnection.closeWStoBE()) Disposable.create(() => this.monitorManagerProxy.disconnect())
); );
} }
@postConstruct() protected override onBeforeAttach(msg: Message): void {
protected init(): void {
this.update(); this.update();
this.toDispose.push( this.toDispose.push(this.monitorModel.onChange(() => this.update()));
this.serialConnection.onConnectionChanged(() => this.clearConsole()) this.getCurrentSettings().then(this.onMonitorSettingsDidChange.bind(this));
this.monitorManagerProxy.onMonitorSettingsDidChange(
this.onMonitorSettingsDidChange.bind(this)
); );
this.toDispose.push(this.serialModel.onChange(() => this.update()));
this.monitorManagerProxy.startMonitor();
}
onMonitorSettingsDidChange(settings: MonitorSettings): void {
this.settings = {
...this.settings,
pluggableMonitorSettings: {
...this.settings.pluggableMonitorSettings,
...settings.pluggableMonitorSettings,
},
};
this.update();
} }
clearConsole(): void { clearConsole(): void {
@@ -75,21 +90,16 @@ export class MonitorWidget extends ReactWidget {
this.update(); this.update();
} }
dispose(): void { override dispose(): void {
super.dispose(); super.dispose();
} }
protected onAfterAttach(msg: Message): void { protected override onCloseRequest(msg: Message): void {
super.onAfterAttach(msg);
this.serialConnection.openWSToBE();
}
onCloseRequest(msg: Message): void {
this.closing = true; this.closing = true;
super.onCloseRequest(msg); super.onCloseRequest(msg);
} }
protected onUpdateRequest(msg: Message): void { protected override onUpdateRequest(msg: Message): void {
// TODO: `this.isAttached` // TODO: `this.isAttached`
// See: https://github.com/eclipse-theia/theia/issues/6704#issuecomment-562574713 // See: https://github.com/eclipse-theia/theia/issues/6704#issuecomment-562574713
if (!this.closing && this.isAttached) { if (!this.closing && this.isAttached) {
@@ -97,13 +107,13 @@ export class MonitorWidget extends ReactWidget {
} }
} }
protected onResize(msg: Widget.ResizeMessage): void { protected override onResize(msg: Widget.ResizeMessage): void {
super.onResize(msg); super.onResize(msg);
this.widgetHeight = msg.height; this.widgetHeight = msg.height;
this.update(); this.update();
} }
protected onActivateRequest(msg: Message): void { protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg); super.onActivateRequest(msg);
(this.focusNode || this.node).focus(); (this.focusNode || this.node).focus();
} }
@@ -119,7 +129,7 @@ export class MonitorWidget extends ReactWidget {
}; };
protected get lineEndings(): OptionsType< protected get lineEndings(): OptionsType<
SerialMonitorOutput.SelectOption<SerialModel.EOL> SerialMonitorOutput.SelectOption<MonitorModel.EOL>
> { > {
return [ return [
{ {
@@ -144,32 +154,40 @@ export class MonitorWidget extends ReactWidget {
]; ];
} }
protected get baudRates(): OptionsType< private getCurrentSettings(): Promise<MonitorSettings> {
SerialMonitorOutput.SelectOption<SerialConfig.BaudRate> const board = this.boardsServiceProvider.boardsConfig.selectedBoard;
> { const port = this.boardsServiceProvider.boardsConfig.selectedPort;
const baudRates: Array<SerialConfig.BaudRate> = [ if (!board || !port) {
300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, return Promise.resolve(this.settings || {});
]; }
return baudRates.map((baudRate) => ({ return this.monitorManagerProxy.getCurrentSettings(board, port);
label: baudRate + ' baud',
value: baudRate,
}));
} }
protected render(): React.ReactNode { protected render(): React.ReactNode {
const { baudRates, lineEndings } = this; const baudrate = this.settings?.pluggableMonitorSettings
? this.settings.pluggableMonitorSettings.baudrate
: undefined;
const baudrateOptions = baudrate?.values.map((b) => ({
label: b + ' baud',
value: b,
}));
const baudrateSelectedOption = baudrateOptions?.find(
(b) => b.value === baudrate?.selectedValue
);
const lineEnding = const lineEnding =
lineEndings.find((item) => item.value === this.serialModel.lineEnding) || this.lineEndings.find(
lineEndings[1]; // Defaults to `\n`. (item) => item.value === this.monitorModel.lineEnding
const baudRate = ) || this.lineEndings[1]; // Defaults to `\n`.
baudRates.find((item) => item.value === this.serialModel.baudRate) ||
baudRates[4]; // Defaults to `9600`.
return ( return (
<div className="serial-monitor"> <div className="serial-monitor">
<div className="head"> <div className="head">
<div className="send"> <div className="send">
<SerialMonitorSendInput <SerialMonitorSendInput
serialConnection={this.serialConnection} boardsServiceProvider={this.boardsServiceProvider}
monitorModel={this.monitorModel}
resolveFocus={this.onFocusResolved} resolveFocus={this.onFocusResolved}
onSend={this.onSend} onSend={this.onSend}
/> />
@@ -178,26 +196,28 @@ export class MonitorWidget extends ReactWidget {
<div className="select"> <div className="select">
<ArduinoSelect <ArduinoSelect
maxMenuHeight={this.widgetHeight - 40} maxMenuHeight={this.widgetHeight - 40}
options={lineEndings} options={this.lineEndings}
value={lineEnding} value={lineEnding}
onChange={this.onChangeLineEnding} onChange={this.onChangeLineEnding}
/> />
</div> </div>
<div className="select"> {baudrateOptions && baudrateSelectedOption && (
<ArduinoSelect <div className="select">
className="select" <ArduinoSelect
maxMenuHeight={this.widgetHeight - 40} className="select"
options={baudRates} maxMenuHeight={this.widgetHeight - 40}
value={baudRate} options={baudrateOptions}
onChange={this.onChangeBaudRate} value={baudrateSelectedOption}
/> onChange={this.onChangeBaudRate}
</div> />
</div>
)}
</div> </div>
</div> </div>
<div className="body"> <div className="body">
<SerialMonitorOutput <SerialMonitorOutput
serialModel={this.serialModel} monitorModel={this.monitorModel}
serialConnection={this.serialConnection} monitorManagerProxy={this.monitorManagerProxy}
clearConsoleEvent={this.clearOutputEmitter.event} clearConsoleEvent={this.clearOutputEmitter.event}
height={Math.floor(this.widgetHeight - 50)} height={Math.floor(this.widgetHeight - 50)}
/> />
@@ -208,18 +228,26 @@ export class MonitorWidget extends ReactWidget {
protected readonly onSend = (value: string) => this.doSend(value); protected readonly onSend = (value: string) => this.doSend(value);
protected async doSend(value: string): Promise<void> { protected async doSend(value: string): Promise<void> {
this.serialConnection.send(value); this.monitorManagerProxy.send(value);
} }
protected readonly onChangeLineEnding = ( protected readonly onChangeLineEnding = (
option: SerialMonitorOutput.SelectOption<SerialModel.EOL> option: SerialMonitorOutput.SelectOption<MonitorModel.EOL>
) => { ): void => {
this.serialModel.lineEnding = option.value; this.monitorModel.lineEnding = option.value;
}; };
protected readonly onChangeBaudRate = ( protected readonly onChangeBaudRate = ({
option: SerialMonitorOutput.SelectOption<SerialConfig.BaudRate> value,
) => { }: {
this.serialModel.baudRate = option.value; value: string;
}): void => {
this.getCurrentSettings().then(({ pluggableMonitorSettings }) => {
if (!pluggableMonitorSettings || !pluggableMonitorSettings['baudrate'])
return;
const baudRateSettings = pluggableMonitorSettings['baudrate'];
baudRateSettings.selectedValue = value;
this.monitorManagerProxy.changeSettings({ pluggableMonitorSettings });
});
}; };
} }

View File

@@ -1,14 +1,15 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { Key, KeyCode } from '@theia/core/lib/browser/keys'; import { Key, KeyCode } from '@theia/core/lib/browser/keys';
import { Board } from '../../../common/protocol/boards-service'; import { Board } from '../../../common/protocol/boards-service';
import { isOSX } from '@theia/core/lib/common/os'; import { isOSX } from '@theia/core/lib/common/os';
import { DisposableCollection, nls } from '@theia/core/lib/common'; import { DisposableCollection, nls } from '@theia/core/lib/common';
import { SerialConnectionManager } from '../serial-connection-manager'; import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { SerialPlotter } from '../plotter/protocol'; import { MonitorModel } from '../../monitor-model';
export namespace SerialMonitorSendInput { export namespace SerialMonitorSendInput {
export interface Props { export interface Props {
readonly serialConnection: SerialConnectionManager; readonly boardsServiceProvider: BoardsServiceProvider;
readonly monitorModel: MonitorModel;
readonly onSend: (text: string) => void; readonly onSend: (text: string) => void;
readonly resolveFocus: (element: HTMLElement | undefined) => void; readonly resolveFocus: (element: HTMLElement | undefined) => void;
} }
@@ -26,41 +27,33 @@ export class SerialMonitorSendInput extends React.Component<
constructor(props: Readonly<SerialMonitorSendInput.Props>) { constructor(props: Readonly<SerialMonitorSendInput.Props>) {
super(props); super(props);
this.state = { text: '', connected: false }; this.state = { text: '', connected: true };
this.onChange = this.onChange.bind(this); this.onChange = this.onChange.bind(this);
this.onSend = this.onSend.bind(this); this.onSend = this.onSend.bind(this);
this.onKeyDown = this.onKeyDown.bind(this); this.onKeyDown = this.onKeyDown.bind(this);
} }
componentDidMount(): void { override componentDidMount(): void {
this.props.serialConnection.isBESerialConnected().then((connected) => { this.setState({ connected: this.props.monitorModel.connected });
this.setState({ connected }); this.toDisposeBeforeUnmount.push(
}); this.props.monitorModel.onChange(({ property }) => {
if (property === 'connected')
this.toDisposeBeforeUnmount.pushAll([ this.setState({ connected: this.props.monitorModel.connected });
this.props.serialConnection.onRead(({ messages }) => { })
if ( );
messages.command ===
SerialPlotter.Protocol.Command.MIDDLEWARE_CONFIG_CHANGED &&
'connected' in messages.data
) {
this.setState({ connected: messages.data.connected });
}
}),
]);
} }
componentWillUnmount(): void { override componentWillUnmount(): void {
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout? // TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
this.toDisposeBeforeUnmount.dispose(); this.toDisposeBeforeUnmount.dispose();
} }
render(): React.ReactNode { override render(): React.ReactNode {
return ( return (
<input <input
ref={this.setRef} ref={this.setRef}
type="text" type="text"
className={`theia-input ${this.state.connected ? '' : 'warning'}`} className={`theia-input ${this.shouldShowWarning() ? 'warning' : ''}`}
placeholder={this.placeholder} placeholder={this.placeholder}
value={this.state.text} value={this.state.text}
onChange={this.onChange} onChange={this.onChange}
@@ -69,15 +62,22 @@ export class SerialMonitorSendInput extends React.Component<
); );
} }
protected shouldShowWarning(): boolean {
const board = this.props.boardsServiceProvider.boardsConfig.selectedBoard;
const port = this.props.boardsServiceProvider.boardsConfig.selectedPort;
return !this.state.connected || !board || !port;
}
protected get placeholder(): string { protected get placeholder(): string {
const serialConfig = this.props.serialConnection.getConfig(); if (this.shouldShowWarning()) {
if (!this.state.connected || !serialConfig) {
return nls.localize( return nls.localize(
'arduino/serial/notConnected', 'arduino/serial/notConnected',
'Not connected. Select a board and a port to connect automatically.' 'Not connected. Select a board and a port to connect automatically.'
); );
} }
const { board, port } = serialConfig;
const board = this.props.boardsServiceProvider.boardsConfig.selectedBoard;
const port = this.props.boardsServiceProvider.boardsConfig.selectedPort;
return nls.localize( return nls.localize(
'arduino/serial/message', 'arduino/serial/message',
"Message ({0} + Enter to send message to '{1}' on '{2}')", "Message ({0} + Enter to send message to '{1}' on '{2}')",
@@ -102,7 +102,7 @@ export class SerialMonitorSendInput extends React.Component<
} }
protected onSend(): void { protected onSend(): void {
this.props.onSend(this.state.text); this.props.onSend(this.state.text + this.props.monitorModel.lineEnding);
this.setState({ text: '' }); this.setState({ text: '' });
} }

View File

@@ -1,11 +1,11 @@
import * as React from 'react'; import * as React from '@theia/core/shared/react';
import { Event } from '@theia/core/lib/common/event'; import { Event } from '@theia/core/lib/common/event';
import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { areEqual, FixedSizeList as List } from 'react-window'; import { areEqual, FixedSizeList as List } from 'react-window';
import { SerialModel } from '../serial-model';
import { SerialConnectionManager } from '../serial-connection-manager';
import dateFormat = require('dateformat'); import dateFormat = require('dateformat');
import { messagesToLines, truncateLines } from './monitor-utils'; import { messagesToLines, truncateLines } from './monitor-utils';
import { MonitorManagerProxyClient } from '../../../common/protocol';
import { MonitorModel } from '../../monitor-model';
export type Line = { message: string; timestamp?: Date; lineLen: number }; export type Line = { message: string; timestamp?: Date; lineLen: number };
@@ -24,12 +24,12 @@ export class SerialMonitorOutput extends React.Component<
this.listRef = React.createRef(); this.listRef = React.createRef();
this.state = { this.state = {
lines: [], lines: [],
timestamp: this.props.serialModel.timestamp, timestamp: this.props.monitorModel.timestamp,
charCount: 0, charCount: 0,
}; };
} }
render(): React.ReactNode { override render(): React.ReactNode {
return ( return (
<List <List
className="serial-monitor-messages" className="serial-monitor-messages"
@@ -51,21 +51,20 @@ export class SerialMonitorOutput extends React.Component<
); );
} }
shouldComponentUpdate(): boolean { override shouldComponentUpdate(): boolean {
return true; return true;
} }
componentDidMount(): void { override componentDidMount(): void {
this.scrollToBottom(); this.scrollToBottom();
this.toDisposeBeforeUnmount.pushAll([ this.toDisposeBeforeUnmount.pushAll([
this.props.serialConnection.onRead(({ messages }) => { this.props.monitorManagerProxy.onMessagesReceived(({ messages }) => {
const [newLines, totalCharCount] = messagesToLines( const [newLines, totalCharCount] = messagesToLines(
messages, messages,
this.state.lines, this.state.lines,
this.state.charCount this.state.charCount
); );
const [lines, charCount] = truncateLines(newLines, totalCharCount); const [lines, charCount] = truncateLines(newLines, totalCharCount);
this.setState({ this.setState({
lines, lines,
charCount, charCount,
@@ -75,9 +74,9 @@ export class SerialMonitorOutput extends React.Component<
this.props.clearConsoleEvent(() => this.props.clearConsoleEvent(() =>
this.setState({ lines: [], charCount: 0 }) this.setState({ lines: [], charCount: 0 })
), ),
this.props.serialModel.onChange(({ property }) => { this.props.monitorModel.onChange(({ property }) => {
if (property === 'timestamp') { if (property === 'timestamp') {
const { timestamp } = this.props.serialModel; const { timestamp } = this.props.monitorModel;
this.setState({ timestamp }); this.setState({ timestamp });
} }
if (property === 'autoscroll') { if (property === 'autoscroll') {
@@ -87,13 +86,13 @@ export class SerialMonitorOutput extends React.Component<
]); ]);
} }
componentWillUnmount(): void { override componentWillUnmount(): void {
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout? // TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
this.toDisposeBeforeUnmount.dispose(); this.toDisposeBeforeUnmount.dispose();
} }
scrollToBottom = ((): void => { scrollToBottom = ((): void => {
if (this.listRef.current && this.props.serialModel.autoscroll) { if (this.listRef.current && this.props.monitorModel.autoscroll) {
this.listRef.current.scrollToItem(this.state.lines.length, 'end'); this.listRef.current.scrollToItem(this.state.lines.length, 'end');
} }
}).bind(this); }).bind(this);
@@ -128,8 +127,8 @@ const Row = React.memo(_Row, areEqual);
export namespace SerialMonitorOutput { export namespace SerialMonitorOutput {
export interface Props { export interface Props {
readonly serialModel: SerialModel; readonly monitorModel: MonitorModel;
readonly serialConnection: SerialConnectionManager; readonly monitorManagerProxy: MonitorManagerProxyClient;
readonly clearConsoleEvent: Event<void>; readonly clearConsoleEvent: Event<void>;
readonly height: number; readonly height: number;
} }

View File

@@ -1,20 +1,19 @@
import { ThemeService } from '@theia/core/lib/browser/theming'; import { ThemeService } from '@theia/core/lib/browser/theming';
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { import {
Command, Command,
CommandRegistry, CommandRegistry,
MaybePromise, MaybePromise,
MenuModelRegistry, MenuModelRegistry,
} from '@theia/core'; } from '@theia/core';
import { SerialModel } from '../serial-model';
import { ArduinoMenus } from '../../menu/arduino-menus'; import { ArduinoMenus } from '../../menu/arduino-menus';
import { Contribution } from '../../contributions/contribution'; import { Contribution } from '../../contributions/contribution';
import { Endpoint, FrontendApplication } from '@theia/core/lib/browser'; import { Endpoint, FrontendApplication } from '@theia/core/lib/browser';
import { ipcRenderer } from '@theia/electron/shared/electron'; import { ipcRenderer } from '@theia/electron/shared/electron';
import { SerialConfig } from '../../../common/protocol'; import { MonitorManagerProxyClient } from '../../../common/protocol';
import { SerialConnectionManager } from '../serial-connection-manager';
import { SerialPlotter } from './protocol';
import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { MonitorModel } from '../../monitor-model';
const queryString = require('query-string'); const queryString = require('query-string');
export namespace SerialPlotterContribution { export namespace SerialPlotterContribution {
@@ -24,6 +23,11 @@ export namespace SerialPlotterContribution {
label: 'Serial Plotter', label: 'Serial Plotter',
category: 'Arduino', category: 'Arduino',
}; };
export const RESET: Command = {
id: 'serial-plotter-reset',
label: 'Reset Serial Plotter',
category: 'Arduino',
};
} }
} }
@@ -33,19 +37,19 @@ export class PlotterFrontendContribution extends Contribution {
protected url: string; protected url: string;
protected wsPort: number; protected wsPort: number;
@inject(SerialModel) @inject(MonitorModel)
protected readonly model: SerialModel; protected readonly model: MonitorModel;
@inject(ThemeService) @inject(ThemeService)
protected readonly themeService: ThemeService; protected readonly themeService: ThemeService;
@inject(SerialConnectionManager) @inject(MonitorManagerProxyClient)
protected readonly serialConnection: SerialConnectionManager; protected readonly monitorManagerProxy: MonitorManagerProxyClient;
@inject(BoardsServiceProvider) @inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider; protected readonly boardsServiceProvider: BoardsServiceProvider;
onStart(app: FrontendApplication): MaybePromise<void> { override onStart(app: FrontendApplication): MaybePromise<void> {
this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString(); this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString();
ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => { ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => {
@@ -53,16 +57,21 @@ export class PlotterFrontendContribution extends Contribution {
this.window = null; this.window = null;
} }
}); });
this.monitorManagerProxy.onMonitorShouldReset(() => this.reset());
return super.onStart(app); return super.onStart(app);
} }
registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(SerialPlotterContribution.Commands.OPEN, { registry.registerCommand(SerialPlotterContribution.Commands.OPEN, {
execute: this.connect.bind(this), execute: this.startPlotter.bind(this),
});
registry.registerCommand(SerialPlotterContribution.Commands.RESET, {
execute: () => this.reset(),
}); });
} }
registerMenus(menus: MenuModelRegistry): void { override registerMenus(menus: MenuModelRegistry): void {
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: SerialPlotterContribution.Commands.OPEN.id, commandId: SerialPlotterContribution.Commands.OPEN.id,
label: SerialPlotterContribution.Commands.OPEN.label, label: SerialPlotterContribution.Commands.OPEN.label,
@@ -70,12 +79,13 @@ export class PlotterFrontendContribution extends Contribution {
}); });
} }
async connect(): Promise<void> { async startPlotter(): Promise<void> {
await this.monitorManagerProxy.startMonitor();
if (!!this.window) { if (!!this.window) {
this.window.focus(); this.window.focus();
return; return;
} }
const wsPort = this.serialConnection.getWsPort(); const wsPort = this.monitorManagerProxy.getWebSocketPort();
if (wsPort) { if (wsPort) {
this.open(wsPort); this.open(wsPort);
} else { } else {
@@ -84,15 +94,10 @@ export class PlotterFrontendContribution extends Contribution {
} }
protected async open(wsPort: number): Promise<void> { protected async open(wsPort: number): Promise<void> {
const initConfig: Partial<SerialPlotter.Config> = { const initConfig = {
baudrates: SerialConfig.BaudRates.map((b) => b),
currentBaudrate: this.model.baudRate,
currentLineEnding: this.model.lineEnding,
darkTheme: this.themeService.getCurrentTheme().type === 'dark', darkTheme: this.themeService.getCurrentTheme().type === 'dark',
wsPort, wsPort,
interpolate: this.model.interpolate, serialPort: this.model.serialPort,
connected: await this.serialConnection.isBESerialConnected(),
serialPort: this.boardsServiceProvider.boardsConfig.selectedPort?.address,
}; };
const urlWithParams = queryString.stringifyUrl( const urlWithParams = queryString.stringifyUrl(
{ {
@@ -103,4 +108,11 @@ export class PlotterFrontendContribution extends Contribution {
); );
this.window = window.open(urlWithParams, 'serialPlotter'); this.window = window.open(urlWithParams, 'serialPlotter');
} }
protected async reset(): Promise<void> {
if (!!this.window) {
this.window.close();
await this.startPlotter();
}
}
} }

View File

@@ -1,26 +0,0 @@
export namespace SerialPlotter {
export type Config = {
currentBaudrate: number;
baudrates: number[];
currentLineEnding: string;
darkTheme: boolean;
wsPort: number;
interpolate: boolean;
serialPort: string;
connected: boolean;
generate?: boolean;
};
export namespace Protocol {
export enum Command {
PLOTTER_SET_BAUDRATE = 'PLOTTER_SET_BAUDRATE',
PLOTTER_SET_LINE_ENDING = 'PLOTTER_SET_LINE_ENDING',
PLOTTER_SET_INTERPOLATE = 'PLOTTER_SET_INTERPOLATE',
PLOTTER_SEND_MESSAGE = 'PLOTTER_SEND_MESSAGE',
MIDDLEWARE_CONFIG_CHANGED = 'MIDDLEWARE_CONFIG_CHANGED',
}
export type Message = {
command: SerialPlotter.Protocol.Command;
data?: any;
};
}
}

View File

@@ -1,360 +0,0 @@
import { injectable, inject } from 'inversify';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { MessageService } from '@theia/core/lib/common/message-service';
import {
SerialService,
SerialConfig,
SerialError,
Status,
SerialServiceClient,
} from '../../common/protocol/serial-service';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import {
Board,
BoardsService,
} from '../../common/protocol/boards-service';
import { BoardsConfig } from '../boards/boards-config';
import { SerialModel } from './serial-model';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { CoreService } from '../../common/protocol';
import { nls } from '@theia/core/lib/common/nls';
@injectable()
export class SerialConnectionManager {
protected config: Partial<SerialConfig> = {
board: undefined,
port: undefined,
baudRate: undefined,
};
protected readonly onConnectionChangedEmitter = new Emitter<boolean>();
/**
* This emitter forwards all read events **if** the connection is established.
*/
protected readonly onReadEmitter = new Emitter<{ messages: string[] }>();
/**
* Array for storing previous serial 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 serialErrors: SerialError[] = [];
protected reconnectTimeout?: number;
/**
* When the websocket server is up on the backend, we save the port here, so that the client knows how to connect to it
* */
protected wsPort?: number;
protected webSocket?: WebSocket;
constructor(
@inject(SerialModel) protected readonly serialModel: SerialModel,
@inject(SerialService) protected readonly serialService: SerialService,
@inject(SerialServiceClient)
protected readonly serialServiceClient: SerialServiceClient,
@inject(BoardsService) protected readonly boardsService: BoardsService,
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider,
@inject(MessageService) protected messageService: MessageService,
@inject(ThemeService) protected readonly themeService: ThemeService,
@inject(CoreService) protected readonly core: CoreService,
@inject(BoardsServiceProvider)
protected readonly boardsServiceClientImpl: BoardsServiceProvider
) {
this.serialServiceClient.onWebSocketChanged(
this.handleWebSocketChanged.bind(this)
);
this.serialServiceClient.onBaudRateChanged((baudRate) => {
if (this.serialModel.baudRate !== baudRate) {
this.serialModel.baudRate = baudRate;
}
});
this.serialServiceClient.onLineEndingChanged((lineending) => {
if (this.serialModel.lineEnding !== lineending) {
this.serialModel.lineEnding = lineending;
}
});
this.serialServiceClient.onInterpolateChanged((interpolate) => {
if (this.serialModel.interpolate !== interpolate) {
this.serialModel.interpolate = interpolate;
}
});
this.serialServiceClient.onError(this.handleError.bind(this));
this.boardsServiceProvider.onBoardsConfigChanged(
this.handleBoardConfigChange.bind(this)
);
// Handles the `baudRate` changes by reconnecting if required.
this.serialModel.onChange(async ({ property }) => {
if (
property === 'baudRate' &&
(await this.serialService.isSerialPortOpen())
) {
const { boardsConfig } = this.boardsServiceProvider;
this.handleBoardConfigChange(boardsConfig);
}
// update the current values in the backend and propagate to websocket clients
this.serialService.updateWsConfigParam({
...(property === 'lineEnding' && {
currentLineEnding: this.serialModel.lineEnding,
}),
...(property === 'interpolate' && {
interpolate: this.serialModel.interpolate,
}),
});
});
this.themeService.onDidColorThemeChange((theme) => {
this.serialService.updateWsConfigParam({
darkTheme: theme.newTheme.type === 'dark',
});
});
}
/**
* Updated the config in the BE passing only the properties that has changed.
* BE will create a new connection if needed.
*
* @param newConfig the porperties of the config that has changed
*/
async setConfig(newConfig: Partial<SerialConfig>): Promise<void> {
let configHasChanged = false;
Object.keys(this.config).forEach((key: keyof SerialConfig) => {
if (newConfig[key] !== this.config[key]) {
configHasChanged = true;
this.config = { ...this.config, [key]: newConfig[key] };
}
});
if (configHasChanged) {
this.serialService.updateWsConfigParam({
currentBaudrate: this.config.baudRate,
serialPort: this.config.port?.address,
});
if (isSerialConfig(this.config)) {
this.serialService.setSerialConfig(this.config);
}
}
}
getConfig(): Partial<SerialConfig> {
return this.config;
}
getWsPort(): number | undefined {
return this.wsPort;
}
protected handleWebSocketChanged(wsPort: number): void {
this.wsPort = wsPort;
}
get serialConfig(): SerialConfig | undefined {
return isSerialConfig(this.config)
? (this.config as SerialConfig)
: undefined;
}
async isBESerialConnected(): Promise<boolean> {
return await this.serialService.isSerialPortOpen();
}
openWSToBE(): void {
if (!isSerialConfig(this.config)) {
this.messageService.error(
`Please select a board and a port to open the serial connection.`
);
}
if (!this.webSocket && this.wsPort) {
try {
this.webSocket = new WebSocket(`ws://localhost:${this.wsPort}`);
this.webSocket.onmessage = (res) => {
const messages = JSON.parse(res.data);
this.onReadEmitter.fire({ messages });
};
} catch {
this.messageService.error(`Unable to connect to websocket`);
}
}
}
closeWStoBE(): void {
if (this.webSocket) {
try {
this.webSocket.close();
this.webSocket = undefined;
} catch {
this.messageService.error(`Unable to close websocket`);
}
}
}
/**
* Handles error on the SerialServiceClient and try to reconnect, eventually
*/
async handleError(error: SerialError): Promise<void> {
if (!(await this.serialService.isSerialPortOpen())) return;
const { code, config } = error;
const { board, port } = config;
const options = { timeout: 3000 };
switch (code) {
case SerialError.ErrorCodes.CLIENT_CANCEL: {
console.debug(
`Serial connection was canceled by client: ${Serial.Config.toString(
this.config
)}.`
);
break;
}
case SerialError.ErrorCodes.DEVICE_BUSY: {
this.messageService.warn(
nls.localize(
'arduino/serial/connectionBusy',
'Connection failed. Serial port is busy: {0}',
port.address
),
options
);
this.serialErrors.push(error);
break;
}
case SerialError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
this.messageService.info(
nls.localize(
'arduino/serial/disconnected',
'Disconnected {0} from {1}.',
Board.toString(board, {
useFqbn: false,
}),
port.address
),
options
);
break;
}
case undefined: {
this.messageService.error(
nls.localize(
'arduino/serial/unexpectedError',
'Unexpected error. Reconnecting {0} on port {1}.',
Board.toString(board),
port.address
),
options
);
console.error(JSON.stringify(error));
break;
}
}
if ((await this.serialService.clientsAttached()) > 0) {
if (this.serialErrors.length >= 10) {
this.messageService.warn(
nls.localize(
'arduino/serial/failedReconnect',
'Failed to reconnect {0} to serial port after 10 consecutive attempts. The {1} serial port is busy.',
Board.toString(board, {
useFqbn: false,
}),
port.address
)
);
this.serialErrors.length = 0;
} else {
const attempts = this.serialErrors.length || 1;
if (this.reconnectTimeout !== undefined) {
// Clear the previous timer.
window.clearTimeout(this.reconnectTimeout);
}
const timeout = attempts * 1000;
this.messageService.warn(
nls.localize(
'arduino/serial/reconnect',
'Reconnecting {0} to {1} in {2} seconds...',
Board.toString(board, {
useFqbn: false,
}),
port.address,
attempts.toString()
)
);
this.reconnectTimeout = window.setTimeout(
() => this.reconnectAfterUpload(),
timeout
);
}
}
}
async reconnectAfterUpload(): Promise<void> {
try {
if (isSerialConfig(this.config)) {
await this.boardsServiceClientImpl.waitUntilAvailable(
Object.assign(this.config.board, { port: this.config.port }),
10_000
);
this.serialService.connectSerialIfRequired();
}
} catch (waitError) {
this.messageService.error(
nls.localize(
'arduino/sketch/couldNotConnectToSerial',
'Could not reconnect to serial port. {0}',
waitError.toString()
)
);
}
}
/**
* Sends the data to the connected serial port.
* 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 (!(await this.serialService.isSerialPortOpen())) {
return Status.NOT_CONNECTED;
}
return new Promise<Status>((resolve) => {
this.serialService
.sendMessageToSerial(data + this.serialModel.lineEnding)
.then(() => resolve(Status.OK));
});
}
get onConnectionChanged(): Event<boolean> {
return this.onConnectionChangedEmitter.event;
}
get onRead(): Event<{ messages: any }> {
return this.onReadEmitter.event;
}
protected async handleBoardConfigChange(
boardsConfig: BoardsConfig.Config
): Promise<void> {
const { selectedBoard: board, selectedPort: port } = boardsConfig;
const { baudRate } = this.serialModel;
const newConfig: Partial<SerialConfig> = { board, port, baudRate };
this.setConfig(newConfig);
}
}
export namespace Serial {
export namespace Config {
export function toString(config: Partial<SerialConfig>): string {
if (!isSerialConfig(config)) return '';
const { board, port } = config;
return `${Board.toString(board)} ${port.address}`;
}
}
}
function isSerialConfig(config: Partial<SerialConfig>): config is SerialConfig {
return !!config.board && !!config.baudRate && !!config.port;
}

View File

@@ -1,163 +0,0 @@
import { injectable, inject } from 'inversify';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { SerialConfig } from '../../common/protocol';
import {
FrontendApplicationContribution,
LocalStorageService,
} from '@theia/core/lib/browser';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
@injectable()
export class SerialModel implements FrontendApplicationContribution {
protected static STORAGE_ID = 'arduino-serial-model';
@inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService;
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
protected readonly onChangeEmitter: Emitter<
SerialModel.State.Change<keyof SerialModel.State>
>;
protected _autoscroll: boolean;
protected _timestamp: boolean;
protected _baudRate: SerialConfig.BaudRate;
protected _lineEnding: SerialModel.EOL;
protected _interpolate: boolean;
constructor() {
this._autoscroll = true;
this._timestamp = false;
this._baudRate = SerialConfig.BaudRate.DEFAULT;
this._lineEnding = SerialModel.EOL.DEFAULT;
this._interpolate = false;
this.onChangeEmitter = new Emitter<
SerialModel.State.Change<keyof SerialModel.State>
>();
}
onStart(): void {
this.localStorageService
.getData<SerialModel.State>(SerialModel.STORAGE_ID)
.then((state) => {
if (state) {
this.restoreState(state);
}
});
}
get onChange(): Event<SerialModel.State.Change<keyof SerialModel.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;
}
toggleTimestamp(): void {
this._timestamp = !this._timestamp;
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'timestamp',
value: this._timestamp,
})
);
}
get baudRate(): SerialConfig.BaudRate {
return this._baudRate;
}
set baudRate(baudRate: SerialConfig.BaudRate) {
this._baudRate = baudRate;
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'baudRate',
value: this._baudRate,
})
);
}
get lineEnding(): SerialModel.EOL {
return this._lineEnding;
}
set lineEnding(lineEnding: SerialModel.EOL) {
this._lineEnding = lineEnding;
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'lineEnding',
value: this._lineEnding,
})
);
}
get interpolate(): boolean {
return this._interpolate;
}
set interpolate(i: boolean) {
this._interpolate = i;
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'interpolate',
value: this._interpolate,
})
);
}
protected restoreState(state: SerialModel.State): void {
this._autoscroll = state.autoscroll;
this._timestamp = state.timestamp;
this._baudRate = state.baudRate;
this._lineEnding = state.lineEnding;
this._interpolate = state.interpolate;
}
protected async storeState(): Promise<void> {
return this.localStorageService.setData(SerialModel.STORAGE_ID, {
autoscroll: this._autoscroll,
timestamp: this._timestamp,
baudRate: this._baudRate,
lineEnding: this._lineEnding,
interpolate: this._interpolate,
});
}
}
export namespace SerialModel {
export interface State {
autoscroll: boolean;
timestamp: boolean;
baudRate: SerialConfig.BaudRate;
lineEnding: EOL;
interpolate: boolean;
}
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

@@ -1,48 +0,0 @@
import { injectable } from 'inversify';
import { Emitter } from '@theia/core/lib/common/event';
import {
SerialServiceClient,
SerialError,
SerialConfig,
} from '../../common/protocol/serial-service';
import { SerialModel } from './serial-model';
@injectable()
export class SerialServiceClientImpl implements SerialServiceClient {
protected readonly onErrorEmitter = new Emitter<SerialError>();
readonly onError = this.onErrorEmitter.event;
protected readonly onWebSocketChangedEmitter = new Emitter<number>();
readonly onWebSocketChanged = this.onWebSocketChangedEmitter.event;
protected readonly onBaudRateChangedEmitter =
new Emitter<SerialConfig.BaudRate>();
readonly onBaudRateChanged = this.onBaudRateChangedEmitter.event;
protected readonly onLineEndingChangedEmitter =
new Emitter<SerialModel.EOL>();
readonly onLineEndingChanged = this.onLineEndingChangedEmitter.event;
protected readonly onInterpolateChangedEmitter = new Emitter<boolean>();
readonly onInterpolateChanged = this.onInterpolateChangedEmitter.event;
notifyError(error: SerialError): void {
this.onErrorEmitter.fire(error);
}
notifyWebSocketChanged(message: number): void {
this.onWebSocketChangedEmitter.fire(message);
}
notifyBaudRateChanged(message: SerialConfig.BaudRate): void {
this.onBaudRateChangedEmitter.fire(message);
}
notifyLineEndingChanged(message: SerialModel.EOL): void {
this.onLineEndingChangedEmitter.fire(message);
}
notifyInterpolateChanged(message: boolean): void {
this.onInterpolateChangedEmitter.fire(message);
}
}

View File

@@ -1,4 +1,4 @@
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { StorageService } from '@theia/core/lib/browser/storage-service'; import { StorageService } from '@theia/core/lib/browser/storage-service';
import { import {
Command, Command,

View File

@@ -8,3 +8,8 @@
.monaco-list-row.show-file-icons.focused { .monaco-list-row.show-file-icons.focused {
background-color: #d6ebff; background-color: #d6ebff;
} }
.monaco-editor .view-overlays .compiler-error {
background-color: var(--theia-inputValidation-errorBackground);
opacity: 0.4 !important;
}

View File

@@ -27,8 +27,9 @@
} }
.ide-updater-dialog .changelog-container { .ide-updater-dialog .changelog-container {
background: white; color: var(--theia-dropdown-foreground);
border: 1px solid #dae3e3; background-color: var(--theia-dropdown-background);
border: 1px solid var(--theia-tree-indentGuidesStroke);
border-radius: 2px; border-radius: 2px;
font-size: 12px; font-size: 12px;
height: 180px; height: 180px;

View File

@@ -108,3 +108,21 @@ button.theia-button.main {
.fa-reload { .fa-reload {
font-size: 14px; font-size: 14px;
} }
/* restore the old Theia spinner */
/* https://github.com/eclipse-theia/theia/pull/10761#issuecomment-1131476318 */
.old-theia-preload {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 50000;
background: var(--theia-editor-background);
background-image: var(--theia-preloader);
background-size: 60px 60px;
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
transition: opacity 0.8s;
}

View File

@@ -0,0 +1,10 @@
import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog';
import { duration } from '../../../common/decorators';
export class AboutDialog extends TheiaAboutDialog {
@duration({ name: 'theia-about#init' })
protected override async init(): Promise<void> {
// NOOP
// IDE2 has a custom about dialog, so it does not make sense to collect Theia extensions at startup time.
}
}

View File

@@ -1,4 +1,4 @@
import { injectable, inject } from 'inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { EditorWidget } from '@theia/editor/lib/browser'; import { EditorWidget } from '@theia/editor/lib/browser';
import { CommandService } from '@theia/core/lib/common/command'; import { CommandService } from '@theia/core/lib/common/command';
import { MessageService } from '@theia/core/lib/common/message-service'; import { MessageService } from '@theia/core/lib/common/message-service';
@@ -15,7 +15,7 @@ import {
} from '@theia/core/lib/browser'; } from '@theia/core/lib/browser';
import { Sketch } from '../../../common/protocol'; import { Sketch } from '../../../common/protocol';
import { SaveAsSketch } from '../../contributions/save-as-sketch'; import { SaveAsSketch } from '../../contributions/save-as-sketch';
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
@@ -33,7 +33,7 @@ export class ApplicationShell extends TheiaApplicationShell {
@inject(ConnectionStatusService) @inject(ConnectionStatusService)
protected readonly connectionStatusService: ConnectionStatusService; protected readonly connectionStatusService: ConnectionStatusService;
protected track(widget: Widget): void { protected override track(widget: Widget): void {
super.track(widget); super.track(widget);
if (widget instanceof OutputWidget) { if (widget instanceof OutputWidget) {
widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700 widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
@@ -41,7 +41,7 @@ export class ApplicationShell extends TheiaApplicationShell {
if (widget instanceof EditorWidget) { if (widget instanceof EditorWidget) {
// Make the editor un-closeable asynchronously. // Make the editor un-closeable asynchronously.
this.sketchesServiceClient.currentSketch().then((sketch) => { this.sketchesServiceClient.currentSketch().then((sketch) => {
if (sketch) { if (CurrentSketch.isValid(sketch)) {
if (!this.isSketchFile(widget.editor.uri, sketch.uri)) { if (!this.isSketchFile(widget.editor.uri, sketch.uri)) {
return; return;
} }
@@ -61,7 +61,7 @@ export class ApplicationShell extends TheiaApplicationShell {
return false; return false;
} }
async addWidget( override async addWidget(
widget: Widget, widget: Widget,
options: Readonly<TheiaApplicationShell.WidgetOptions> = {} options: Readonly<TheiaApplicationShell.WidgetOptions> = {}
): Promise<void> { ): Promise<void> {
@@ -87,19 +87,19 @@ export class ApplicationShell extends TheiaApplicationShell {
return super.addWidget(widget, { ...options, ref }); return super.addWidget(widget, { ...options, ref });
} }
handleEvent(): boolean { override handleEvent(): boolean {
// NOOP, dragging has been disabled // NOOP, dragging has been disabled
return false return false;
} }
// Avoid hiding top panel as we use it for arduino toolbar // Avoid hiding top panel as we use it for arduino toolbar
protected createTopPanel(): Panel { protected override createTopPanel(): Panel {
const topPanel = super.createTopPanel(); const topPanel = super.createTopPanel();
topPanel.show(); topPanel.show();
return topPanel; return topPanel;
} }
async saveAll(): Promise<void> { override async saveAll(): Promise<void> {
if ( if (
this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE
) { ) {
@@ -130,5 +130,5 @@ DockPanel.prototype.handleEvent = function (event) {
case 'p-drop': case 'p-drop':
return; return;
} }
originalHandleEvent(event); originalHandleEvent.bind(this)(event);
}; };

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