mirror of
				https://github.com/arduino/arduino-ide.git
				synced 2025-10-30 21:48:33 +00:00 
			
		
		
		
	Compare commits
	
		
			37 Commits
		
	
	
		
			pluggable-
			...
			2.0.0-rc8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 439cdfbbff | ||
|   | 672fd4e4b0 | ||
|   | 0f1d379e58 | ||
|   | a79c9b4449 | ||
|   | 0f8a29a493 | ||
|   | a54d7c8f45 | ||
|   | 84109e416a | ||
|   | 083337de1c | ||
|   | bd6bc135fd | ||
|   | 4611381a38 | ||
|   | d6f4096cd0 | ||
|   | a715da3d18 | ||
|   | 94ceefd960 | ||
|   | 27dd120e5d | ||
|   | f5cee97fef | ||
|   | a9aac0dbb0 | ||
|   | 4c6243176c | ||
|   | a8047660a6 | ||
|   | 7c2843f7fd | ||
|   | fd5154ae93 | ||
|   | 726628e20c | ||
|   | 585a82b51a | ||
|   | 5edccb9c35 | ||
|   | 555da878f4 | ||
|   | df8658eff9 | ||
|   | 4c55807392 | ||
| ![github-actions[bot]](/assets/img/avatar_default.png)  | cb50d3a70d | ||
|   | eaf14aa1eb | ||
|   | a59e0da2af | ||
|   | 3a3ac6da4e | ||
|   | d7809616a4 | ||
|   | 5b486b1480 | ||
|   | 5fc30bd33e | ||
|   | 522a5c6e01 | ||
|   | 1ae60ec9bc | ||
|   | b8c718ce9e | ||
|   | b407d0aee0 | 
							
								
								
									
										1
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										49
									
								
								.github/workflows/themes-weekly-pull.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								.github/workflows/themes-weekly-pull.yml
									
									
									
									
										vendored
									
									
										Normal 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
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||||
|   | |||||||
| @@ -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
									
									
								
							
							
						
						
									
										40
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @@ -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": [ | ||||||
|   | |||||||
| @@ -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" | ||||||
|   | |||||||
| @@ -4,8 +4,8 @@ | |||||||
| const version = '1.9.1'; | const version = '1.9.1'; | ||||||
|  |  | ||||||
| (async () => { | (async () => { | ||||||
|  |  | ||||||
|   const os = require('os'); |   const os = require('os'); | ||||||
|  |   const { promises: fs } = require('fs'); | ||||||
|   const path = require('path'); |   const path = require('path'); | ||||||
|   const shell = require('shelljs'); |   const shell = require('shelljs'); | ||||||
|   const { v4 } = require('uuid'); |   const { v4 } = require('uuid'); | ||||||
| @@ -13,21 +13,84 @@ const version = '1.9.1'; | |||||||
|   const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`); |   const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`); | ||||||
|   if (shell.mkdir('-p', repository).code !== 0) { |   if (shell.mkdir('-p', repository).code !== 0) { | ||||||
|     shell.exit(1); |     shell.exit(1); | ||||||
|         process.exit(1); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|     if (shell.exec(`git clone https://github.com/arduino/arduino-examples.git ${repository}`).code !== 0) { |   if ( | ||||||
|  |     shell.exec( | ||||||
|  |       `git clone https://github.com/arduino/arduino-examples.git ${repository}` | ||||||
|  |     ).code !== 0 | ||||||
|  |   ) { | ||||||
|     shell.exit(1); |     shell.exit(1); | ||||||
|         process.exit(1); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|     if (shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`).code !== 0) { |   if ( | ||||||
|  |     shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`) | ||||||
|  |       .code !== 0 | ||||||
|  |   ) { | ||||||
|     shell.exit(1); |     shell.exit(1); | ||||||
|         process.exit(1); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   const destination = path.join(__dirname, '..', 'Examples'); |   const destination = path.join(__dirname, '..', 'Examples'); | ||||||
|   shell.mkdir('-p', destination); |   shell.mkdir('-p', destination); | ||||||
|   shell.cp('-fR', path.join(repository, 'examples', '*'), 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; | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |   const examples = []; | ||||||
|  |   const categories = await fs.readdir(destination); | ||||||
|  |   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); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |   for (const category of categories) { | ||||||
|  |     const example = { | ||||||
|  |       label: category, | ||||||
|  |       children: [], | ||||||
|  |       sketches: [], | ||||||
|  |     }; | ||||||
|  |     await visit(path.join(destination, category), example); | ||||||
|  |     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')}`); | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -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, | ||||||
|  |     } | ||||||
|  |   ); | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -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) { | ||||||
|  |           const editorWidget = editor; | ||||||
|           if (SaveableWidget.is(editorWidget)) { |           if (SaveableWidget.is(editorWidget)) { | ||||||
|             editorWidget.closeWithoutSaving(); |             editorWidget.closeWithoutSaving(); | ||||||
|           } else { |           } else { | ||||||
|             editorWidget.close(); |             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; | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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,7 +541,8 @@ 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(); |         const container = parentContainer.createChild(); | ||||||
|         container.bind(TabBarToolbar).toSelf().inSingletonScope(); |         container.bind(TabBarToolbar).toSelf().inSingletonScope(); | ||||||
|         return container.get(TabBarToolbar); |         return container.get(TabBarToolbar); | ||||||
| @@ -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); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -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'); | ||||||
|   | |||||||
| @@ -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'], | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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( | ||||||
|   | |||||||
| @@ -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.toDispose.pushAll([ | ||||||
|  |       this.props.onAppStateDidChange((state) => { | ||||||
|  |         if (state === 'ready') { | ||||||
|           this.updateBoards(); |           this.updateBoards(); | ||||||
|           this.updatePorts( |           this.updatePorts( | ||||||
|             this.props.boardsServiceProvider.availableBoards |             this.props.boardsServiceProvider.availableBoards | ||||||
|               .map(({ port }) => port) |               .map(({ port }) => port) | ||||||
|               .filter(notEmpty) |               .filter(notEmpty) | ||||||
|           ); |           ); | ||||||
|     this.toDispose.pushAll([ |         } | ||||||
|  |       }), | ||||||
|       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))} | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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'; | ||||||
|   | |||||||
| @@ -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, | ||||||
|   }: { |   }: { | ||||||
|   | |||||||
| @@ -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'; | ||||||
|   | |||||||
| @@ -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, { | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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( | ||||||
|   | |||||||
| @@ -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({ | ||||||
|   | |||||||
| @@ -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', | ||||||
|   | |||||||
| @@ -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( | ||||||
|   | |||||||
| @@ -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> { | ||||||
|   | |||||||
| @@ -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(); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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', | ||||||
|   | |||||||
| @@ -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', | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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()); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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,10 +80,34 @@ export class Debug extends SketchContribution { | |||||||
|             : Debug.Commands.START_DEBUGGING.label |             : Debug.Commands.START_DEBUGGING.label | ||||||
|         }`) |         }`) | ||||||
|     ); |     ); | ||||||
|     const refreshState = async ( |     this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => | ||||||
|  |       this.refreshState(selectedBoard) | ||||||
|  |     ); | ||||||
|  |     this.notificationCenter.onPlatformInstalled(() => this.refreshState()); | ||||||
|  |     this.notificationCenter.onPlatformUninstalled(() => this.refreshState()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   override onReady(): MaybePromise<void> { | ||||||
|  |     this.refreshState(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   override registerCommands(registry: CommandRegistry): void { | ||||||
|  |     registry.registerCommand(Debug.Commands.START_DEBUGGING, { | ||||||
|  |       execute: () => this.startDebug(), | ||||||
|  |       isVisible: (widget) => | ||||||
|  |         ArduinoToolbar.is(widget) && widget.side === 'left', | ||||||
|  |       isEnabled: () => !this.disabledMessage, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   override registerToolbarItems(registry: TabBarToolbarRegistry): void { | ||||||
|  |     registry.registerItem(this.debugToolbarItem); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private async refreshState( | ||||||
|     board: Board | undefined = this.boardsServiceProvider.boardsConfig |     board: Board | undefined = this.boardsServiceProvider.boardsConfig | ||||||
|       .selectedBoard |       .selectedBoard | ||||||
|     ) => { |   ): Promise<void> { | ||||||
|     if (!board) { |     if (!board) { | ||||||
|       this.disabledMessage = nls.localize( |       this.disabledMessage = nls.localize( | ||||||
|         'arduino/common/noBoardSelected', |         'arduino/common/noBoardSelected', | ||||||
| @@ -118,26 +143,6 @@ export class Debug extends SketchContribution { | |||||||
|     } else { |     } else { | ||||||
|       this.disabledMessage = undefined; |       this.disabledMessage = undefined; | ||||||
|     } |     } | ||||||
|     }; |  | ||||||
|     this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => |  | ||||||
|       refreshState(selectedBoard) |  | ||||||
|     ); |  | ||||||
|     this.notificationCenter.onPlatformInstalled(() => refreshState()); |  | ||||||
|     this.notificationCenter.onPlatformUninstalled(() => refreshState()); |  | ||||||
|     refreshState(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   registerCommands(registry: CommandRegistry): void { |  | ||||||
|     registry.registerCommand(Debug.Commands.START_DEBUGGING, { |  | ||||||
|       execute: () => this.startDebug(), |  | ||||||
|       isVisible: (widget) => |  | ||||||
|         ArduinoToolbar.is(widget) && widget.side === 'left', |  | ||||||
|       isEnabled: () => !this.disabledMessage, |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   registerToolbarItems(registry: TabBarToolbarRegistry): void { |  | ||||||
|     registry.registerItem(this.debugToolbarItem); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected async startDebug( |   protected async startDebug( | ||||||
| @@ -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( | ||||||
|   | |||||||
| @@ -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 | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										79
									
								
								arduino-ide-extension/src/browser/contributions/format.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								arduino-ide-extension/src/browser/contributions/format.ts
									
									
									
									
									
										Normal 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, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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', | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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', | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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+,', | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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}`; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
							
								
								
									
										149
									
								
								arduino-ide-extension/src/browser/data/dark.color-theme.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								arduino-ide-extension/src/browser/data/dark.color-theme.json
									
									
									
									
									
										Normal 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" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										149
									
								
								arduino-ide-extension/src/browser/data/default.color-theme.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								arduino-ide-extension/src/browser/data/default.color-theme.json
									
									
									
									
									
										Normal 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" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -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( | ||||||
|  |     (event: React.ChangeEvent<HTMLInputElement>) => { | ||||||
|       setValue(event.target.value); |       setValue(event.target.value); | ||||||
|   }, []); |     }, | ||||||
|  |     [] | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <form |     <form | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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'; | ||||||
|   | |||||||
| @@ -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; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -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); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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'; | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -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 = ( | ||||||
|   | |||||||
| @@ -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); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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); | ||||||
| @@ -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'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { injectable, inject } from 'inversify'; | import { injectable, inject } from '@theia/core/shared/inversify'; | ||||||
| import { | import { | ||||||
|   FrontendApplicationContribution, |   FrontendApplicationContribution, | ||||||
|   FrontendApplication, |   FrontendApplication, | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								arduino-ide-extension/src/browser/hosted-plugin-events.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								arduino-ide-extension/src/browser/hosted-plugin-events.ts
									
									
									
									
									
										Normal 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; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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'; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								arduino-ide-extension/src/browser/ino-selectors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								arduino-ide-extension/src/browser/ino-selectors.ts
									
									
									
									
									
										Normal 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. | ||||||
|  |   })); | ||||||
|  | } | ||||||
| @@ -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); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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'; | ||||||
|   | |||||||
| @@ -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, | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										278
									
								
								arduino-ide-extension/src/browser/monitor-model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								arduino-ide-extension/src/browser/monitor-model.ts
									
									
									
									
									
										Normal 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'; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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"> | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |             {baudrateOptions && baudrateSelectedOption && ( | ||||||
|               <div className="select"> |               <div className="select"> | ||||||
|                 <ArduinoSelect |                 <ArduinoSelect | ||||||
|                   className="select" |                   className="select" | ||||||
|                   maxMenuHeight={this.widgetHeight - 40} |                   maxMenuHeight={this.widgetHeight - 40} | ||||||
|                 options={baudRates} |                   options={baudrateOptions} | ||||||
|                 value={baudRate} |                   value={baudrateSelectedOption} | ||||||
|                   onChange={this.onChangeBaudRate} |                   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 }); | ||||||
|  |     }); | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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: '' }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -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(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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; |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -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; |  | ||||||
| } |  | ||||||
| @@ -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'; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -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); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -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, | ||||||
|   | |||||||
| @@ -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; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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; | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								arduino-ide-extension/src/browser/theia/core/about-dialog.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								arduino-ide-extension/src/browser/theia/core/about-dialog.ts
									
									
									
									
									
										Normal 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. | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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
		Reference in New Issue
	
	Block a user