├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ └── feature-request.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── label-configuration-files │ └── labels.yml └── workflows │ ├── assets │ └── linux.Dockerfile │ ├── build.yml │ ├── check-certificates.yml │ ├── check-containers.yml │ ├── check-i18n-task.yml │ ├── check-javascript.yml │ ├── check-yarn.yml │ ├── compose-full-changelog.yml │ ├── i18n-nightly-push.yml │ ├── i18n-weekly-pull.yml │ ├── push-container-images.yml │ ├── sync-labels.yml │ ├── test-javascript.yml │ └── themes-weekly-pull.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── BUILDING.md ├── LICENSE.txt ├── README.md ├── arduino-ide-extension ├── README.md ├── arduino-icons.json ├── package.json ├── scripts │ ├── compose-changelog.js │ ├── download-cli.js │ ├── download-examples.js │ ├── download-fwuploader.js │ ├── download-ls.js │ ├── downloader.js │ ├── generate-protocol.js │ └── utils.js ├── src │ ├── browser │ │ ├── app-service.ts │ │ ├── arduino-frontend-contribution.tsx │ │ ├── arduino-ide-frontend-module.ts │ │ ├── arduino-preferences.ts │ │ ├── auth │ │ │ ├── authentication-client-service.ts │ │ │ └── cloud-user-commands.ts │ │ ├── boards │ │ │ ├── boards-auto-installer.ts │ │ │ ├── boards-config-component.tsx │ │ │ ├── boards-config-dialog.tsx │ │ │ ├── boards-data-store.ts │ │ │ ├── boards-list-widget.ts │ │ │ ├── boards-service-provider.ts │ │ │ ├── boards-toolbar-item.tsx │ │ │ └── boards-widget-frontend-contribution.ts │ │ ├── components │ │ │ └── ProgressBar.tsx │ │ ├── config │ │ │ └── config-service-client.ts │ │ ├── contributions │ │ │ ├── about.ts │ │ │ ├── account.ts │ │ │ ├── add-file.ts │ │ │ ├── add-zip-library.ts │ │ │ ├── archive-sketch.ts │ │ │ ├── auto-select-programmer.ts │ │ │ ├── board-selection.ts │ │ │ ├── boards-data-menu-updater.ts │ │ │ ├── burn-bootloader.ts │ │ │ ├── check-for-ide-updates.ts │ │ │ ├── check-for-updates.ts │ │ │ ├── close.ts │ │ │ ├── cloud-contribution.ts │ │ │ ├── compiler-errors.ts │ │ │ ├── contribution.ts │ │ │ ├── core-error-handler.ts │ │ │ ├── create-cloud-copy.ts │ │ │ ├── daemon.ts │ │ │ ├── debug.ts │ │ │ ├── delete-sketch.ts │ │ │ ├── edit-contributions.ts │ │ │ ├── examples.ts │ │ │ ├── first-startup-installer.ts │ │ │ ├── format.ts │ │ │ ├── help.ts │ │ │ ├── include-library.ts │ │ │ ├── indexes-update-progress.ts │ │ │ ├── ino-language.ts │ │ │ ├── interface-scale.ts │ │ │ ├── new-cloud-sketch.ts │ │ │ ├── new-sketch.ts │ │ │ ├── open-boards-config.ts │ │ │ ├── open-recent-sketch.ts │ │ │ ├── open-settings.ts │ │ │ ├── open-sketch-external.ts │ │ │ ├── open-sketch-files.ts │ │ │ ├── open-sketch.ts │ │ │ ├── quit-app.ts │ │ │ ├── rename-cloud-sketch.ts │ │ │ ├── save-as-sketch.ts │ │ │ ├── save-sketch.ts │ │ │ ├── selected-board.ts │ │ │ ├── sketch-control.ts │ │ │ ├── sketch-files-tracker.ts │ │ │ ├── sketchbook.ts │ │ │ ├── startup-tasks-executor.ts │ │ │ ├── update-arduino-state.ts │ │ │ ├── update-indexes.ts │ │ │ ├── upload-certificate.ts │ │ │ ├── upload-firmware.ts │ │ │ ├── upload-sketch.ts │ │ │ ├── user-fields.ts │ │ │ ├── validate-sketch.ts │ │ │ └── verify-sketch.ts │ │ ├── create │ │ │ ├── create-api.ts │ │ │ ├── create-features.ts │ │ │ ├── create-fs-provider.ts │ │ │ ├── create-paths.ts │ │ │ ├── create-uri.ts │ │ │ └── typings.ts │ │ ├── data │ │ │ ├── dark.color-theme.json │ │ │ └── default.color-theme.json │ │ ├── dialog-service.ts │ │ ├── dialogs │ │ │ ├── certificate-uploader │ │ │ │ ├── certificate-add-new.tsx │ │ │ │ ├── certificate-list.tsx │ │ │ │ ├── certificate-uploader-component.tsx │ │ │ │ ├── certificate-uploader-dialog.tsx │ │ │ │ ├── select-board-components.tsx │ │ │ │ └── utils.ts │ │ │ ├── cloud-share-sketch-dialog.tsx │ │ │ ├── do-not-ask-again-dialog.ts │ │ │ ├── firmware-uploader │ │ │ │ ├── firmware-uploader-component.tsx │ │ │ │ └── firmware-uploader-dialog.tsx │ │ │ ├── ide-updater │ │ │ │ ├── ide-updater-component.tsx │ │ │ │ └── ide-updater-dialog.tsx │ │ │ ├── settings │ │ │ │ ├── settings-component.tsx │ │ │ │ ├── settings-dialog.tsx │ │ │ │ ├── settings-step-input.tsx │ │ │ │ └── settings.ts │ │ │ ├── user-fields │ │ │ │ ├── user-fields-component.tsx │ │ │ │ └── user-fields-dialog.tsx │ │ │ └── version-welcome-dialog.tsx │ │ ├── hosted │ │ │ ├── hosted-plugin-events.ts │ │ │ └── hosted-plugin-support.ts │ │ ├── icons │ │ │ ├── arduino-cloud-download.svg │ │ │ ├── arduino-cloud-filled-offline.svg │ │ │ ├── arduino-cloud-filled.svg │ │ │ ├── arduino-cloud-offline.svg │ │ │ ├── arduino-cloud-upload.svg │ │ │ ├── arduino-cloud.svg │ │ │ ├── boards-manager.svg │ │ │ ├── buttons.svg │ │ │ ├── debug-dark.svg │ │ │ ├── library-tab-icon.svg │ │ │ ├── link-open-icon.svg │ │ │ ├── loading-dark.svg │ │ │ ├── loading-light.svg │ │ │ ├── mask-buttons.svg │ │ │ ├── monitor-tab-icon.svg │ │ │ ├── monitor.svg │ │ │ ├── plotter.svg │ │ │ ├── sketch-tabs-menu.svg │ │ │ ├── upload.svg │ │ │ └── verify.svg │ │ ├── ide-updater │ │ │ ├── ide-updater-client-impl.ts │ │ │ └── ide-updater-commands.ts │ │ ├── library │ │ │ ├── library-list-widget.ts │ │ │ └── library-widget-frontend-contribution.ts │ │ ├── local-cache │ │ │ └── local-cache-fs-provider.ts │ │ ├── menu │ │ │ └── arduino-menus.ts │ │ ├── monitor-manager-proxy-client-impl.ts │ │ ├── monitor-model.ts │ │ ├── notification-center.ts │ │ ├── response-service-impl.ts │ │ ├── selectors.ts │ │ ├── serial │ │ │ ├── monitor │ │ │ │ ├── monitor-utils.ts │ │ │ │ ├── monitor-view-contribution.tsx │ │ │ │ ├── monitor-widget.tsx │ │ │ │ ├── serial-monitor-send-input.tsx │ │ │ │ └── serial-monitor-send-output.tsx │ │ │ └── plotter │ │ │ │ └── plotter-frontend-contribution.ts │ │ ├── sketches-service-client-impl.ts │ │ ├── style │ │ │ ├── arduino-select.css │ │ │ ├── boards-config-dialog.css │ │ │ ├── browser-menu.css │ │ │ ├── certificate-uploader-dialog.css │ │ │ ├── cloud-sketchbook-tree-icon-filled.svg │ │ │ ├── cloud-sketchbook-tree-icon.svg │ │ │ ├── cloud-sketchbook.css │ │ │ ├── connected-status-icon.svg │ │ │ ├── custom-codicon.css │ │ │ ├── debug.css │ │ │ ├── dialogs.css │ │ │ ├── editor.css │ │ │ ├── firmware-uploader-dialog.css │ │ │ ├── fonts.css │ │ │ ├── fonts │ │ │ │ ├── FontAwesome.svg │ │ │ │ ├── FontAwesome.ttf │ │ │ │ ├── FontAwesome.woff │ │ │ │ ├── OpenSans-Bold-webfont.woff │ │ │ │ └── OpenSans-Regular-webfont.woff │ │ │ ├── ide-logo.png │ │ │ ├── ide-updater-dialog.css │ │ │ ├── index.css │ │ │ ├── list-widget.css │ │ │ ├── main.css │ │ │ ├── monitor.css │ │ │ ├── offline-status-icon.svg │ │ │ ├── progress-bar.css │ │ │ ├── refresh-icon.svg │ │ │ ├── settings-dialog.css │ │ │ ├── settings-step-input.css │ │ │ ├── sketchbook-opts-icon.svg │ │ │ ├── sketchbook-tree-icon-filled.svg │ │ │ ├── sketchbook-tree-icon.svg │ │ │ ├── sketchbook.css │ │ │ ├── sketchbook.svg │ │ │ ├── status-bar.css │ │ │ ├── terminal.css │ │ │ ├── user-fields-dialog.css │ │ │ └── version-welcome-dialog.css │ │ ├── theia │ │ │ ├── core │ │ │ │ ├── about-dialog.ts │ │ │ │ ├── application-shell.ts │ │ │ │ ├── browser-menu-plugin.ts │ │ │ │ ├── common-frontend-contribution.ts │ │ │ │ ├── connection-status-service.ts │ │ │ │ ├── frontend-application.ts │ │ │ │ ├── json-schema-store.ts │ │ │ │ ├── shell-layout-restorer.ts │ │ │ │ ├── sidebar-bottom-menu-widget.tsx │ │ │ │ ├── tab-bar-decorator.ts │ │ │ │ ├── tab-bars.ts │ │ │ │ ├── theming.ts │ │ │ │ ├── widget-manager.ts │ │ │ │ ├── window-contribution.ts │ │ │ │ ├── window-service-ext.ts │ │ │ │ └── window-title-updater.ts │ │ │ ├── debug │ │ │ │ ├── debug-configuration-manager.ts │ │ │ │ ├── debug-configuration-model.ts │ │ │ │ ├── debug-configuration-widget.tsx │ │ │ │ ├── debug-frontend-application-contribution.ts │ │ │ │ ├── debug-session-contribution.ts │ │ │ │ ├── debug-session-manager.ts │ │ │ │ ├── debug-session.ts │ │ │ │ └── debug-widget.ts │ │ │ ├── dialogs │ │ │ │ ├── dialogs.tsx │ │ │ │ └── widgets.ts │ │ │ ├── editor │ │ │ │ ├── editor-contribution.ts │ │ │ │ ├── editor-file.ts │ │ │ │ ├── editor-manager.ts │ │ │ │ ├── editor-navigation-contribution.ts │ │ │ │ └── editor-widget-factory.ts │ │ │ ├── filesystem │ │ │ │ └── file-resource.ts │ │ │ ├── keymaps │ │ │ │ └── keymaps-frontend-contribution.ts │ │ │ ├── markers │ │ │ │ ├── problem-contribution.ts │ │ │ │ └── problem-manager.ts │ │ │ ├── messages │ │ │ │ ├── notification-center-component.tsx │ │ │ │ ├── notification-component.tsx │ │ │ │ ├── notification-toasts-component.tsx │ │ │ │ ├── notifications-manager.ts │ │ │ │ └── notifications-renderer.tsx │ │ │ ├── monaco │ │ │ │ ├── monaco-editor-provider.ts │ │ │ │ ├── monaco-formatting-conflicts.ts │ │ │ │ ├── monaco-menu.ts │ │ │ │ ├── monaco-status-bar-contribution.ts │ │ │ │ ├── monaco-text-model-service.ts │ │ │ │ └── monaco-theming-service.ts │ │ │ ├── navigator │ │ │ │ ├── navigator-contribution.ts │ │ │ │ └── navigator-tab-bar-decorator.ts │ │ │ ├── outline │ │ │ │ └── outline-contribution.ts │ │ │ ├── output │ │ │ │ ├── output-channel.ts │ │ │ │ ├── output-editor-factory.ts │ │ │ │ └── output-toolbar-contribution.ts │ │ │ ├── plugin-ext │ │ │ │ ├── hosted-plugin.ts │ │ │ │ └── output-channel-registry-main.ts │ │ │ ├── preferences │ │ │ │ ├── preference-editor-widget.ts │ │ │ │ ├── preference-tree-generator.ts │ │ │ │ └── preferences-contribution.ts │ │ │ ├── scm │ │ │ │ └── scm-contribution.ts │ │ │ ├── search-in-workspace │ │ │ │ ├── search-in-workspace-factory.ts │ │ │ │ └── search-in-workspace-frontend-contribution.ts │ │ │ ├── terminal │ │ │ │ └── terminal-frontend-contribution.ts │ │ │ ├── test │ │ │ │ └── test-view-contribution.ts │ │ │ ├── typehierarchy │ │ │ │ ├── type-hierarchy-contribution.ts │ │ │ │ └── type-hierarchy-service.ts │ │ │ └── workspace │ │ │ │ ├── workspace-commands.ts │ │ │ │ ├── workspace-delete-handler.ts │ │ │ │ ├── workspace-frontend-contribution.ts │ │ │ │ ├── workspace-input-dialog.ts │ │ │ │ ├── workspace-service.ts │ │ │ │ └── workspace-variable-contribution.ts │ │ ├── toolbar │ │ │ ├── arduino-toolbar-contribution.ts │ │ │ └── arduino-toolbar.tsx │ │ ├── utils │ │ │ ├── constants.ts │ │ │ ├── dom.ts │ │ │ └── window.ts │ │ └── widgets │ │ │ ├── arduino-select.tsx │ │ │ ├── cloud-sketchbook │ │ │ ├── README.md │ │ │ ├── cloud-sketch-cache.ts │ │ │ ├── cloud-sketchbook-commands.ts │ │ │ ├── cloud-sketchbook-composite-widget.tsx │ │ │ ├── cloud-sketchbook-contributions.ts │ │ │ ├── cloud-sketchbook-tree-container.ts │ │ │ ├── cloud-sketchbook-tree-model.ts │ │ │ ├── cloud-sketchbook-tree-widget.tsx │ │ │ ├── cloud-sketchbook-tree.ts │ │ │ ├── cloud-sketchbook-widget.ts │ │ │ └── cloud-status.tsx │ │ │ ├── component-list │ │ │ ├── component-list-item.tsx │ │ │ ├── component-list.tsx │ │ │ ├── filter-renderer.tsx │ │ │ ├── filterable-list-container.tsx │ │ │ ├── list-item-renderer.tsx │ │ │ ├── list-widget-frontend-contribution.ts │ │ │ ├── list-widget.tsx │ │ │ └── search-bar.tsx │ │ │ └── sketchbook │ │ │ ├── create-new.tsx │ │ │ ├── sketchbook-commands.ts │ │ │ ├── sketchbook-composite-widget.tsx │ │ │ ├── sketchbook-tree-container.ts │ │ │ ├── sketchbook-tree-model.ts │ │ │ ├── sketchbook-tree-widget.tsx │ │ │ ├── sketchbook-tree.ts │ │ │ ├── sketchbook-widget-contribution.ts │ │ │ └── sketchbook-widget.tsx │ ├── common │ │ ├── decorators.ts │ │ ├── main-menu-manager.ts │ │ ├── nls.ts │ │ ├── protocol │ │ │ ├── arduino-component.ts │ │ │ ├── arduino-context-mapper.ts │ │ │ ├── arduino-daemon.ts │ │ │ ├── arduino-firmware-uploader.ts │ │ │ ├── authentication-service.ts │ │ │ ├── board-list.ts │ │ │ ├── boards-service.ts │ │ │ ├── config-service.ts │ │ │ ├── core-service.ts │ │ │ ├── examples-service.ts │ │ │ ├── executable-service.ts │ │ │ ├── filesystem-ext.ts │ │ │ ├── formatter.ts │ │ │ ├── ide-updater.ts │ │ │ ├── index.ts │ │ │ ├── installable.ts │ │ │ ├── library-service.ts │ │ │ ├── monitor-service.ts │ │ │ ├── notification-service.ts │ │ │ ├── progressible.ts │ │ │ ├── response-service.ts │ │ │ ├── searchable.ts │ │ │ └── sketches-service.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── electron-browser │ │ ├── electron-app-service.ts │ │ ├── electron-arduino-module.ts │ │ ├── electron-dialog-service.ts │ │ ├── preload.ts │ │ └── theia │ │ │ └── core │ │ │ ├── electron-context-menu-renderer.ts │ │ │ ├── electron-main-menu-factory.ts │ │ │ ├── electron-menu-contribution.ts │ │ │ ├── electron-menu-module.ts │ │ │ ├── electron-window-module.ts │ │ │ └── electron-window-service.ts │ ├── electron-common │ │ ├── electron-arduino.ts │ │ └── startup-task.ts │ ├── electron-main │ │ ├── arduino-electron-main-module.ts │ │ ├── electron-arduino.ts │ │ ├── fix-app-image-icon.ts │ │ ├── ide-updater │ │ │ └── ide-updater-impl.ts │ │ └── theia │ │ │ ├── electron-main-application.ts │ │ │ ├── electron-main-window-service.ts │ │ │ ├── theia-api-main.ts │ │ │ └── theia-electron-window.ts │ ├── node │ │ ├── arduino-core-service-client.ts │ │ ├── arduino-daemon-impl.ts │ │ ├── arduino-firmware-uploader-impl.ts │ │ ├── arduino-ide-backend-module.ts │ │ ├── auth │ │ │ ├── arduino-auth-provider.ts │ │ │ ├── authentication-server.ts │ │ │ ├── authentication-service-impl.ts │ │ │ ├── body.ts │ │ │ ├── keychain.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── board-discovery.ts │ │ ├── boards-service-impl.ts │ │ ├── clang-formatter.ts │ │ ├── cli-config.ts │ │ ├── cli-error-parser.ts │ │ ├── cli-protocol │ │ │ ├── cc │ │ │ │ └── arduino │ │ │ │ │ └── cli │ │ │ │ │ └── commands │ │ │ │ │ └── v1 │ │ │ │ │ ├── board_grpc_pb.js │ │ │ │ │ ├── board_pb.d.ts │ │ │ │ │ ├── board_pb.js │ │ │ │ │ ├── commands_grpc_pb.d.ts │ │ │ │ │ ├── commands_grpc_pb.js │ │ │ │ │ ├── commands_pb.d.ts │ │ │ │ │ ├── commands_pb.js │ │ │ │ │ ├── common_grpc_pb.js │ │ │ │ │ ├── common_pb.d.ts │ │ │ │ │ ├── common_pb.js │ │ │ │ │ ├── compile_grpc_pb.js │ │ │ │ │ ├── compile_pb.d.ts │ │ │ │ │ ├── compile_pb.js │ │ │ │ │ ├── core_grpc_pb.js │ │ │ │ │ ├── core_pb.d.ts │ │ │ │ │ ├── core_pb.js │ │ │ │ │ ├── debug_grpc_pb.js │ │ │ │ │ ├── debug_pb.d.ts │ │ │ │ │ ├── debug_pb.js │ │ │ │ │ ├── lib_grpc_pb.js │ │ │ │ │ ├── lib_pb.d.ts │ │ │ │ │ ├── lib_pb.js │ │ │ │ │ ├── monitor_grpc_pb.js │ │ │ │ │ ├── monitor_pb.d.ts │ │ │ │ │ ├── monitor_pb.js │ │ │ │ │ ├── port_grpc_pb.js │ │ │ │ │ ├── port_pb.d.ts │ │ │ │ │ ├── port_pb.js │ │ │ │ │ ├── settings_grpc_pb.js │ │ │ │ │ ├── settings_pb.d.ts │ │ │ │ │ ├── settings_pb.js │ │ │ │ │ ├── upload_grpc_pb.js │ │ │ │ │ ├── upload_pb.d.ts │ │ │ │ │ └── upload_pb.js │ │ │ └── google │ │ │ │ └── rpc │ │ │ │ ├── status_grpc_pb.js │ │ │ │ ├── status_pb.d.ts │ │ │ │ └── status_pb.js │ │ ├── config-service-impl.ts │ │ ├── core-client-provider.ts │ │ ├── core-service-impl.ts │ │ ├── default-formatter-config.json │ │ ├── examples-service-impl.ts │ │ ├── exec-util.ts │ │ ├── executable-service-impl.ts │ │ ├── grpc-progressible.ts │ │ ├── i18n │ │ │ └── arduino-localization-contribution.ts │ │ ├── is-temp-sketch.ts │ │ ├── library-service-impl.ts │ │ ├── monitor-manager-proxy-impl.ts │ │ ├── monitor-manager.ts │ │ ├── monitor-service-factory.ts │ │ ├── monitor-service.ts │ │ ├── monitor-settings │ │ │ ├── monitor-settings-provider-impl.ts │ │ │ ├── monitor-settings-provider.ts │ │ │ └── monitor-settings-utils.ts │ │ ├── node-filesystem-ext.ts │ │ ├── notification-service-server.ts │ │ ├── plotter │ │ │ └── plotter-backend-contribution.ts │ │ ├── resources.ts │ │ ├── service-error.ts │ │ ├── settings-reader.ts │ │ ├── sketches-service-impl.ts │ │ ├── theia │ │ │ ├── core │ │ │ │ ├── backend-application.ts │ │ │ │ └── websocket-endpoint.ts │ │ │ ├── env-variables │ │ │ │ └── env-variables-server.ts │ │ │ ├── filesystem │ │ │ │ ├── parcel-bindings.ts │ │ │ │ └── parcel-watcher │ │ │ │ │ ├── index.ts │ │ │ │ │ └── parcel-filesystem-service.ts │ │ │ ├── plugin-ext-vscode │ │ │ │ └── scanner-vscode.ts │ │ │ ├── plugin-ext │ │ │ │ ├── hosted-plugin-localization-service.ts │ │ │ │ ├── plugin-deployer.ts │ │ │ │ └── plugin-reader.ts │ │ │ └── workspace │ │ │ │ └── default-workspace-server.ts │ │ ├── utils │ │ │ ├── buffers.ts │ │ │ └── errors.ts │ │ └── web-socket │ │ │ ├── web-socket-provider-impl.ts │ │ │ └── web-socket-provider.ts │ └── test │ │ ├── browser │ │ ├── auto-select-programmer.test.ts │ │ ├── board-service-provider.test.ts │ │ ├── boards-data-store.test.ts │ │ ├── browser-test-bindings.ts │ │ ├── connection-status-service.test.ts │ │ ├── create-api.slow-test.ts │ │ ├── debug.test.ts │ │ ├── dom.test.ts │ │ ├── fixtures │ │ │ └── boards.ts │ │ ├── monitor-utils.test.ts │ │ ├── theming.test.ts │ │ ├── update-arduino-state.test.ts │ │ ├── widgets.test.ts │ │ └── workspace-commands.test.ts │ │ ├── common │ │ ├── arduino-context-mapper.test.ts │ │ ├── board-list.test.ts │ │ ├── boards-service.test.ts │ │ ├── common-test-bindings.ts │ │ ├── config-service.test.ts │ │ ├── fixtures.ts │ │ ├── installable.test.ts │ │ ├── searchable.test.ts │ │ └── sketches-service.test.ts │ │ ├── node │ │ ├── __test_sketchbook__ │ │ │ ├── a_sketch │ │ │ │ └── a_sketch.ino │ │ │ ├── bar++ 2 │ │ │ │ └── bar++ 2.ino │ │ │ ├── bar++ │ │ │ │ ├── bar++.ino │ │ │ │ └── foo++ │ │ │ │ │ └── foo++.ino │ │ │ ├── defaultIno.ino │ │ │ ├── empty │ │ │ │ └── .gitkeep │ │ │ ├── libraries │ │ │ │ └── my_library │ │ │ │ │ └── examples │ │ │ │ │ ├── Ethernet │ │ │ │ │ └── Example_1 │ │ │ │ │ │ └── Example_1.ino │ │ │ │ │ └── WiFi │ │ │ │ │ └── Example_2 │ │ │ │ │ └── Example_2.ino │ │ │ ├── mismatchingName │ │ │ │ └── MismatchingName.ino │ │ │ ├── nested_4 │ │ │ │ └── nested_3 │ │ │ │ │ └── nested_2 │ │ │ │ │ ├── nested_1 │ │ │ │ │ └── nested_1.ino │ │ │ │ │ └── nested_2.ino │ │ │ └── project1 │ │ │ │ ├── CodeA │ │ │ │ ├── version1A │ │ │ │ │ └── version1A.ino │ │ │ │ └── version2A │ │ │ │ │ └── version2A.ino │ │ │ │ └── CodeB │ │ │ │ ├── version1B │ │ │ │ └── version1B.ino │ │ │ │ └── version2B │ │ │ │ └── version2B.pde │ │ ├── arduino-daemon-impl.test.ts │ │ ├── boards-service-impl.slow-test.ts │ │ ├── clang-formatter.test.ts │ │ ├── cli-config.test.ts │ │ ├── config-service-impl.slow-test.ts │ │ ├── core-client-provider.slow-test.ts │ │ ├── core-service-impl.slow-test.ts │ │ ├── core-service-impl.test.ts │ │ ├── exec-util.test.ts │ │ ├── library-service-impl.slow-test.ts │ │ ├── monitor-settings-utils.test.ts │ │ ├── node-test-bindings.ts │ │ ├── settings.reader.test.ts │ │ ├── sketches-service-impl.slow-test.ts │ │ └── sketches-service-impl.test.ts │ │ └── utils.ts └── tsconfig.json ├── docs ├── CONTRIBUTING.md ├── README.md ├── advanced-usage.md ├── assets │ ├── preferences.png │ └── remote.png ├── contributor-guide │ ├── assets │ │ ├── checks-tab.png │ │ ├── checks.png │ │ ├── tester-build-artifacts.png │ │ └── tester-build-link.png │ ├── beta-testing.md │ ├── issues.md │ ├── pull-requests.md │ └── translation.md ├── development.md └── internal │ ├── Ubuntu.md │ └── release-procedure.md ├── electron-app ├── arduino-ide-backend-main.js ├── arduino-ide-electron-main.js ├── package.json ├── resources │ ├── entitlements.mac.plist │ ├── eula.txt │ ├── icon.icns │ ├── icon.ico │ ├── icons │ │ └── 512x512.png │ ├── installerSidebar.bmp │ └── preload.html ├── scripts │ ├── archive.js │ ├── notarize.js │ ├── package.js │ ├── post-package.js │ ├── utils.js │ └── windowsCustomSign.js ├── test │ ├── archive.test.js │ └── test-resources │ │ ├── not-a-zip.dmg │ │ ├── zip with whitespace.zip │ │ ├── zip-with-base-folder.zip │ │ ├── zip-with-symlink.zip │ │ └── zip-without-symlink.zip ├── webpack.base.js ├── webpack.config.js └── webpack.dev.js ├── i18n ├── README.md ├── af.json ├── ar.json ├── az.json ├── be.json ├── bg.json ├── ca_ES.json ├── cs.json ├── da.json ├── de.json ├── el.json ├── en.json ├── es.json ├── eu.json ├── fa.json ├── fil.json ├── fr.json ├── he.json ├── hu.json ├── id.json ├── it.json ├── ja.json ├── ko.json ├── my_MM.json ├── ne.json ├── nl.json ├── no.json ├── pl.json ├── pt.json ├── ro.json ├── ru.json ├── si.json ├── sr.json ├── th.json ├── tr.json ├── uk.json ├── vi.json ├── zh-Hant.json ├── zh.json └── zh_TW.json ├── lerna.json ├── package.json ├── scripts ├── i18n │ ├── transifex-pull.js │ ├── transifex-push.js │ └── transifex.js ├── merge-channel-files.js ├── package.sh ├── sort-dependencies ├── themes │ ├── theme-generator.js │ └── theme-tokens-pull.js └── update-version.js ├── static └── screenshot.png └── yarn.lock /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Source: 2 | # https://github.com/arduino/tooling-project-assets/blob/main/issue-templates/template-choosers/general/config.yml 3 | blank_issues_enabled: false 4 | contact_links: 5 | - name: Learn about using this project 6 | url: https://github.com/arduino/arduino-ide#readme 7 | about: Detailed usage documentation is available here. 8 | - name: Support request 9 | url: https://forum.arduino.cc/ 10 | about: We can help you out on the Arduino Forum! 11 | - name: Issue report guide 12 | url: https://github.com/arduino/arduino-ide/blob/main/docs/contributor-guide/issues.md#issue-report-guide 13 | about: Learn about submitting issue reports to this repository. 14 | - name: Contributor guide 15 | url: https://github.com/arduino/arduino-ide/blob/main/docs/CONTRIBUTING.md#contributor-guide 16 | about: Learn about contributing to this project. 17 | - name: Discuss development work on the project 18 | url: https://groups.google.com/a/arduino.cc/g/developers 19 | about: Arduino Developers Mailing List 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Motivation 2 | 3 | 4 | 5 | ### Change description 6 | 7 | 8 | 9 | ### Other information 10 | 11 | 12 | 13 | ### Reviewer checklist 14 | 15 | - [ ] PR addresses a single concern. 16 | - [ ] The PR has no duplicates (please search among the [Pull Requests](https://github.com/arduino/arduino-ide/pulls) before creating one) 17 | - [ ] PR title and description are properly filled. 18 | - [ ] Docs have been added / updated (for bug fixes / features) 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See: https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#about-the-dependabotyml-file 2 | version: 2 3 | 4 | updates: 5 | # Configure check for outdated GitHub Actions actions in workflows. 6 | # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/dependabot/README.md 7 | # See: https://docs.github.com/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot 8 | - package-ecosystem: github-actions 9 | directory: / # Check the repository's workflows under /.github/workflows/ 10 | assignees: 11 | - per1234 12 | schedule: 13 | interval: daily 14 | labels: 15 | - 'topic: infrastructure' 16 | -------------------------------------------------------------------------------- /.github/label-configuration-files/labels.yml: -------------------------------------------------------------------------------- 1 | # Used by the "Sync Labels" workflow 2 | # See: https://github.com/Financial-Times/github-label-sync#label-config-file 3 | 4 | - name: 'topic: accessibility' 5 | color: '00ffff' 6 | description: Enabling the use of the software by everyone 7 | - name: 'topic: CLI' 8 | color: '00ffff' 9 | description: Related to Arduino CLI 10 | - name: 'topic: cloud' 11 | color: '00ffff' 12 | description: Related to Arduino Cloud and cloud sketches 13 | - name: 'topic: debugger' 14 | color: '00ffff' 15 | description: Related to the integrated debugger 16 | - name: 'topic: language server' 17 | color: '00ffff' 18 | description: Related to the Arduino Language Server 19 | - name: 'topic: serial monitor' 20 | color: '00ffff' 21 | description: Related to the Serial Monitor 22 | - name: 'topic: theia' 23 | color: '00ffff' 24 | description: Related to the Theia IDE framework 25 | - name: 'topic: theme' 26 | color: '00ffff' 27 | description: Related to GUI theming 28 | -------------------------------------------------------------------------------- /.github/workflows/check-containers.yml: -------------------------------------------------------------------------------- 1 | name: Check Containers 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/check-containers.ya?ml" 7 | - "**.Dockerfile" 8 | - "**/Dockerfile" 9 | push: 10 | paths: 11 | - ".github/workflows/check-containers.ya?ml" 12 | - "**.Dockerfile" 13 | - "**/Dockerfile" 14 | repository_dispatch: 15 | schedule: 16 | # Run periodically to catch breakage caused by external changes. 17 | - cron: "0 7 * * MON" 18 | workflow_dispatch: 19 | 20 | jobs: 21 | run: 22 | name: Run (${{ matrix.image.path }}) 23 | runs-on: ubuntu-latest 24 | permissions: {} 25 | services: 26 | registry: 27 | image: registry:2 28 | ports: 29 | - 5000:5000 30 | 31 | env: 32 | IMAGE_NAME: name/app:latest 33 | REGISTRY: localhost:5000 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | image: 39 | - path: .github/workflows/assets/linux.Dockerfile 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | 45 | - name: Build and push to local registry 46 | uses: docker/build-push-action@v6 47 | with: 48 | context: . 49 | file: ${{ matrix.image.path }} 50 | push: true 51 | tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 52 | 53 | - name: Run container 54 | run: | 55 | docker \ 56 | run \ 57 | --rm \ 58 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 59 | -------------------------------------------------------------------------------- /.github/workflows/i18n-nightly-push.yml: -------------------------------------------------------------------------------- 1 | name: i18n-nightly-push 2 | 3 | env: 4 | # See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml 5 | GO_VERSION: '1.21' 6 | 7 | on: 8 | schedule: 9 | # run every day at 1AM 10 | - cron: '0 1 * * *' 11 | 12 | jobs: 13 | push-to-transifex: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Install Node.js 18.17 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: '18.17' 23 | registry-url: 'https://registry.npmjs.org' 24 | cache: 'yarn' 25 | 26 | - name: Install Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version: ${{ env.GO_VERSION }} 30 | 31 | - name: Install Task 32 | uses: arduino/setup-task@v2 33 | with: 34 | repo-token: ${{ secrets.GITHUB_TOKEN }} 35 | version: 3.x 36 | 37 | - name: Install dependencies (Linux only) 38 | if: runner.os == 'Linux' 39 | run: | 40 | sudo apt-get update 41 | sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev 42 | 43 | - name: Install dependencies 44 | run: yarn install --immutable 45 | 46 | - name: Run i18n:push script 47 | run: yarn run i18n:push 48 | env: 49 | TRANSIFEX_ORGANIZATION: ${{ secrets.TRANSIFEX_ORGANIZATION }} 50 | TRANSIFEX_PROJECT: ${{ secrets.TRANSIFEX_PROJECT }} 51 | TRANSIFEX_RESOURCE: ${{ secrets.TRANSIFEX_RESOURCE }} 52 | TRANSIFEX_API_KEY: ${{ secrets.TRANSIFEX_API_KEY }} 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | downloads/ 4 | arduino-ide-extension/src/node/resources 5 | arduino-ide-extension/Examples/ 6 | src-gen/ 7 | gen-webpack.config.js 8 | gen-webpack.node.config.js 9 | .DS_Store 10 | # switching from `electron` to `browser` in dev mode. 11 | .browser_modules 12 | yarn*.log 13 | # For the VS Code extensions used by Theia. 14 | electron-app/plugins 15 | # the tokens folder for the themes 16 | scripts/themes/tokens 17 | # content trace files for electron 18 | electron-app/traces 19 | # any Arduino LS generated log files 20 | inols*.log 21 | # The electron-builder output. 22 | electron-app/dist 23 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib 2 | dist 3 | plugins 4 | src-gen 5 | i18n 6 | gen-webpack* 7 | .browser_modules 8 | arduino-ide-extension/src/node/resources 9 | cli-protocol 10 | *color-theme.json 11 | arduino-icons.json 12 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "printWidth": 80, 6 | "endOfLine": "auto", 7 | "overrides": [ 8 | { 9 | "files": "*.json", 10 | "options": { 11 | "tabWidth": 2 12 | } 13 | }, 14 | { 15 | "files": "*.css", 16 | "options": { 17 | "tabWidth": 4, 18 | "singleQuote": false 19 | } 20 | }, 21 | { 22 | "files": "*.html", 23 | "options": { 24 | "tabWidth": 4 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/lib": false 4 | }, 5 | "search.exclude": { 6 | "arduino-ide-extension/src/test/node/__test_sketchbook__": true 7 | }, 8 | "typescript.tsdk": "node_modules/typescript/lib", 9 | "editor.codeActionsOnSave": { 10 | "source.fixAll.eslint": "explicit" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Rebuild App", 6 | "type": "shell", 7 | "command": "yarn rebuild", 8 | "group": "build", 9 | "options": { 10 | "cwd": "${workspaceFolder}/electron-app" 11 | }, 12 | "presentation": { 13 | "reveal": "always", 14 | "panel": "new", 15 | "clear": false 16 | } 17 | }, 18 | { 19 | "label": "Watch Extension", 20 | "type": "shell", 21 | "command": "yarn --cwd ./arduino-ide-extension watch", 22 | "group": "build", 23 | "presentation": { 24 | "reveal": "always", 25 | "panel": "new", 26 | "clear": false 27 | } 28 | }, 29 | { 30 | "label": "Watch App", 31 | "type": "shell", 32 | "command": "yarn --cwd ./electron-app watch", 33 | "group": "build", 34 | "presentation": { 35 | "reveal": "always", 36 | "panel": "new", 37 | "clear": false 38 | } 39 | }, 40 | { 41 | "label": "Watch All", 42 | "type": "shell", 43 | "dependsOn": ["Watch Extension", "Watch App"] 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Development Guide 2 | 3 | This documentation has been moved [**here**](docs/development.md#development-guide). 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/app-service.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable } from '@theia/core/lib/common/disposable'; 2 | import type { AppInfo } from '../electron-common/electron-arduino'; 3 | import type { StartupTasks } from '../electron-common/startup-task'; 4 | import type { Sketch } from './contributions/contribution'; 5 | 6 | export type { AppInfo }; 7 | 8 | export const AppService = Symbol('AppService'); 9 | export interface AppService { 10 | quit(): void; 11 | info(): Promise; 12 | registerStartupTasksHandler( 13 | handler: (tasks: StartupTasks) => void 14 | ): Disposable; 15 | scheduleDeletion(sketch: Sketch): void; // TODO: find a better place 16 | } 17 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/auth/cloud-user-commands.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '@theia/core/lib/common/command'; 2 | 3 | export const LEARN_MORE_URL = 4 | 'https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-cloud-sketch-sync'; 5 | 6 | export namespace CloudUserCommands { 7 | export const LOGIN = Command.toLocalizedCommand( 8 | { 9 | id: 'arduino-cloud--login', 10 | label: 'Sign in', 11 | }, 12 | 'arduino/cloud/signIn' 13 | ); 14 | 15 | export const LOGOUT = Command.toLocalizedCommand( 16 | { 17 | id: 'arduino-cloud--logout', 18 | label: 'Sign Out', 19 | }, 20 | 'arduino/cloud/signOut' 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { 3 | BoardSearch, 4 | BoardsPackage, 5 | } from '../../common/protocol/boards-service'; 6 | import { URI } from '../contributions/contribution'; 7 | import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution'; 8 | import { BoardsListWidget } from './boards-list-widget'; 9 | 10 | @injectable() 11 | export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution< 12 | BoardsPackage, 13 | BoardSearch 14 | > { 15 | constructor() { 16 | super({ 17 | widgetId: BoardsListWidget.WIDGET_ID, 18 | widgetName: BoardsListWidget.WIDGET_LABEL, 19 | defaultWidgetOptions: { 20 | area: 'left', 21 | rank: 2, 22 | }, 23 | toggleCommandId: `${BoardsListWidget.WIDGET_ID}:toggle`, 24 | toggleKeybinding: 'CtrlCmd+Shift+B', 25 | }); 26 | } 27 | 28 | protected canParse(uri: URI): boolean { 29 | try { 30 | BoardSearch.UriParser.parse(uri); 31 | return true; 32 | } catch { 33 | return false; 34 | } 35 | } 36 | 37 | protected parse(uri: URI): BoardSearch | undefined { 38 | return BoardSearch.UriParser.parse(uri); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/components/ProgressBar.tsx: -------------------------------------------------------------------------------- 1 | import React from '@theia/core/shared/react'; 2 | 3 | export type ProgressBarProps = { 4 | percent?: number; 5 | showPercentage?: boolean; 6 | }; 7 | 8 | export default function ProgressBar({ 9 | percent = 0, 10 | showPercentage = false, 11 | }: ProgressBarProps): React.ReactElement { 12 | const roundedPercent = Math.round(percent); 13 | return ( 14 |
15 |
16 |
20 |
21 | {showPercentage && ( 22 |
23 |
{roundedPercent}%
24 |
25 | )} 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/contributions/core-error-handler.ts: -------------------------------------------------------------------------------- 1 | import { Emitter, Event } from '@theia/core'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | import { CoreError } from '../../common/protocol/core-service'; 4 | 5 | @injectable() 6 | export class CoreErrorHandler { 7 | private readonly errors: CoreError.ErrorLocation[] = []; 8 | private readonly compilerErrorsDidChangeEmitter = new Emitter< 9 | CoreError.ErrorLocation[] 10 | >(); 11 | 12 | tryHandle(error: unknown): void { 13 | if (CoreError.is(error)) { 14 | this.errors.length = 0; 15 | this.errors.push(...error.data); 16 | this.fireCompilerErrorsDidChange(); 17 | } 18 | } 19 | 20 | reset(): void { 21 | this.errors.length = 0; 22 | this.fireCompilerErrorsDidChange(); 23 | } 24 | 25 | get onCompilerErrorsDidChange(): Event { 26 | return this.compilerErrorsDidChangeEmitter.event; 27 | } 28 | 29 | private fireCompilerErrorsDidChange(): void { 30 | this.compilerErrorsDidChangeEmitter.fire(this.errors.slice()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/contributions/daemon.ts: -------------------------------------------------------------------------------- 1 | import { nls } from '@theia/core'; 2 | import { inject, injectable } from '@theia/core/shared/inversify'; 3 | import { ArduinoDaemon } from '../../common/protocol'; 4 | import { Contribution, Command, CommandRegistry } from './contribution'; 5 | 6 | @injectable() 7 | export class Daemon extends Contribution { 8 | @inject(ArduinoDaemon) 9 | private readonly daemon: ArduinoDaemon; 10 | 11 | override registerCommands(registry: CommandRegistry): void { 12 | registry.registerCommand(Daemon.Commands.START_DAEMON, { 13 | execute: () => this.daemon.start(), 14 | }); 15 | registry.registerCommand(Daemon.Commands.STOP_DAEMON, { 16 | execute: () => this.daemon.stop(), 17 | }); 18 | registry.registerCommand(Daemon.Commands.RESTART_DAEMON, { 19 | execute: () => this.daemon.restart(), 20 | }); 21 | } 22 | } 23 | export namespace Daemon { 24 | export namespace Commands { 25 | export const START_DAEMON: Command = { 26 | id: 'arduino-start-daemon', 27 | label: nls.localize('arduino/daemon/start', 'Start Daemon'), 28 | category: 'Arduino', 29 | }; 30 | export const STOP_DAEMON: Command = { 31 | id: 'arduino-stop-daemon', 32 | label: nls.localize('arduino/daemon/stop', 'Stop Daemon'), 33 | category: 'Arduino', 34 | }; 35 | export const RESTART_DAEMON: Command = { 36 | id: 'arduino-restart-daemon', 37 | label: nls.localize('arduino/daemon/restart', 'Restart Daemon'), 38 | category: 'Arduino', 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/contributions/new-sketch.ts: -------------------------------------------------------------------------------- 1 | import { nls } from '@theia/core/lib/common'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | import { ArduinoMenus } from '../menu/arduino-menus'; 4 | import { 5 | SketchContribution, 6 | URI, 7 | Command, 8 | CommandRegistry, 9 | MenuModelRegistry, 10 | KeybindingRegistry, 11 | } from './contribution'; 12 | 13 | @injectable() 14 | export class NewSketch extends SketchContribution { 15 | override registerCommands(registry: CommandRegistry): void { 16 | registry.registerCommand(NewSketch.Commands.NEW_SKETCH, { 17 | execute: () => this.newSketch(), 18 | }); 19 | } 20 | 21 | override registerMenus(registry: MenuModelRegistry): void { 22 | registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { 23 | commandId: NewSketch.Commands.NEW_SKETCH.id, 24 | label: nls.localize('arduino/sketch/new', 'New Sketch'), 25 | order: '0', 26 | }); 27 | } 28 | 29 | override registerKeybindings(registry: KeybindingRegistry): void { 30 | registry.registerKeybinding({ 31 | command: NewSketch.Commands.NEW_SKETCH.id, 32 | keybinding: 'CtrlCmd+N', 33 | }); 34 | } 35 | 36 | async newSketch(): Promise { 37 | try { 38 | const sketch = await this.sketchesService.createNewSketch(); 39 | this.workspaceService.open(new URI(sketch.uri)); 40 | } catch (e) { 41 | await this.messageService.error(e.toString()); 42 | } 43 | } 44 | } 45 | 46 | export namespace NewSketch { 47 | export namespace Commands { 48 | export const NEW_SKETCH: Command = { 49 | id: 'arduino-new-sketch', 50 | }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/contributions/open-boards-config.ts: -------------------------------------------------------------------------------- 1 | import type { Command, CommandRegistry } from '@theia/core/lib/common/command'; 2 | import { inject, injectable } from '@theia/core/shared/inversify'; 3 | import type { EditBoardsConfigActionParams } from '../../common/protocol/board-list'; 4 | import { BoardsConfigDialog } from '../boards/boards-config-dialog'; 5 | import { Contribution } from './contribution'; 6 | 7 | @injectable() 8 | export class OpenBoardsConfig extends Contribution { 9 | @inject(BoardsConfigDialog) 10 | private readonly boardsConfigDialog: BoardsConfigDialog; 11 | 12 | override registerCommands(registry: CommandRegistry): void { 13 | registry.registerCommand(OpenBoardsConfig.Commands.OPEN_DIALOG, { 14 | execute: async (params?: EditBoardsConfigActionParams) => 15 | this.boardsConfigDialog.open(true, params), 16 | }); 17 | } 18 | } 19 | export namespace OpenBoardsConfig { 20 | export namespace Commands { 21 | export const OPEN_DIALOG: Command = { 22 | id: 'arduino-open-boards-dialog', 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/contributions/quit-app.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from '@theia/core/shared/inversify'; 2 | import { isOSX } from '@theia/core/lib/common/os'; 3 | import { 4 | Contribution, 5 | Command, 6 | MenuModelRegistry, 7 | KeybindingRegistry, 8 | CommandRegistry, 9 | } from './contribution'; 10 | import { ArduinoMenus } from '../menu/arduino-menus'; 11 | import { nls } from '@theia/core/lib/common/nls'; 12 | import { AppService } from '../app-service'; 13 | 14 | @injectable() 15 | export class QuitApp extends Contribution { 16 | @inject(AppService) 17 | private readonly appService: AppService; 18 | 19 | override registerCommands(registry: CommandRegistry): void { 20 | if (!isOSX) { 21 | registry.registerCommand(QuitApp.Commands.QUIT_APP, { 22 | execute: () => this.appService.quit(), 23 | }); 24 | } 25 | } 26 | 27 | override registerMenus(registry: MenuModelRegistry): void { 28 | // On macOS we will get the `Quit ${YOUR_APP_NAME}` menu item natively, no need to duplicate it. 29 | if (!isOSX) { 30 | registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, { 31 | commandId: QuitApp.Commands.QUIT_APP.id, 32 | label: nls.localize('vscode/bulkEditService/quit', 'Quit'), 33 | order: '0', 34 | }); 35 | } 36 | } 37 | 38 | override registerKeybindings(registry: KeybindingRegistry): void { 39 | if (!isOSX) { 40 | registry.registerKeybinding({ 41 | command: QuitApp.Commands.QUIT_APP.id, 42 | keybinding: 'CtrlCmd+Q', 43 | }); 44 | } 45 | } 46 | } 47 | 48 | export namespace QuitApp { 49 | export namespace Commands { 50 | export const QUIT_APP: Command = { 51 | id: 'arduino-quit-app', 52 | }; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/contributions/upload-firmware.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from '@theia/core/shared/inversify'; 2 | import { 3 | Command, 4 | MenuModelRegistry, 5 | CommandRegistry, 6 | Contribution, 7 | } from './contribution'; 8 | import { ArduinoMenus } from '../menu/arduino-menus'; 9 | import { UploadFirmwareDialog } from '../dialogs/firmware-uploader/firmware-uploader-dialog'; 10 | import { nls } from '@theia/core/lib/common'; 11 | 12 | @injectable() 13 | export class UploadFirmware extends Contribution { 14 | @inject(UploadFirmwareDialog) 15 | protected readonly dialog: UploadFirmwareDialog; 16 | 17 | protected dialogOpened = false; 18 | 19 | override registerCommands(registry: CommandRegistry): void { 20 | registry.registerCommand(UploadFirmware.Commands.OPEN, { 21 | execute: async () => { 22 | try { 23 | this.dialogOpened = true; 24 | this.menuManager.update(); 25 | await this.dialog.open(); 26 | } finally { 27 | this.dialogOpened = false; 28 | this.menuManager.update(); 29 | } 30 | }, 31 | isEnabled: () => !this.dialogOpened, 32 | }); 33 | } 34 | 35 | override registerMenus(registry: MenuModelRegistry): void { 36 | registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, { 37 | commandId: UploadFirmware.Commands.OPEN.id, 38 | label: UploadFirmware.Commands.OPEN.label, 39 | order: '0', 40 | }); 41 | } 42 | } 43 | 44 | export namespace UploadFirmware { 45 | export namespace Commands { 46 | export const OPEN: Command = { 47 | id: 'arduino-upload-firmware-open', 48 | label: nls.localize('arduino/firmware/updater', 'Firmware Updater'), 49 | category: 'Arduino', 50 | }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/create/create-uri.ts: -------------------------------------------------------------------------------- 1 | import { URI as Uri } from '@theia/core/shared/vscode-uri'; 2 | import URI from '@theia/core/lib/common/uri'; 3 | import { toPosixPath, parentPosix, posix } from './create-paths'; 4 | import { Create } from './typings'; 5 | 6 | export namespace CreateUri { 7 | export const scheme = 'arduino-create'; 8 | export const root = toUri(posix.sep); 9 | 10 | export function toUri( 11 | posixPathOrResource: string | Create.Resource | Create.Sketch 12 | ): URI { 13 | const posixPath = 14 | typeof posixPathOrResource === 'string' 15 | ? posixPathOrResource 16 | : toPosixPath(posixPathOrResource.path); 17 | return new URI(Uri.parse(posixPath).with({ scheme, authority: 'create' })); 18 | } 19 | 20 | export function is(uri: URI): boolean { 21 | return uri.scheme === scheme; 22 | } 23 | 24 | export function equals(left: URI, right: URI): boolean { 25 | return is(left) && is(right) && left.toString() === right.toString(); 26 | } 27 | 28 | export function parent(uri: URI): URI { 29 | if (!is(uri)) { 30 | throw new Error( 31 | `Invalid URI scheme. Expected '${scheme}' got '${uri.scheme}' instead.` 32 | ); 33 | } 34 | if (equals(uri, root)) { 35 | return uri; 36 | } 37 | return toUri(parentPosix(uri.path.toString())); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/dialog-service.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | MessageBoxOptions, 3 | MessageBoxReturnValue, 4 | OpenDialogOptions, 5 | OpenDialogReturnValue, 6 | SaveDialogOptions, 7 | SaveDialogReturnValue, 8 | } from '../electron-common/electron-arduino'; 9 | 10 | export const DialogService = Symbol('DialogService'); 11 | export interface DialogService { 12 | showMessageBox(options: MessageBoxOptions): Promise; 13 | showOpenDialog(options: OpenDialogOptions): Promise; 14 | showSaveDialog(options: SaveDialogOptions): Promise; 15 | } 16 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-add-new.tsx: -------------------------------------------------------------------------------- 1 | import { nls } from '@theia/core/lib/common'; 2 | import React from '@theia/core/shared/react'; 3 | 4 | export const CertificateAddComponent = ({ 5 | addCertificate, 6 | }: { 7 | addCertificate: (cert: string) => void; 8 | }): React.ReactElement => { 9 | const [value, setValue] = React.useState(''); 10 | 11 | const handleChange = React.useCallback( 12 | (event: React.ChangeEvent) => { 13 | setValue(event.target.value); 14 | }, 15 | [] 16 | ); 17 | 18 | return ( 19 |
{ 22 | event.preventDefault(); 23 | event.stopPropagation(); 24 | addCertificate(value); 25 | setValue(''); 26 | }} 27 | > 28 | 47 |
48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-list.tsx: -------------------------------------------------------------------------------- 1 | import React from '@theia/core/shared/react'; 2 | 3 | export const CertificateListComponent = ({ 4 | certificates, 5 | selectedCerts, 6 | setSelectedCerts, 7 | openContextMenu, 8 | }: { 9 | certificates: string[]; 10 | selectedCerts: string[]; 11 | setSelectedCerts: React.Dispatch>; 12 | openContextMenu: (x: number, y: number, cert: string) => void; 13 | }): React.ReactElement => { 14 | const handleOnChange = (event: any) => { 15 | const target = event.target; 16 | 17 | const newSelectedCerts = selectedCerts.filter( 18 | (cert) => cert !== target.name 19 | ); 20 | 21 | if (target.checked) { 22 | newSelectedCerts.push(target.name); 23 | } 24 | 25 | setSelectedCerts(newSelectedCerts); 26 | }; 27 | 28 | const handleContextMenu = (event: React.MouseEvent, cert: string) => { 29 | openContextMenu(event.clientX, event.clientY, cert); 30 | }; 31 | 32 | return ( 33 |
34 | {certificates.map((certificate, i) => ( 35 | 48 | ))} 49 |
50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/dialogs/certificate-uploader/utils.ts: -------------------------------------------------------------------------------- 1 | export const arduinoCert = 'arduino.cc:443'; 2 | 3 | export function sanifyCertString(cert: string): string { 4 | const regex = /^(?:.*:\/\/)*(\S+\.+[^:]*):*(\d*)*$/gm; 5 | 6 | const m = regex.exec(cert); 7 | 8 | if (!m) { 9 | return ''; 10 | } 11 | 12 | const domain = m[1] || ''; 13 | const port = m[2] || '443'; 14 | 15 | if (domain.length === 0 || port.length === 0) { 16 | return ''; 17 | } 18 | 19 | return `${domain}:${port}`; 20 | } 21 | 22 | export function certificateList(certificates: string): string[] { 23 | let certs = certificates 24 | .split(',') 25 | .map((cert) => sanifyCertString(cert.trim())) 26 | .filter((cert) => { 27 | // remove empty certificates 28 | if (!cert || cert.length === 0) { 29 | return false; 30 | } 31 | return true; 32 | }); 33 | 34 | // add arduino certificate at the top of the list 35 | certs = certs.filter((cert) => cert !== arduinoCert); 36 | certs.unshift(arduinoCert); 37 | return certs; 38 | } 39 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/hosted/hosted-plugin-support.ts: -------------------------------------------------------------------------------- 1 | import type { Event } from '@theia/core/lib/common/event'; 2 | 3 | /* 4 | This implementation hides the default HostedPluginSupport implementation from Theia to be able to test it. 5 | Otherwise, the default implementation fails at require time due to the `import.meta` in the Theia plugin worker code. 6 | https://github.com/eclipse-theia/theia/blob/964f69ca3b3a5fb87ffa0177fb300b74ba0ca39f/packages/plugin-ext/src/hosted/browser/plugin-worker.ts#L30-L32 7 | */ 8 | 9 | export const HostedPluginSupport = Symbol('HostedPluginSupport'); 10 | export interface HostedPluginSupport { 11 | readonly didStart: Promise; 12 | readonly onDidLoad: Event; 13 | readonly onDidCloseConnection: Event; 14 | } 15 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/arduino-cloud-filled-offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/arduino-cloud-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/arduino-cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/boards-manager.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/library-tab-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/link-open-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/monitor-tab-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/monitor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/plotter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/sketch-tabs-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/icons/verify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts: -------------------------------------------------------------------------------- 1 | import { nls } from '@theia/core/lib/common'; 2 | import { MenuModelRegistry } from '@theia/core/lib/common/menu'; 3 | import { injectable } from '@theia/core/shared/inversify'; 4 | import { LibraryPackage, LibrarySearch } from '../../common/protocol'; 5 | import { URI } from '../contributions/contribution'; 6 | import { ArduinoMenus } from '../menu/arduino-menus'; 7 | import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution'; 8 | import { LibraryListWidget } from './library-list-widget'; 9 | 10 | @injectable() 11 | export class LibraryListWidgetFrontendContribution extends ListWidgetFrontendContribution< 12 | LibraryPackage, 13 | LibrarySearch 14 | > { 15 | constructor() { 16 | super({ 17 | widgetId: LibraryListWidget.WIDGET_ID, 18 | widgetName: LibraryListWidget.WIDGET_LABEL, 19 | defaultWidgetOptions: { 20 | area: 'left', 21 | rank: 3, 22 | }, 23 | toggleCommandId: `${LibraryListWidget.WIDGET_ID}:toggle`, 24 | toggleKeybinding: 'CtrlCmd+Shift+I', 25 | }); 26 | } 27 | 28 | override registerMenus(menus: MenuModelRegistry): void { 29 | if (this.toggleCommand) { 30 | menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { 31 | commandId: this.toggleCommand.id, 32 | label: nls.localize( 33 | 'arduino/library/manageLibraries', 34 | 'Manage Libraries...' 35 | ), 36 | order: '3', 37 | }); 38 | } 39 | } 40 | 41 | protected canParse(uri: URI): boolean { 42 | try { 43 | LibrarySearch.UriParser.parse(uri); 44 | return true; 45 | } catch { 46 | return false; 47 | } 48 | } 49 | 50 | protected parse(uri: URI): LibrarySearch | undefined { 51 | return LibrarySearch.UriParser.parse(uri); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/response-service-impl.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from '@theia/core/shared/inversify'; 2 | import { Emitter } from '@theia/core/lib/common/event'; 3 | import { 4 | OutputChannelManager, 5 | OutputChannelSeverity, 6 | } from '@theia/output/lib/browser/output-channel'; 7 | import { 8 | OutputMessage, 9 | ProgressMessage, 10 | ResponseServiceClient, 11 | } from '../common/protocol/response-service'; 12 | 13 | @injectable() 14 | export class ResponseServiceImpl implements ResponseServiceClient { 15 | @inject(OutputChannelManager) 16 | private readonly outputChannelManager: OutputChannelManager; 17 | 18 | private readonly progressDidChangeEmitter = new Emitter(); 19 | 20 | readonly onProgressDidChange = this.progressDidChangeEmitter.event; 21 | 22 | clearOutput(): void { 23 | this.outputChannelManager.getChannel('Arduino').clear(); 24 | } 25 | 26 | appendToOutput(message: OutputMessage): void { 27 | const { chunk, severity } = message; 28 | const channel = this.outputChannelManager.getChannel('Arduino'); 29 | channel.show({ preserveFocus: true }); 30 | channel.append(chunk, mapSeverity(severity)); 31 | } 32 | 33 | reportProgress(progress: ProgressMessage): void { 34 | this.progressDidChangeEmitter.fire(progress); 35 | } 36 | } 37 | 38 | function mapSeverity(severity?: OutputMessage.Severity): OutputChannelSeverity { 39 | if (severity === OutputMessage.Severity.Error) { 40 | return OutputChannelSeverity.Error; 41 | } else if (severity === OutputMessage.Severity.Warning) { 42 | return OutputChannelSeverity.Warning; 43 | } 44 | return OutputChannelSeverity.Info; 45 | } 46 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/selectors.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from '@theia/monaco-editor-core'; 2 | import { OutputUri } from '@theia/output/lib/common/output-uri'; 3 | /** 4 | * Exclusive "ino" document selector for monaco. 5 | */ 6 | export const InoSelector = selectorOf('ino', 'c', 'cpp', 'h', 'hpp', 'pde'); 7 | function selectorOf( 8 | ...languageId: string[] 9 | ): monaco.languages.LanguageSelector { 10 | return languageId.map((language) => ({ 11 | language, 12 | exclusive: true, // <-- this should make sure the custom formatter has higher precedence over the LS formatter. 13 | })); 14 | } 15 | 16 | /** 17 | * Selector for the `monaco` resource in the Arduino _Output_ channel. 18 | */ 19 | export const ArduinoOutputSelector: monaco.languages.LanguageSelector = { 20 | scheme: OutputUri.SCHEME, 21 | pattern: '**/Arduino', 22 | }; 23 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/browser-menu.css: -------------------------------------------------------------------------------- 1 | #theia-top-panel { 2 | min-height: 64px; 3 | flex-direction: column; 4 | } 5 | 6 | #theia-top-panel .p-TabBar-toolbar { 7 | justify-content: flex-end; 8 | margin: 0; 9 | padding-left: 10px; 10 | width: 100%; 11 | } 12 | 13 | .p-MenuBar-item.p-mod-active { 14 | color: var(--theia-menubar-selectionForeground); 15 | } 16 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/cloud-sketchbook-tree-icon-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/cloud-sketchbook-tree-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/connected-status-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/custom-codicon.css: -------------------------------------------------------------------------------- 1 | .codicon-debug-alt:before { 2 | font-family: "FontAwesome" !important; 3 | content: "\e905" !important; 4 | } 5 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/debug.css: -------------------------------------------------------------------------------- 1 | /* Naive way of hiding the debug widget when the debug functionality is disabled https://github.com/arduino/arduino-ide/issues/14 */ 2 | .theia-debug-container .debug-toolbar.hidden, 3 | .theia-debug-container .theia-session-container.hidden { 4 | visibility: hidden; 5 | } 6 | 7 | .theia-debug-container .status-message { 8 | font-family: "Open Sans"; 9 | font-style: normal; 10 | font-size: 12px; 11 | 12 | padding: 10px; 13 | } 14 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/editor.css: -------------------------------------------------------------------------------- 1 | /* Show the dirty indicator on unclosable widgets. On hover, it should still show the dot instead of the X. */ 2 | /* https://github.com/arduino/arduino-pro-ide/issues/380 */ 3 | .p-TabBar.theia-app-centers 4 | .p-TabBar-tab.p-mod-closable.a-mod-uncloseable.theia-mod-dirty 5 | > .p-TabBar-tabCloseIcon:before { 6 | content: "\ea71"; 7 | } 8 | 9 | .monaco-list-row.show-file-icons.focused { 10 | background-color: var(--theia-quickInputList-focusBackground); 11 | } 12 | 13 | .monaco-editor .view-overlays .compiler-error { 14 | background-color: var(--theia-inputValidation-errorBackground); 15 | opacity: 0.4 !important; 16 | } 17 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/firmware-uploader-dialog.css: -------------------------------------------------------------------------------- 1 | #firmware-uploader-dialog-container > .dialogBlock { 2 | width: 600px; 3 | } 4 | 5 | .firmware-uploader-dialog .theia-select { 6 | border: none !important; 7 | } 8 | .firmware-uploader-dialog .arduino-select__control { 9 | height: 31px; 10 | background: var(--theia-input-background) !important; 11 | } 12 | 13 | .firmware-uploader-dialog .dialogRow > button { 14 | margin-right: 3px; 15 | } 16 | 17 | .firmware-uploader-dialog #firmware-select { 18 | flex: unset; 19 | } 20 | 21 | .firmware-uploader-dialog .success { 22 | color: #1da086; 23 | } 24 | 25 | .firmware-uploader-dialog .warn { 26 | color: #c11f09; 27 | } 28 | 29 | .firmware-uploader-dialog .status-icon { 30 | margin-right: 10px; 31 | } 32 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/browser/style/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/fonts/FontAwesome.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/browser/style/fonts/FontAwesome.woff -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/browser/style/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/browser/style/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/ide-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/browser/style/ide-logo.png -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/offline-status-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/progress-bar.css: -------------------------------------------------------------------------------- 1 | .progress-bar { 2 | margin-top: 20px; 3 | } 4 | 5 | .progress-bar--outer { 6 | background: var(--theia-editorWidget-background); 7 | border-radius: 11px; 8 | height: 6px; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | .progress-bar--inner { 14 | transition: width 1s; 15 | height: 100%; 16 | background: var(--theia-progressBar-background); 17 | border-radius: 11px; 18 | } 19 | 20 | .progress-bar--percentage { 21 | align-items: flex-end; 22 | display: flex; 23 | height: 40px; 24 | justify-content: center; 25 | margin-top: 10px; 26 | width: 100%; 27 | } 28 | 29 | .progress-bar--percentage-text { 30 | font-size: 14px; 31 | line-height: 24px; 32 | } 33 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/refresh-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/settings-step-input.css: -------------------------------------------------------------------------------- 1 | .settings-step-input-container { 2 | position: relative; 3 | } 4 | 5 | .settings-step-input-element::-webkit-inner-spin-button, 6 | .settings-step-input-element::-webkit-outer-spin-button { 7 | -webkit-appearance: none; 8 | margin: 0; 9 | } 10 | 11 | .settings-step-input-buttons-container { 12 | display: none; 13 | flex-direction: column; 14 | position: absolute; 15 | right: 0px; 16 | top: 50%; 17 | transform: translate(0px, -50%); 18 | height: calc(100% - 4px); 19 | width: 14px; 20 | padding: 2px; 21 | background: var(--theia-input-background); 22 | } 23 | 24 | .settings-step-input-buttons-container-perc { 25 | right: 14px; 26 | } 27 | 28 | .settings-step-input-container:hover > .settings-step-input-buttons-container { 29 | display: flex; 30 | } 31 | 32 | .settings-step-input-up-button { 33 | transform: rotate(-180deg); 34 | } 35 | 36 | .settings-step-input-button { 37 | border: none; 38 | border-radius: 0; 39 | height: 50%; 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | user-select: none; 44 | cursor: pointer; 45 | line-height: 12px; 46 | } 47 | 48 | .settings-step-input-button:hover { 49 | background: rgba(128, 128, 128, 0.8); 50 | } 51 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/sketchbook-opts-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/sketchbook-tree-icon-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/sketchbook-tree-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/sketchbook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/status-bar.css: -------------------------------------------------------------------------------- 1 | #theia-statusBar .area .element.arduino-selected-port { 2 | margin-left: 0px; 3 | } 4 | 5 | #theia-statusBar .area .element.arduino-selected-board > *:last-child { 6 | margin-right: 0px; 7 | } 8 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/terminal.css: -------------------------------------------------------------------------------- 1 | .terminal-container .xterm .xterm-helper-textarea { 2 | opacity: 0 !important; /* fix secondary cursor-like issue. See https://github.com/eclipse-theia/theia/issues/8158 */ 3 | } 4 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/user-fields-dialog.css: -------------------------------------------------------------------------------- 1 | .user-fields-container { 2 | max-height: 332px; 3 | overflow: auto; 4 | padding: 2px; 5 | } 6 | 7 | .user-fields-list { 8 | margin: 16px 0; 9 | } 10 | 11 | .user-fields-dialog-content { 12 | width: 408px; 13 | max-height: 491px; 14 | } 15 | 16 | .user-fields-dialog-content .field-label { 17 | color: var(--theia-editorWidget-foreground); 18 | font-size: 14px; 19 | font-style: normal; 20 | font-weight: 400; 21 | line-height: 21px; 22 | letter-spacing: 0.01em; 23 | text-align: left; 24 | } 25 | 26 | .user-fields-dialog-content .theia-input { 27 | flex-grow: 1; 28 | } 29 | 30 | .user-fields-dialog-content .button-container { 31 | justify-content: flex-end; 32 | } 33 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/style/version-welcome-dialog.css: -------------------------------------------------------------------------------- 1 | #version-welcome-dialog-container > .dialogBlock { 2 | width: 546px; 3 | 4 | .bold { 5 | font-weight: bold; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/core/about-dialog.ts: -------------------------------------------------------------------------------- 1 | import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog'; 2 | 3 | export class AboutDialog extends TheiaAboutDialog { 4 | protected override init(): void { 5 | // NOOP 6 | // IDE2 has a custom about dialog, so it does not make sense to collect Theia extensions at startup time. 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { FrontendApplication } from '@theia/core/lib/browser'; 3 | import { BrowserMenuBarContribution } from '@theia/core/lib/browser/menu/browser-menu-plugin'; 4 | 5 | @injectable() 6 | export class ArduinoMenuContribution extends BrowserMenuBarContribution { 7 | override onStart(app: FrontendApplication): void { 8 | const menu = this.factory.createMenuBar(); 9 | app.shell.addWidget(menu, { area: 'top' }); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/core/frontend-application.ts: -------------------------------------------------------------------------------- 1 | import { injectable, inject } from '@theia/core/shared/inversify'; 2 | import { CommandService } from '@theia/core/lib/common/command'; 3 | import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; 4 | import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application'; 5 | import { SketchesService } from '../../../common/protocol'; 6 | import { OpenSketchFiles } from '../../contributions/open-sketch-files'; 7 | 8 | @injectable() 9 | export class FrontendApplication extends TheiaFrontendApplication { 10 | @inject(WorkspaceService) 11 | private readonly workspaceService: WorkspaceService; 12 | 13 | @inject(CommandService) 14 | private readonly commandService: CommandService; 15 | 16 | @inject(SketchesService) 17 | private readonly sketchesService: SketchesService; 18 | 19 | private layoutWasRestored = false; 20 | 21 | protected override async initializeLayout(): Promise { 22 | await super.initializeLayout(); 23 | this.workspaceService.roots.then(async (roots) => { 24 | for (const root of roots) { 25 | await this.commandService.executeCommand( 26 | OpenSketchFiles.Commands.OPEN_SKETCH_FILES.id, 27 | root.resource, 28 | !this.layoutWasRestored 29 | ); 30 | this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu 31 | } 32 | }); 33 | } 34 | 35 | protected override async restoreLayout(): Promise { 36 | this.layoutWasRestored = await super.restoreLayout(); 37 | return this.layoutWasRestored; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/core/json-schema-store.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { DefaultJsonSchemaContribution as TheiaDefaultJsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store'; 3 | 4 | @injectable() 5 | export class DefaultJsonSchemaContribution extends TheiaDefaultJsonSchemaContribution { 6 | override async registerSchemas(): Promise { 7 | // NOOP 8 | // Do not fetch the https://www.schemastore.org/api/json/catalog.json on every single browser window load. 9 | // If the schemas are required in the future, we should fetch the `catalog.json` on build time and load it. 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | inject, 3 | injectable, 4 | postConstruct, 5 | } from '@theia/core/shared/inversify'; 6 | import URI from '@theia/core/lib/common/uri'; 7 | import { Title, Widget } from '@theia/core/shared/@phosphor/widgets'; 8 | import { EditorWidget } from '@theia/editor/lib/browser'; 9 | import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration'; 10 | import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator'; 11 | import { ConfigServiceClient } from '../../config/config-service-client'; 12 | import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; 13 | 14 | @injectable() 15 | export class TabBarDecoratorService extends TheiaTabBarDecoratorService { 16 | @inject(ConfigServiceClient) 17 | private readonly configService: ConfigServiceClient; 18 | @inject(FrontendApplicationStateService) 19 | private readonly appStateService: FrontendApplicationStateService; 20 | 21 | private dataDirUri: URI | undefined; 22 | 23 | @postConstruct() 24 | protected init(): void { 25 | const fireDidChange = () => 26 | this.appStateService 27 | .reachedState('ready') 28 | .then(() => this.fireDidChangeDecorations()); 29 | this.dataDirUri = this.configService.tryGetDataDirUri(); 30 | this.configService.onDidChangeDataDirUri((dataDirUri) => { 31 | this.dataDirUri = dataDirUri; 32 | fireDidChange(); 33 | }); 34 | if (this.dataDirUri) { 35 | fireDidChange(); 36 | } 37 | } 38 | 39 | override getDecorations(title: Title): WidgetDecoration.Data[] { 40 | if (title.owner instanceof EditorWidget) { 41 | const editor = title.owner.editor; 42 | if (this.dataDirUri && this.dataDirUri.isEqualOrParent(editor.uri)) { 43 | return []; 44 | } 45 | } 46 | return super.getDecorations(title); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/core/tab-bars.ts: -------------------------------------------------------------------------------- 1 | import type { TabBar } from '@theia/core/shared/@phosphor/widgets'; 2 | import { Saveable } from '@theia/core/lib/browser/saveable'; 3 | import { 4 | TabBarRenderer as TheiaTabBarRenderer, 5 | ToolbarAwareTabBar as TheiaToolbarAwareTabBar, 6 | } from '@theia/core/lib/browser/shell/tab-bars'; 7 | import debounce from 'lodash.debounce'; 8 | 9 | export class TabBarRenderer extends TheiaTabBarRenderer { 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | override createTabClass(data: TabBar.IRenderData): string { 12 | let className = super.createTabClass(data); 13 | if (!data.title.closable && Saveable.isDirty(data.title.owner)) { 14 | className += ' p-mod-closable'; 15 | } 16 | return className; 17 | } 18 | 19 | protected override handleContextMenuEvent = (): void => { 20 | // NOOP 21 | // Context menus are empty, so they have been removed 22 | }; 23 | } 24 | 25 | export class ToolbarAwareTabBar extends TheiaToolbarAwareTabBar { 26 | protected override async updateBreadcrumbs(): Promise { 27 | // NOOP 28 | // IDE2 does not use breadcrumbs. 29 | } 30 | 31 | private readonly doUpdateToolbar = debounce(() => super.updateToolbar(), 500); 32 | protected override updateToolbar(): void { 33 | // Unlike Theia, IDE2 debounces the toolbar updates with 500ms 34 | this.doUpdateToolbar(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/core/window-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { WindowContribution as TheiaWindowContribution } from '@theia/core/lib/browser/window-contribution'; 3 | 4 | @injectable() 5 | export class WindowContribution extends TheiaWindowContribution { 6 | override registerCommands(): void { 7 | // NOOP 8 | } 9 | override registerKeybindings(): void { 10 | // NOO 11 | } 12 | override registerMenus(): void { 13 | // NOOP; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/core/window-service-ext.ts: -------------------------------------------------------------------------------- 1 | import type { StartupTasks } from '../../../electron-common/startup-task'; 2 | 3 | export const WindowServiceExt = Symbol('WindowServiceExt'); 4 | export interface WindowServiceExt { 5 | /** 6 | * Returns with a promise that resolves to `true` if the current window is the first window. 7 | */ 8 | isFirstWindow(): Promise; 9 | reload(tasks?: StartupTasks): void; 10 | close(): void; 11 | } 12 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { MenuModelRegistry } from '@theia/core/lib/common/menu'; 3 | import { 4 | DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution, 5 | DebugMenus, 6 | } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; 7 | import { unregisterSubmenu } from '../../menu/arduino-menus'; 8 | 9 | @injectable() 10 | export class DebugFrontendApplicationContribution extends TheiaDebugFrontendApplicationContribution { 11 | constructor() { 12 | super(); 13 | this.options.defaultWidgetOptions.rank = 4; 14 | } 15 | 16 | override registerMenus(registry: MenuModelRegistry): void { 17 | super.registerMenus(registry); 18 | unregisterSubmenu(DebugMenus.DEBUG, registry); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from '@theia/core/shared/inversify'; 2 | import { DebugSession } from '@theia/debug/lib/browser/debug-session'; 3 | import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; 4 | import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options'; 5 | import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; 6 | import deepEqual from 'fast-deep-equal'; 7 | 8 | @injectable() 9 | export class DebugSessionManager extends TheiaDebugSessionManager { 10 | @inject(WorkspaceService) 11 | private readonly workspaceService: WorkspaceService; 12 | 13 | protected override doStart( 14 | sessionId: string, 15 | options: DebugConfigurationSessionOptions 16 | ): Promise { 17 | this.syncCurrentOptions(options); 18 | return super.doStart(sessionId, options); 19 | } 20 | 21 | /** 22 | * If the debug config manager knows about the currently started options, and it's not the currently selected one, select it. 23 | */ 24 | private syncCurrentOptions(options: DebugConfigurationSessionOptions): void { 25 | const knownConfigOptions = this.debugConfigurationManager.find( 26 | options.configuration, 27 | options.workspaceFolderUri ?? 28 | this.workspaceService 29 | .tryGetRoots() 30 | .map((stat) => stat.resource.toString())[0] 31 | ); 32 | if ( 33 | knownConfigOptions && 34 | !deepEqual(knownConfigOptions, this.debugConfigurationManager.current) 35 | ) { 36 | this.debugConfigurationManager.current = knownConfigOptions; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/debug/debug-session.ts: -------------------------------------------------------------------------------- 1 | import { DebugSession as TheiaDebugSession } from '@theia/debug/lib/browser/debug-session'; 2 | 3 | export class DebugSession extends TheiaDebugSession { 4 | // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars 5 | protected override handleDisconnectError(err: unknown): void { 6 | // NOOP 7 | } 8 | // eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars 9 | protected override handleTerminateError(err: unknown): void { 10 | // NOOP 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/dialogs/dialogs.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AbstractDialog as TheiaAbstractDialog, 3 | DialogProps, 4 | } from '@theia/core/lib/browser/dialogs'; 5 | import { ReactDialog as TheiaReactDialog } from '@theia/core/lib/browser/dialogs/react-dialog'; 6 | import { codiconArray } from '@theia/core/lib/browser/widgets/widget'; 7 | import type { Message } from '@theia/core/shared/@phosphor/messaging'; 8 | import { inject, injectable } from '@theia/core/shared/inversify'; 9 | 10 | @injectable() 11 | export abstract class AbstractDialog extends TheiaAbstractDialog { 12 | constructor( 13 | @inject(DialogProps) protected override readonly props: DialogProps 14 | ) { 15 | super(props); 16 | this.closeCrossNode.classList.remove(...codiconArray('close')); 17 | this.closeCrossNode.classList.add('fa', 'fa-close'); 18 | } 19 | } 20 | 21 | @injectable() 22 | export abstract class ReactDialog extends TheiaReactDialog { 23 | private _isOnCloseRequestInProgress = false; 24 | 25 | override dispose(): void { 26 | // There is a bug in Theia, and the React component's `componentWillUnmount` will not be called, as the Theia widget is already disposed when closing and reopening a dialog. 27 | // Widget lifecycle issue in Theia: https://github.com/eclipse-theia/theia/issues/12093 28 | // Bogus react widget lifecycle management PR: https://github.com/eclipse-theia/theia/pull/11687 29 | // Do not call super. Do not let the Phosphor widget to be disposed on dialog close. 30 | if (this._isOnCloseRequestInProgress) { 31 | // Do not let the widget dispose on close. 32 | return; 33 | } 34 | super.dispose(); 35 | } 36 | 37 | protected override onCloseRequest(message: Message): void { 38 | this._isOnCloseRequestInProgress = true; 39 | try { 40 | super.onCloseRequest(message); 41 | } finally { 42 | this._isOnCloseRequestInProgress = false; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/editor/editor-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { EditorContribution as TheiaEditorContribution } from '@theia/editor/lib/browser/editor-contribution'; 3 | 4 | @injectable() 5 | export class EditorContribution extends TheiaEditorContribution { 6 | protected override updateLanguageStatus( 7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 8 | ..._: Parameters 9 | ): void { 10 | // NOOP 11 | } 12 | 13 | protected override updateEncodingStatus( 14 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 15 | ..._: Parameters 16 | ): void { 17 | // https://github.com/arduino/arduino-ide/issues/1393 18 | // NOOP 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/editor/editor-file.ts: -------------------------------------------------------------------------------- 1 | import { MenuModelRegistry } from '@theia/core'; 2 | import { CommonCommands } from '@theia/core/lib/browser'; 3 | import { injectable } from '@theia/core/shared/inversify'; 4 | import { EditorMenuContribution as TheiaEditorMenuContribution } from '@theia/editor/lib/browser/editor-menu'; 5 | 6 | @injectable() 7 | export class EditorMenuContribution extends TheiaEditorMenuContribution { 8 | override registerMenus(registry: MenuModelRegistry): void { 9 | super.registerMenus(registry); 10 | registry.unregisterMenuAction(CommonCommands.CLOSE_MAIN_TAB.id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/editor/editor-manager.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser/editor-manager'; 3 | 4 | @injectable() 5 | export class EditorManager extends TheiaEditorManager { 6 | protected override getOrCreateCounterForUri(): number { 7 | return 0; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { EditorNavigationContribution as TheiaEditorNavigationContribution } from '@theia/editor/lib/browser/editor-navigation-contribution'; 3 | 4 | @injectable() 5 | export class EditorNavigationContribution extends TheiaEditorNavigationContribution { 6 | override async onStart(): Promise { 7 | // No await. 8 | // Restore the navigation history asynchronously. 9 | super.onStart(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from '@theia/core/shared/inversify'; 2 | import URI from '@theia/core/lib/common/uri'; 3 | import { EditorWidget } from '@theia/editor/lib/browser'; 4 | import type { NavigatableWidgetOptions } from '@theia/core/lib/browser'; 5 | import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory'; 6 | import { 7 | CurrentSketch, 8 | SketchesServiceClientImpl, 9 | } from '../../sketches-service-client-impl'; 10 | import { SketchesService, Sketch } from '../../../common/protocol'; 11 | import { nls } from '@theia/core/lib/common'; 12 | 13 | @injectable() 14 | export class EditorWidgetFactory extends TheiaEditorWidgetFactory { 15 | @inject(SketchesService) 16 | private readonly sketchesService: SketchesService; 17 | 18 | @inject(SketchesServiceClientImpl) 19 | private readonly sketchesServiceClient: SketchesServiceClientImpl; 20 | 21 | protected override async createEditor( 22 | uri: URI, 23 | options?: NavigatableWidgetOptions 24 | ): Promise { 25 | const widget = await super.createEditor(uri, options); 26 | return this.maybeUpdateCaption(widget); 27 | } 28 | 29 | protected async maybeUpdateCaption( 30 | widget: EditorWidget 31 | ): Promise { 32 | const sketch = await this.sketchesServiceClient.currentSketch(); 33 | const { uri } = widget.editor; 34 | if (CurrentSketch.isValid(sketch) && Sketch.isInSketch(uri, sketch)) { 35 | const isTemp = await this.sketchesService.isTemp(sketch); 36 | if (isTemp) { 37 | widget.title.caption = nls.localize( 38 | 'theia/editor/unsavedTitle', 39 | 'Unsaved – {0}', 40 | this.labelProvider.getName(uri) 41 | ); 42 | } 43 | } 44 | return widget; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { MenuModelRegistry } from '@theia/core'; 3 | import { 4 | KeymapsFrontendContribution as TheiaKeymapsFrontendContribution, 5 | KeymapsCommands, 6 | } from '@theia/keymaps/lib/browser/keymaps-frontend-contribution'; 7 | import { ArduinoMenus } from '../../menu/arduino-menus'; 8 | import { nls } from '@theia/core/lib/common'; 9 | 10 | @injectable() 11 | export class KeymapsFrontendContribution extends TheiaKeymapsFrontendContribution { 12 | override registerMenus(menus: MenuModelRegistry): void { 13 | menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, { 14 | commandId: KeymapsCommands.OPEN_KEYMAPS.id, 15 | label: nls.localize( 16 | 'vscode/helpActions/miKeyboardShortcuts', 17 | 'Keyboard Shortcuts' 18 | ), 19 | order: '1', 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { KeybindingRegistry } from '@theia/core/lib/browser'; 3 | import { ProblemStat } from '@theia/markers/lib/browser/problem/problem-manager'; 4 | import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; 5 | import { ProblemContribution as TheiaProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution'; 6 | 7 | @injectable() 8 | export class ProblemContribution extends TheiaProblemContribution { 9 | override async initializeLayout(app: FrontendApplication): Promise { 10 | // NOOP 11 | } 12 | 13 | protected override setStatusBarElement(problemStat: ProblemStat): void { 14 | // NOOP 15 | } 16 | 17 | override registerKeybindings(keybindings: KeybindingRegistry): void { 18 | if (this.toggleCommand) { 19 | keybindings.registerKeybinding({ 20 | command: this.toggleCommand.id, 21 | keybinding: 'ctrlcmd+alt+shift+m', 22 | }); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx: -------------------------------------------------------------------------------- 1 | import React from '@theia/core/shared/react'; 2 | import { NotificationComponent } from './notification-component'; 3 | import { NotificationToastsComponent as TheiaNotificationToastsComponent } from '@theia/messages/lib/browser/notification-toasts-component'; 4 | 5 | export class NotificationToastsComponent extends TheiaNotificationToastsComponent { 6 | override render(): React.ReactNode { 7 | return ( 8 |
13 |
14 | {this.state.toasts.map((notification) => ( 15 | 20 | ))} 21 |
22 |
23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/messages/notifications-manager.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken } from '@theia/core/lib/common/cancellation'; 2 | import type { 3 | ProgressMessage, 4 | ProgressUpdate, 5 | } from '@theia/core/lib/common/message-service-protocol'; 6 | import { injectable } from '@theia/core/shared/inversify'; 7 | import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager'; 8 | 9 | @injectable() 10 | export class NotificationManager extends TheiaNotificationManager { 11 | override async reportProgress( 12 | messageId: string, 13 | update: ProgressUpdate, 14 | originalMessage: ProgressMessage, 15 | cancellationToken: CancellationToken 16 | ): Promise { 17 | const notification = this.find(messageId); 18 | if (!notification) { 19 | return; 20 | } 21 | if (cancellationToken.isCancellationRequested) { 22 | this.clear(messageId); 23 | } else { 24 | notification.message = 25 | originalMessage.text && update.message 26 | ? `${originalMessage.text}: ${update.message}` 27 | : originalMessage.text || update?.message || notification.message; 28 | 29 | // Unlike in Theia, we allow resetting the progress monitor to NaN to enforce unknown progress. 30 | const candidate = this.toPlainProgress(update); 31 | notification.progress = 32 | typeof candidate === 'number' ? candidate : notification.progress; 33 | } 34 | this.fireUpdatedEvent(); 35 | } 36 | 37 | protected override toPlainProgress( 38 | update: ProgressUpdate 39 | ): number | undefined { 40 | if (!update.work) { 41 | return undefined; 42 | } 43 | if (Number.isNaN(update.work.done) || Number.isNaN(update.work.total)) { 44 | return Number.NaN; // This should trigger the unknown monitor. 45 | } 46 | return Math.min((update.work.done / update.work.total) * 100, 100); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/messages/notifications-renderer.tsx: -------------------------------------------------------------------------------- 1 | import React from '@theia/core/shared/react'; 2 | import { 3 | inject, 4 | injectable, 5 | postConstruct, 6 | } from '@theia/core/shared/inversify'; 7 | import { NotificationCenterComponent } from './notification-center-component'; 8 | import { NotificationToastsComponent } from './notification-toasts-component'; 9 | import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/messages/lib/browser/notifications-renderer'; 10 | import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; 11 | 12 | @injectable() 13 | export class NotificationsRenderer extends TheiaNotificationsRenderer { 14 | @inject(FrontendApplicationStateService) 15 | private readonly appStateService: FrontendApplicationStateService; 16 | 17 | @postConstruct() 18 | protected override init(): void { 19 | // Unlike Theia, IDE2 renders the notification area only when the app is ready. 20 | this.appStateService.reachedState('ready').then(() => { 21 | this.createOverlayContainer(); 22 | this.render(); 23 | }); 24 | } 25 | 26 | protected override render(): void { 27 | this.containerRoot.render( 28 |
29 | 33 | 34 |
35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/monaco/monaco-formatting-conflicts.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { MonacoFormattingConflictsContribution as TheiaMonacoFormattingConflictsContribution } from '@theia/monaco/lib/browser/monaco-formatting-conflicts'; 3 | 4 | @injectable() 5 | export class MonacoFormattingConflictsContribution extends TheiaMonacoFormattingConflictsContribution { 6 | override async initialize(): Promise { 7 | // NOOP - does not register a custom formatting conflicts selects. 8 | // Does not get and set formatter preferences when selecting from multiple formatters. 9 | // Does not show quick-pick input when multiple formatters are available for the text model. 10 | // Uses the default behavior from VS Code: https://github.com/microsoft/vscode/blob/fb9f488e51af2e2efe95a34f24ca11e1b2a3f744/src/vs/editor/editor.api.ts#L19-L21 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/monaco/monaco-menu.ts: -------------------------------------------------------------------------------- 1 | import type { MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | import { MonacoEditorMenuContribution as TheiaMonacoEditorMenuContribution } from '@theia/monaco/lib/browser/monaco-menu'; 4 | 5 | @injectable() 6 | export class MonacoEditorMenuContribution extends TheiaMonacoEditorMenuContribution { 7 | override registerMenus(registry: MenuModelRegistry): void { 8 | super.registerMenus(registry); 9 | // https://github.com/arduino/arduino-ide/issues/1394 10 | registry.unregisterMenuAction('editor.action.refactor'); // Refactor... 11 | registry.unregisterMenuAction('editor.action.sourceAction'); // Source Action... 12 | // https://github.com/arduino/arduino-ide/pull/2027#pullrequestreview-1414246614 13 | // Root editor context menu 14 | registry.unregisterMenuAction('editor.action.revealDeclaration'); // Go to Declaration 15 | registry.unregisterMenuAction('editor.action.goToTypeDefinition'); // Go to Type Definition 16 | registry.unregisterMenuAction('editor.action.goToImplementation'); // Go to Implementations 17 | registry.unregisterMenuAction('editor.action.goToReferences'); // Go to References 18 | // Peek submenu 19 | registry.unregisterMenuAction('editor.action.peekDeclaration'); // Peek Declaration 20 | registry.unregisterMenuAction('editor.action.peekTypeDefinition'); // Peek Type Definition 21 | registry.unregisterMenuAction('editor.action.peekImplementation'); // Peek Implementation 22 | registry.unregisterMenuAction('editor.action.referenceSearch.trigger'); // Peek References 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution'; 3 | 4 | @injectable() 5 | export class MonacoStatusBarContribution extends TheiaMonacoStatusBarContribution { 6 | protected override setConfigTabSizeWidget() {} 7 | 8 | protected override setLineEndingWidget() {} 9 | } 10 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration'; 3 | import { NavigatorTabBarDecorator as TheiaNavigatorTabBarDecorator } from '@theia/navigator/lib/browser/navigator-tab-bar-decorator'; 4 | 5 | /** 6 | * To silent the badge decoration in the `Explorer`. 7 | * https://github.com/eclipse-theia/theia/issues/8709 8 | */ 9 | @injectable() 10 | export class NavigatorTabBarDecorator extends TheiaNavigatorTabBarDecorator { 11 | override onStart(): void { 12 | // NOOP 13 | } 14 | 15 | override decorate(): WidgetDecoration.Data[] { 16 | // Does not decorate anything. 17 | return []; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; 3 | import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution'; 4 | 5 | @injectable() 6 | export class OutlineViewContribution extends TheiaOutlineViewContribution { 7 | constructor() { 8 | super(); 9 | this.options.defaultWidgetOptions = { 10 | area: 'left', 11 | rank: 500, 12 | }; 13 | } 14 | 15 | override async initializeLayout(app: FrontendApplication): Promise { 16 | // NOOP 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/output/output-editor-factory.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor'; 3 | import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model'; 4 | import { OutputEditorFactory as TheiaOutputEditorFactory } from '@theia/output/lib/browser/output-editor-factory'; 5 | 6 | @injectable() 7 | export class OutputEditorFactory extends TheiaOutputEditorFactory { 8 | protected override createOptions( 9 | model: MonacoEditorModel, 10 | defaultOptions: MonacoEditor.IOptions 11 | ): MonacoEditor.IOptions { 12 | const options = super.createOptions(model, defaultOptions); 13 | return { 14 | ...options, 15 | // Taken from https://github.com/microsoft/vscode/blob/35b971c92d210face8c446a1c6f1e470ad2bcb54/src/vs/workbench/contrib/output/browser/outputView.ts#L211-L214 16 | // To fix https://github.com/arduino/arduino-ide/issues/1210 17 | unicodeHighlight: { 18 | nonBasicASCII: false, 19 | invisibleCharacters: false, 20 | ambiguousCharacters: false, 21 | }, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { 3 | ReactTabBarToolbarItem, 4 | TabBarToolbarItem, 5 | TabBarToolbarRegistry, 6 | } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; 7 | import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution'; 8 | 9 | @injectable() 10 | export class OutputToolbarContribution extends TheiaOutputToolbarContribution { 11 | override async registerToolbarItems( 12 | registry: TabBarToolbarRegistry 13 | ): Promise { 14 | await super.registerToolbarItems(registry); // Why is it async? 15 | // It's a hack. Currently, it's not possible to unregister a toolbar contribution via API. 16 | ( 17 | (registry as any).items as Map< 18 | string, 19 | TabBarToolbarItem | ReactTabBarToolbarItem 20 | > 21 | ).delete('channels'); 22 | (registry as any).fireOnDidChange(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts: -------------------------------------------------------------------------------- 1 | import { injectable, inject } from '@theia/core/shared/inversify'; 2 | import { CommandService } from '@theia/core/lib/common/command'; 3 | import { OutputCommands } from '@theia/output/lib/browser/output-commands'; 4 | import { PluginInfo } from '@theia/plugin-ext/lib/common/plugin-api-rpc'; 5 | import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl } from '@theia/plugin-ext/lib/main/browser/output-channel-registry-main'; 6 | 7 | @injectable() 8 | export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMainImpl { 9 | @inject(CommandService) 10 | protected override readonly commandService: CommandService; 11 | 12 | override $append( 13 | name: string, 14 | text: string, 15 | pluginInfo: PluginInfo 16 | ): PromiseLike { 17 | this.commandService.executeCommand(OutputCommands.APPEND.id, { 18 | name, 19 | text, 20 | }); 21 | return Promise.resolve(); 22 | } 23 | 24 | override $clear(name: string): PromiseLike { 25 | this.commandService.executeCommand(OutputCommands.CLEAR.id, { name }); 26 | return Promise.resolve(); 27 | } 28 | 29 | override $dispose(name: string): PromiseLike { 30 | this.commandService.executeCommand(OutputCommands.DISPOSE.id, { name }); 31 | return Promise.resolve(); 32 | } 33 | 34 | override async $reveal(name: string, preserveFocus: boolean): Promise { 35 | const options = { preserveFocus }; 36 | this.commandService.executeCommand(OutputCommands.SHOW.id, { 37 | name, 38 | options, 39 | }); 40 | } 41 | 42 | override $close(name: string): PromiseLike { 43 | this.commandService.executeCommand(OutputCommands.HIDE.id, { name }); 44 | return Promise.resolve(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/preferences/preference-editor-widget.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/preferences/lib/browser/views/preference-editor-widget'; 3 | 4 | @injectable() 5 | export class PreferencesEditorWidget extends TheiaPreferencesEditorWidget { 6 | protected override resetScroll( 7 | nodeIDToScrollTo?: string, 8 | filterWasCleared = false 9 | ): void { 10 | if (this.scrollBar) { 11 | // Absent on widget creation 12 | this.doResetScroll(nodeIDToScrollTo, filterWasCleared); 13 | } else { 14 | // NOOP 15 | // Unlike Theia, IDE2 does not start multiple tasks to check if the scrollbar is ready to reset it. 16 | // If the "scroll reset" request arrived before the existence of the scrollbar, what to reset? 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FrontendApplicationState, 3 | FrontendApplicationStateService, 4 | } from '@theia/core/lib/browser/frontend-application-state'; 5 | import { CompositeTreeNode } from '@theia/core/lib/browser/tree/tree'; 6 | import { inject, injectable } from '@theia/core/shared/inversify'; 7 | import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator'; 8 | 9 | @injectable() 10 | export class PreferenceTreeGenerator extends TheiaPreferenceTreeGenerator { 11 | private shouldHandleChangedSchemaOnReady = false; 12 | private state: FrontendApplicationState | undefined; 13 | 14 | @inject(FrontendApplicationStateService) 15 | private readonly appStateService: FrontendApplicationStateService; 16 | 17 | protected override init(): void { 18 | this.appStateService.onStateChanged((state) => { 19 | this.state = state; 20 | // manually trigger a model (and UI) refresh if it was requested during the startup phase. 21 | if (this.state === 'ready' && this.shouldHandleChangedSchemaOnReady) { 22 | this.doHandleChangedSchema(); 23 | } 24 | }); 25 | return super.init(); 26 | } 27 | 28 | override doHandleChangedSchema(): void { 29 | if (this.state === 'ready') { 30 | super.doHandleChangedSchema(); 31 | } 32 | // don't do anything until the app is `ready`, then invoke `doHandleChangedSchema`. 33 | this.shouldHandleChangedSchemaOnReady = true; 34 | } 35 | 36 | override generateTree(): CompositeTreeNode { 37 | if (this.state === 'ready') { 38 | return super.generateTree(); 39 | } 40 | // always create an empty root when the app is not ready. 41 | this._root = this.createRootNode(); 42 | return this._root; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { MenuModelRegistry } from '@theia/core/lib/common/menu'; 3 | import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding'; 4 | import { CommonCommands, CommonMenus } from '@theia/core/lib/browser'; 5 | import { PreferencesContribution as TheiaPreferencesContribution } from '@theia/preferences/lib/browser/preferences-contribution'; 6 | 7 | @injectable() 8 | export class PreferencesContribution extends TheiaPreferencesContribution { 9 | override registerMenus(registry: MenuModelRegistry): void { 10 | super.registerMenus(registry); 11 | // The settings group: preferences, CLI config is not part of the `File` menu on macOS. 12 | // On Windows and Linux, we rebind it to `Preferences...`. It is safe to remove here. 13 | registry.unregisterMenuAction( 14 | CommonCommands.OPEN_PREFERENCES.id, 15 | CommonMenus.FILE_SETTINGS_SUBMENU_OPEN 16 | ); 17 | } 18 | 19 | override registerKeybindings(registry: KeybindingRegistry): void { 20 | registry.unregisterKeybinding(CommonCommands.OPEN_PREFERENCES.id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { ScmContribution as TheiaScmContribution } from '@theia/scm/lib/browser/scm-contribution'; 3 | import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar'; 4 | 5 | @injectable() 6 | export class ScmContribution extends TheiaScmContribution { 7 | override async initializeLayout(): Promise { 8 | // NOOP 9 | } 10 | 11 | protected override setStatusBarEntry( 12 | id: string, 13 | entry: StatusBarEntry 14 | ): void { 15 | // NOOP 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts: -------------------------------------------------------------------------------- 1 | import { ViewContainer } from '@theia/core/lib/browser/view-container'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | 4 | import { 5 | SearchInWorkspaceFactory as TheiaSearchInWorkspaceFactory, 6 | SEARCH_VIEW_CONTAINER_TITLE_OPTIONS, 7 | } from '@theia/search-in-workspace/lib/browser/search-in-workspace-factory'; 8 | 9 | @injectable() 10 | export class SearchInWorkspaceFactory extends TheiaSearchInWorkspaceFactory { 11 | override async createWidget(): Promise { 12 | const viewContainer = await super.createWidget(); 13 | viewContainer.setTitleOptions({ 14 | ...SEARCH_VIEW_CONTAINER_TITLE_OPTIONS, 15 | iconClass: 'fa fa-arduino-search', 16 | }); 17 | return viewContainer; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { MenuModelRegistry } from '@theia/core/lib/common/menu'; 3 | import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding'; 4 | import { 5 | SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution, 6 | SearchInWorkspaceCommands, 7 | } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; 8 | 9 | @injectable() 10 | export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspaceFrontendContribution { 11 | constructor() { 12 | super(); 13 | this.options.defaultWidgetOptions.rank = 5; 14 | } 15 | 16 | override registerMenus(registry: MenuModelRegistry): void { 17 | super.registerMenus(registry); 18 | registry.unregisterMenuAction(SearchInWorkspaceCommands.OPEN_SIW_WIDGET); 19 | } 20 | 21 | override registerKeybindings(keybindings: KeybindingRegistry): void { 22 | super.registerKeybindings(keybindings); 23 | keybindings.unregisterKeybinding(SearchInWorkspaceCommands.OPEN_SIW_WIDGET); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/terminal/terminal-frontend-contribution.ts: -------------------------------------------------------------------------------- 1 | import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | import { 4 | TerminalCommands, 5 | TerminalFrontendContribution as TheiaTerminalFrontendContribution, 6 | } from '@theia/terminal/lib/browser/terminal-frontend-contribution'; 7 | 8 | @injectable() 9 | export class TerminalFrontendContribution extends TheiaTerminalFrontendContribution { 10 | override registerToolbarItems(toolbar: TabBarToolbarRegistry): void { 11 | super.registerToolbarItems(toolbar); 12 | // removes the `split-terminal` command from the tabbar toolbar 13 | // https://github.com/dankeboy36/esp-exception-decoder/pull/1#pullrequestreview-1500146673 14 | toolbar.unregisterItem(TerminalCommands.SPLIT.id); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/test/test-view-contribution.ts: -------------------------------------------------------------------------------- 1 | import { TestViewContribution as TheiaTestViewContribution } from '@theia/test/lib/browser/view/test-view-contribution'; 2 | import { injectable } from 'inversify'; 3 | 4 | @injectable() 5 | export class TestViewContribution extends TheiaTestViewContribution { 6 | override async initializeLayout(): Promise { 7 | // NOOP 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/typehierarchy/type-hierarchy-contribution.ts: -------------------------------------------------------------------------------- 1 | import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding'; 2 | import { CommandRegistry } from '@theia/core/lib/common/command'; 3 | import { MenuModelRegistry } from '@theia/core/lib/common/menu'; 4 | import { injectable } from '@theia/core/shared/inversify'; 5 | import { 6 | TypeHierarchyCommands, 7 | TypeHierarchyContribution as TheiaTypeHierarchyContribution, 8 | } from '@theia/typehierarchy/lib/browser/typehierarchy-contribution'; 9 | 10 | @injectable() 11 | export class TypeHierarchyContribution extends TheiaTypeHierarchyContribution { 12 | protected override init(): void { 13 | // NOOP 14 | } 15 | 16 | override registerCommands(registry: CommandRegistry): void { 17 | super.registerCommands(registry); 18 | registry.unregisterCommand(TypeHierarchyCommands.OPEN_SUBTYPE.id); 19 | registry.unregisterCommand(TypeHierarchyCommands.OPEN_SUPERTYPE.id); 20 | } 21 | 22 | override registerMenus(registry: MenuModelRegistry): void { 23 | super.registerMenus(registry); 24 | registry.unregisterMenuAction(TypeHierarchyCommands.OPEN_SUBTYPE.id); 25 | registry.unregisterMenuAction(TypeHierarchyCommands.OPEN_SUPERTYPE.id); 26 | } 27 | 28 | override registerKeybindings(registry: KeybindingRegistry): void { 29 | super.registerKeybindings(registry); 30 | registry.unregisterKeybinding(TypeHierarchyCommands.OPEN_SUBTYPE.id); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/typehierarchy/type-hierarchy-service.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { TypeHierarchyServiceProvider as TheiaTypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser/typehierarchy-service'; 3 | 4 | @injectable() 5 | export class TypeHierarchyServiceProvider extends TheiaTypeHierarchyServiceProvider { 6 | override init(): void { 7 | // NOOP 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts: -------------------------------------------------------------------------------- 1 | import { CommandService } from '@theia/core/lib/common/command'; 2 | import URI from '@theia/core/lib/common/uri'; 3 | import { inject, injectable } from '@theia/core/shared/inversify'; 4 | import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler'; 5 | import { DeleteSketch } from '../../contributions/delete-sketch'; 6 | import { 7 | CurrentSketch, 8 | SketchesServiceClientImpl, 9 | } from '../../sketches-service-client-impl'; 10 | 11 | @injectable() 12 | export class WorkspaceDeleteHandler extends TheiaWorkspaceDeleteHandler { 13 | @inject(CommandService) 14 | private readonly commandService: CommandService; 15 | @inject(SketchesServiceClientImpl) 16 | private readonly sketchesServiceClient: SketchesServiceClientImpl; 17 | 18 | override async execute(uris: URI[]): Promise { 19 | const sketch = await this.sketchesServiceClient.currentSketch(); 20 | if (!CurrentSketch.isValid(sketch)) { 21 | return; 22 | } 23 | // Deleting the main sketch file means deleting the sketch folder and all its content. 24 | if (uris.some((uri) => uri.toString() === sketch.mainFileUri)) { 25 | return this.commandService.executeCommand( 26 | DeleteSketch.Commands.DELETE_SKETCH.id, 27 | { 28 | toDelete: sketch, 29 | willNavigateAway: true, 30 | } 31 | ); 32 | } 33 | // Individual file deletion(s). 34 | return super.execute(uris); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts: -------------------------------------------------------------------------------- 1 | import { 2 | inject, 3 | injectable, 4 | postConstruct, 5 | } from '@theia/core/shared/inversify'; 6 | import URI from '@theia/core/lib/common/uri'; 7 | import { WorkspaceVariableContribution as TheiaWorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution'; 8 | import { Sketch } from '../../../common/protocol'; 9 | import { 10 | CurrentSketch, 11 | SketchesServiceClientImpl, 12 | } from '../../sketches-service-client-impl'; 13 | import { DisposableCollection } from '@theia/core/lib/common/disposable'; 14 | 15 | @injectable() 16 | export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContribution { 17 | @inject(SketchesServiceClientImpl) 18 | private readonly sketchesServiceClient: SketchesServiceClientImpl; 19 | 20 | private currentSketch?: Sketch; 21 | 22 | @postConstruct() 23 | protected override init(): void { 24 | const sketch = this.sketchesServiceClient.tryGetCurrentSketch(); 25 | if (CurrentSketch.isValid(sketch)) { 26 | this.currentSketch = sketch; 27 | } else { 28 | const toDispose = new DisposableCollection(); 29 | toDispose.push( 30 | this.sketchesServiceClient.onCurrentSketchDidChange((sketch) => { 31 | if (CurrentSketch.isValid(sketch)) { 32 | this.currentSketch = sketch; 33 | } 34 | toDispose.dispose(); 35 | }) 36 | ); 37 | } 38 | } 39 | 40 | override getResourceUri(): URI | undefined { 41 | const resourceUri = super.getResourceUri(); 42 | // https://github.com/arduino/arduino-ide/issues/46 43 | // `currentWidget` can be an editor representing a file outside of the workspace. The current sketch should be a fallback. 44 | if (!resourceUri && this.currentSketch?.uri) { 45 | return new URI(this.currentSketch.uri); 46 | } 47 | return resourceUri; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const REMOTE_SKETCHBOOK_FOLDER = 'RemoteSketchbook'; 2 | export const ARDUINO_CLOUD_FOLDER = 'ArduinoCloud'; 3 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/utils/window.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Changes the `window.location` without navigating away. 3 | */ 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | export function setURL(url: URL, data: any = {}): void { 6 | history.pushState(data, '', url); 7 | } 8 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/widgets/cloud-sketchbook/README.md: -------------------------------------------------------------------------------- 1 | Sketchcache = is a cache that holds sketches and fileStat objects. 2 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketch-cache.ts: -------------------------------------------------------------------------------- 1 | import { FileStat } from '@theia/filesystem/lib/common/files'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | import { splitSketchPath } from '../../create/create-paths'; 4 | import { Create } from '../../create/typings'; 5 | 6 | @injectable() 7 | export class SketchCache { 8 | sketches: Record = {}; 9 | fileStats: Record = {}; 10 | private _createPathPrefix: string | undefined; 11 | 12 | init(): void { 13 | // reset the data 14 | this.sketches = {}; 15 | this.fileStats = {}; 16 | } 17 | 18 | addItem(item: FileStat): void { 19 | this.fileStats[item.resource.path.toString()] = item; 20 | } 21 | 22 | getItem(path: string): FileStat | null { 23 | return this.fileStats[path] || null; 24 | } 25 | 26 | purgeByPath(path: string): void { 27 | for (const itemPath in this.fileStats) { 28 | if (itemPath.indexOf(path) === 0) { 29 | delete this.fileStats[itemPath]; 30 | } 31 | } 32 | } 33 | 34 | addSketch(sketch: Create.Sketch): void { 35 | const { path } = sketch; 36 | const [pathPrefix, posixPath] = splitSketchPath(path); 37 | if (pathPrefix !== this._createPathPrefix) { 38 | this._createPathPrefix = pathPrefix; 39 | } 40 | this.sketches[posixPath] = sketch; 41 | } 42 | 43 | getSketch(path: string): Create.Sketch | null { 44 | return this.sketches[path] || null; 45 | } 46 | 47 | get createPathPrefix(): string | undefined { 48 | return this._createPathPrefix; 49 | } 50 | 51 | toString(): string { 52 | return JSON.stringify({ 53 | sketches: this.sketches, 54 | fileStats: this.fileStats, 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-container.ts: -------------------------------------------------------------------------------- 1 | import { interfaces, Container } from '@theia/core/shared/inversify'; 2 | import { CloudSketchbookTreeWidget } from './cloud-sketchbook-tree-widget'; 3 | import { CloudSketchbookTree } from './cloud-sketchbook-tree'; 4 | import { CloudSketchbookTreeModel } from './cloud-sketchbook-tree-model'; 5 | import { createSketchbookTreeContainer } from '../sketchbook/sketchbook-tree-container'; 6 | import { SketchbookTree } from '../sketchbook/sketchbook-tree'; 7 | import { SketchbookTreeModel } from '../sketchbook/sketchbook-tree-model'; 8 | import { SketchbookTreeWidget } from '../sketchbook/sketchbook-tree-widget'; 9 | 10 | function createCloudSketchbookTreeContainer( 11 | parent: interfaces.Container 12 | ): Container { 13 | const child = createSketchbookTreeContainer(parent); 14 | child.bind(CloudSketchbookTree).toSelf(); 15 | child.rebind(SketchbookTree).toService(CloudSketchbookTree); 16 | child.bind(CloudSketchbookTreeModel).toSelf(); 17 | child.rebind(SketchbookTreeModel).toService(CloudSketchbookTreeModel); 18 | child.bind(CloudSketchbookTreeWidget).toSelf(); 19 | child.rebind(SketchbookTreeWidget).toService(CloudSketchbookTreeWidget); 20 | return child; 21 | } 22 | 23 | export function createCloudSketchbookTreeWidget( 24 | parent: interfaces.Container 25 | ): CloudSketchbookTreeWidget { 26 | return createCloudSketchbookTreeContainer(parent).get( 27 | CloudSketchbookTreeWidget 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx: -------------------------------------------------------------------------------- 1 | import React from '@theia/core/shared/react'; 2 | import { Virtuoso } from '@theia/core/shared/react-virtuoso'; 3 | import { ArduinoComponent } from '../../../common/protocol/arduino-component'; 4 | import { Installable } from '../../../common/protocol/installable'; 5 | import { ComponentListItem } from './component-list-item'; 6 | import { ListItemRenderer } from './list-item-renderer'; 7 | 8 | export class ComponentList extends React.Component< 9 | ComponentList.Props 10 | > { 11 | override render(): React.ReactNode { 12 | return ( 13 | ( 16 | 17 | key={this.props.itemLabel(item)} 18 | item={item} 19 | itemRenderer={this.props.itemRenderer} 20 | install={this.props.install} 21 | uninstall={this.props.uninstall} 22 | edited={this.props.edited} 23 | onItemEdit={this.props.onItemEdit} 24 | /> 25 | )} 26 | /> 27 | ); 28 | } 29 | } 30 | export namespace ComponentList { 31 | export interface Props { 32 | readonly items: T[]; 33 | readonly itemLabel: (item: T) => string; 34 | readonly itemRenderer: ListItemRenderer; 35 | readonly install: (item: T, version?: Installable.Version) => Promise; 36 | readonly uninstall: (item: T) => Promise; 37 | readonly edited?: { 38 | item: T; 39 | selectedVersion: Installable.Version; 40 | }; 41 | readonly onItemEdit: ( 42 | item: T, 43 | selectedVersion: Installable.Version 44 | ) => void; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx: -------------------------------------------------------------------------------- 1 | import { nls } from '@theia/core/lib/common'; 2 | import React from '@theia/core/shared/react'; 3 | 4 | export class SearchBar extends React.Component { 5 | constructor(props: Readonly) { 6 | super(props); 7 | this.handleFilterTextChange = this.handleFilterTextChange.bind(this); 8 | } 9 | 10 | override render(): React.ReactNode { 11 | return ( 12 | 24 | ); 25 | } 26 | 27 | private setRef = (element: HTMLElement | null) => { 28 | if (this.props.resolveFocus) { 29 | this.props.resolveFocus(element || undefined); 30 | } 31 | }; 32 | 33 | private handleFilterTextChange( 34 | event: React.ChangeEvent 35 | ): void { 36 | this.props.onFilterTextChanged(event.target.value); 37 | } 38 | } 39 | 40 | export namespace SearchBar { 41 | export interface Props { 42 | filterText: string; 43 | onFilterTextChanged(filterText: string): void; 44 | readonly resolveFocus?: (element: HTMLElement | undefined) => void; 45 | } 46 | 47 | export namespace Styles { 48 | export const SEARCH_BAR_CLASS = 'search-bar'; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/widgets/sketchbook/create-new.tsx: -------------------------------------------------------------------------------- 1 | import React from '@theia/core/shared/react'; 2 | 3 | export class CreateNew extends React.Component { 4 | override render(): React.ReactNode { 5 | return ( 6 |
7 | 10 |
11 | ); 12 | } 13 | } 14 | 15 | export namespace CreateNew { 16 | export interface Props { 17 | readonly label: string; 18 | readonly onClick: () => void; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-commands.ts: -------------------------------------------------------------------------------- 1 | import { Command } from '@theia/core/lib/common/command'; 2 | 3 | export namespace SketchbookCommands { 4 | export const TOGGLE_SKETCHBOOK_WIDGET: Command = { 5 | id: 'arduino-sketchbook-widget:toggle', 6 | }; 7 | 8 | export const REVEAL_SKETCH_NODE: Command = { 9 | id: 'arduino-sketchbook--reveal-sketch-node', 10 | }; 11 | 12 | export const OPEN_NEW_WINDOW = Command.toLocalizedCommand( 13 | { 14 | id: 'arduino-sketchbook--open-sketch-new-window', 15 | label: 'Open Sketch in New Window', 16 | }, 17 | 'arduino/sketch/openSketchInNewWindow' 18 | ); 19 | 20 | export const REVEAL_IN_FINDER = Command.toLocalizedCommand( 21 | { 22 | id: 'arduino-sketchbook--reveal-in-finder', 23 | label: 'Open Folder', 24 | }, 25 | 'arduino/sketch/openFolder' 26 | ); 27 | 28 | export const OPEN_SKETCHBOOK_CONTEXT_MENU: Command = { 29 | id: 'arduino-sketchbook--open-sketch-context-menu', 30 | iconClass: 'sketchbook-tree__opts', 31 | }; 32 | 33 | export const SKETCHBOOK_HIDE_FILES: Command = { 34 | id: 'arduino-sketchbook--hide-files', 35 | }; 36 | 37 | export const SKETCHBOOK_SHOW_FILES: Command = { 38 | id: 'arduino-sketchbook--show-files', 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-container.ts: -------------------------------------------------------------------------------- 1 | import { interfaces, Container } from '@theia/core/shared/inversify'; 2 | import { 3 | CompressionToggle, 4 | createTreeContainer, 5 | Tree, 6 | TreeCompressionService, 7 | TreeImpl, 8 | TreeModel, 9 | TreeModelImpl, 10 | TreeWidget, 11 | } from '@theia/core/lib/browser/tree'; 12 | import { SketchbookTree } from './sketchbook-tree'; 13 | import { SketchbookTreeModel } from './sketchbook-tree-model'; 14 | import { SketchbookTreeWidget } from './sketchbook-tree-widget'; 15 | 16 | export function createSketchbookTreeContainer( 17 | parent: interfaces.Container 18 | ): Container { 19 | const child = createTreeContainer(parent); 20 | 21 | child.unbind(TreeImpl); 22 | child.bind(SketchbookTree).toSelf(); 23 | child.rebind(Tree).toService(SketchbookTree); 24 | 25 | child.unbind(TreeModelImpl); 26 | child.bind(SketchbookTreeModel).toSelf(); 27 | child.rebind(TreeModel).toService(SketchbookTreeModel); 28 | 29 | child.bind(SketchbookTreeWidget).toSelf(); 30 | child.rebind(TreeWidget).toService(SketchbookTreeWidget); 31 | 32 | child.bind(CompressionToggle).toConstantValue({ compress: false }); 33 | child.bind(TreeCompressionService).toSelf().inSingletonScope(); 34 | 35 | return child; 36 | } 37 | 38 | export function createSketchbookTreeWidget( 39 | parent: interfaces.Container 40 | ): SketchbookTreeWidget { 41 | return createSketchbookTreeContainer(parent).get(SketchbookTreeWidget); 42 | } 43 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/main-menu-manager.ts: -------------------------------------------------------------------------------- 1 | export const MainMenuManager = Symbol('MainMenuManager'); 2 | export interface MainMenuManager { 3 | /** 4 | * Call this method if you have changed the content of the main menu (updated a toggle flag, removed/added new groups or menu items) 5 | * and you want to re-render it from scratch. Works for electron too. 6 | */ 7 | update(): void; 8 | } 9 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/nls.ts: -------------------------------------------------------------------------------- 1 | import { nls } from '@theia/core/lib/common/nls'; 2 | 3 | // TODO: rename constants: `Unknown` should be `unknownLabel`, change `Later` to `laterLabel`, etc. 4 | export const Unknown = nls.localize('arduino/common/unknown', 'Unknown'); 5 | export const Later = nls.localize('arduino/common/later', 'Later'); 6 | export const Updatable = nls.localize('arduino/common/updateable', 'Updatable'); 7 | export const All = nls.localize('arduino/common/all', 'All'); 8 | export const Type = nls.localize('arduino/common/type', 'Type'); 9 | export const Partner = nls.localize('arduino/common/partner', 'Partner'); 10 | export const Contributed = nls.localize( 11 | 'arduino/common/contributed', 12 | 'Contributed' 13 | ); 14 | export const Recommended = nls.localize( 15 | 'arduino/common/recommended', 16 | 'Recommended' 17 | ); 18 | export const Retired = nls.localize('arduino/common/retired', 'Retired'); 19 | export const InstallManually = nls.localize( 20 | 'arduino/common/installManually', 21 | 'Install Manually' 22 | ); 23 | export const SelectManually = nls.localize( 24 | 'arduino/common/selectManually', 25 | 'Select Manually' 26 | ); 27 | 28 | export const serialMonitorWidgetLabel = nls.localize( 29 | 'arduino/common/serialMonitor', 30 | 'Serial Monitor' 31 | ); 32 | 33 | export const noBoardSelected = nls.localize( 34 | 'arduino/common/noBoardSelected', 35 | 'No board selected' 36 | ); 37 | 38 | export const noSketchOpened = nls.localize( 39 | 'arduino/common/noSketchOpened', 40 | 'No sketch opened' 41 | ); 42 | 43 | export const userAbort = nls.localize('arduino/common/userAbort', 'User abort'); 44 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/arduino-component.ts: -------------------------------------------------------------------------------- 1 | import type { Installable } from './installable'; 2 | 3 | export interface ArduinoComponent { 4 | readonly name: string; 5 | readonly author: string; 6 | readonly summary: string; 7 | readonly description: string; 8 | readonly availableVersions: Installable.Version[]; 9 | readonly installedVersion?: Installable.Version; 10 | /** 11 | * This is the `Type` in IDE (1.x) UI. 12 | */ 13 | readonly types: string[]; 14 | readonly deprecated?: boolean; 15 | readonly moreInfoLink?: string; 16 | } 17 | export namespace ArduinoComponent { 18 | export function is(arg: unknown): arg is ArduinoComponent { 19 | return ( 20 | typeof arg === 'object' && 21 | (arg).name !== undefined && 22 | typeof (arg).name === 'string' && 23 | (arg).author !== undefined && 24 | typeof (arg).author === 'string' && 25 | (arg).summary !== undefined && 26 | typeof (arg).summary === 'string' && 27 | (arg).description !== undefined && 28 | typeof (arg).description === 'string' && 29 | (arg).availableVersions !== undefined && 30 | Array.isArray((arg).availableVersions) && 31 | (arg).types !== undefined && 32 | Array.isArray((arg).types) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/arduino-daemon.ts: -------------------------------------------------------------------------------- 1 | export const ArduinoDaemonPath = '/services/arduino-daemon'; 2 | export const ArduinoDaemon = Symbol('ArduinoDaemon'); 3 | export interface ArduinoDaemon { 4 | /** 5 | * Returns with a promise that resolves with the port 6 | * of the CLI daemon when it's up and running. 7 | */ 8 | getPort(): Promise; 9 | /** 10 | * Unlike `getPort` this method returns with a promise 11 | * that resolves to `undefined` when the daemon is not running. 12 | * Otherwise resolves to the CLI daemon port. 13 | */ 14 | tryGetPort(): Promise; 15 | start(): Promise; 16 | stop(): Promise; 17 | restart(): Promise; 18 | } 19 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/arduino-firmware-uploader.ts: -------------------------------------------------------------------------------- 1 | import type { Port } from './boards-service'; 2 | 3 | export const ArduinoFirmwareUploaderPath = 4 | '/services/arduino-firmware-uploader'; 5 | export const ArduinoFirmwareUploader = Symbol('ArduinoFirmwareUploader'); 6 | export interface FirmwareInfo { 7 | board_name: string; 8 | board_fqbn: string; 9 | module: string; 10 | firmware_version: string; 11 | Latest: boolean; 12 | } 13 | export interface UploadCertificateParams { 14 | readonly fqbn: string; 15 | readonly address: string; 16 | readonly urls: readonly string[]; 17 | } 18 | export interface ArduinoFirmwareUploader { 19 | list(fqbn?: string): Promise; 20 | flash(firmware: FirmwareInfo, port: Port): Promise; 21 | uploadCertificates(params: UploadCertificateParams): Promise; 22 | updatableBoards(): Promise; 23 | availableFirmwares(fqbn: string): Promise; 24 | } 25 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/authentication-service.ts: -------------------------------------------------------------------------------- 1 | import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; 2 | export const authServerPort = 9876; 3 | 4 | export interface AuthOptions { 5 | redirectUri: string; 6 | responseType: string; 7 | clientID: string; 8 | domain: string; 9 | audience: string; 10 | registerUri: string; 11 | scopes: string[]; 12 | } 13 | 14 | export interface AuthenticationSession { 15 | readonly id: string; 16 | readonly accessToken: string; 17 | readonly account: AuthenticationSessionAccountInformation; 18 | readonly scopes: ReadonlyArray; 19 | } 20 | export interface AuthenticationSessionAccountInformation { 21 | readonly id: string; 22 | readonly email: string; 23 | readonly label: string; 24 | readonly picture: string; 25 | } 26 | 27 | export const AuthenticationServicePath = '/services/authentication-service'; 28 | export const AuthenticationService = Symbol('AuthenticationService'); 29 | export interface AuthenticationService 30 | extends JsonRpcServer { 31 | login(): Promise; 32 | logout(): Promise; 33 | session(): Promise; 34 | disposeClient(client: AuthenticationServiceClient): void; 35 | setOptions(authOptions: AuthOptions): Promise; 36 | initAuthSession(): Promise; 37 | } 38 | 39 | export interface AuthenticationServiceClient { 40 | notifySessionDidChange(session?: AuthenticationSession | undefined): void; 41 | } 42 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/examples-service.ts: -------------------------------------------------------------------------------- 1 | import { SketchContainer } from './sketches-service'; 2 | 3 | export const ExamplesServicePath = '/services/example-service'; 4 | export const ExamplesService = Symbol('ExamplesService'); 5 | export interface ExamplesService { 6 | builtIns(): Promise; 7 | installed(options: { fqbn?: string }): Promise<{ 8 | user: SketchContainer[]; 9 | current: SketchContainer[]; 10 | any: SketchContainer[]; 11 | }>; 12 | /** 13 | * Finds example sketch containers for the installed library. 14 | */ 15 | find(options: { libraryName: string }): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/executable-service.ts: -------------------------------------------------------------------------------- 1 | export const ExecutableServicePath = '/services/executable-service'; 2 | export const ExecutableService = Symbol('ExecutableService'); 3 | export interface ExecutableService { 4 | list(): Promise<{ 5 | clangdUri: string; 6 | cliUri: string; 7 | lsUri: string; 8 | }>; 9 | } 10 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/filesystem-ext.ts: -------------------------------------------------------------------------------- 1 | export const FileSystemExtPath = '/services/file-system-ext'; 2 | export const FileSystemExt = Symbol('FileSystemExt'); 3 | export interface FileSystemExt { 4 | getUri(fsPath: string): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/formatter.ts: -------------------------------------------------------------------------------- 1 | export const FormatterPath = '/services/formatter'; 2 | export const Formatter = Symbol('Formatter'); 3 | export interface Formatter { 4 | format({ 5 | content, 6 | formatterConfigFolderUris, 7 | options, 8 | }: { 9 | content: string; 10 | formatterConfigFolderUris: string[]; 11 | options?: FormatterOptions; 12 | }): Promise; 13 | } 14 | export interface FormatterOptions { 15 | /** 16 | * Size of a tab in spaces. 17 | */ 18 | tabSize: number; 19 | /** 20 | * Prefer spaces over tabs. 21 | */ 22 | insertSpaces: boolean; 23 | } 24 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/index.ts: -------------------------------------------------------------------------------- 1 | export * from './arduino-component'; 2 | export * from './arduino-daemon'; 3 | export * from './boards-service'; 4 | export * from './config-service'; 5 | export * from './core-service'; 6 | export * from './filesystem-ext'; 7 | export * from './installable'; 8 | export * from './library-service'; 9 | export * from './searchable'; 10 | export * from './sketches-service'; 11 | export * from './examples-service'; 12 | export * from './executable-service'; 13 | export * from './response-service'; 14 | export * from './notification-service'; 15 | export * from './monitor-service'; 16 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/protocol/response-service.ts: -------------------------------------------------------------------------------- 1 | import type { Event } from '@theia/core/lib/common/event'; 2 | 3 | export interface OutputMessage { 4 | readonly chunk: string; 5 | readonly severity?: OutputMessage.Severity; 6 | } 7 | export namespace OutputMessage { 8 | export enum Severity { 9 | Error, 10 | Warning, 11 | Info, 12 | } 13 | } 14 | 15 | export interface ProgressMessage { 16 | readonly progressId: string; 17 | readonly message: string; 18 | readonly work?: ProgressMessage.Work; 19 | } 20 | export namespace ProgressMessage { 21 | export function is(arg: unknown): arg is ProgressMessage { 22 | if (typeof arg === 'object') { 23 | const object = arg as Record; 24 | return ( 25 | 'progressId' in object && 26 | typeof object.progressId === 'string' && 27 | 'message' in object && 28 | typeof object.message === 'string' 29 | ); 30 | } 31 | return false; 32 | } 33 | export interface Work { 34 | readonly done: number; 35 | readonly total: number; 36 | } 37 | } 38 | 39 | export const ResponseServicePath = '/services/response-service'; 40 | export const ResponseService = Symbol('ResponseService'); 41 | export interface ResponseService { 42 | appendToOutput(message: OutputMessage): void; 43 | reportProgress(message: ProgressMessage): void; 44 | } 45 | 46 | export const ResponseServiceClient = Symbol('ResponseServiceClient'); 47 | export interface ResponseServiceClient extends ResponseService { 48 | onProgressDidChange: Event; 49 | clearOutput: () => void; // TODO: this should not belong here. 50 | } 51 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/common/types.ts: -------------------------------------------------------------------------------- 1 | export type RecursiveRequired = { 2 | [P in keyof T]-?: RecursiveRequired; 3 | }; 4 | 5 | export type Defined = { 6 | [P in keyof T]: NonNullable; 7 | }; 8 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/electron-browser/electron-app-service.ts: -------------------------------------------------------------------------------- 1 | import type { Disposable } from '@theia/core/lib/common/disposable'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | import type { AppInfo, AppService } from '../browser/app-service'; 4 | import type { Sketch } from '../common/protocol/sketches-service'; 5 | import type { StartupTasks } from '../electron-common/startup-task'; 6 | 7 | @injectable() 8 | export class ElectronAppService implements AppService { 9 | quit(): void { 10 | window.electronArduino.quitApp(); 11 | } 12 | 13 | info(): Promise { 14 | return window.electronArduino.appInfo(); 15 | } 16 | 17 | registerStartupTasksHandler( 18 | handler: (tasks: StartupTasks) => void 19 | ): Disposable { 20 | return window.electronArduino.registerStartupTasksHandler(handler); 21 | } 22 | 23 | scheduleDeletion(sketch: Sketch): void { 24 | window.electronArduino.scheduleDeletion(sketch); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/electron-browser/electron-arduino-module.ts: -------------------------------------------------------------------------------- 1 | import { ContainerModule } from '@theia/core/shared/inversify'; 2 | import { AppService } from '../browser/app-service'; 3 | import { DialogService } from '../browser/dialog-service'; 4 | import { ElectronAppService } from './electron-app-service'; 5 | import { ElectronDialogService } from './electron-dialog-service'; 6 | 7 | export default new ContainerModule((bind) => { 8 | bind(ElectronAppService).toSelf().inSingletonScope(); 9 | bind(AppService).toService(ElectronAppService); 10 | bind(ElectronDialogService).toSelf().inSingletonScope(); 11 | bind(DialogService).toService(ElectronDialogService); 12 | }); 13 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/electron-browser/electron-dialog-service.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import type { DialogService } from '../browser/dialog-service'; 3 | import type { 4 | MessageBoxOptions, 5 | MessageBoxReturnValue, 6 | OpenDialogOptions, 7 | OpenDialogReturnValue, 8 | SaveDialogOptions, 9 | SaveDialogReturnValue, 10 | } from '../electron-common/electron-arduino'; 11 | 12 | @injectable() 13 | export class ElectronDialogService implements DialogService { 14 | showMessageBox(options: MessageBoxOptions): Promise { 15 | return window.electronArduino.showMessageBox(options); 16 | } 17 | 18 | showOpenDialog(options: OpenDialogOptions): Promise { 19 | return window.electronArduino.showOpenDialog(options); 20 | } 21 | 22 | showSaveDialog(options: SaveDialogOptions): Promise { 23 | return window.electronArduino.showSaveDialog(options); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts: -------------------------------------------------------------------------------- 1 | import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer'; 2 | import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory'; 3 | import { ElectronMenuContribution as TheiaElectronMenuContribution } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'; 4 | import { ContainerModule } from '@theia/core/shared/inversify'; 5 | import { MainMenuManager } from '../../../common/main-menu-manager'; 6 | import { ElectronContextMenuRenderer } from './electron-context-menu-renderer'; 7 | import { ElectronMainMenuFactory } from './electron-main-menu-factory'; 8 | import { 9 | ElectronMenuContribution, 10 | ElectronMenuUpdater, 11 | } from './electron-menu-contribution'; 12 | 13 | export default new ContainerModule((bind, unbind, isBound, rebind) => { 14 | bind(ElectronMenuContribution).toSelf().inSingletonScope(); 15 | bind(ElectronMenuUpdater).toSelf().inSingletonScope(); 16 | bind(MainMenuManager).toService(ElectronMenuUpdater); 17 | bind(ElectronContextMenuRenderer).toSelf().inSingletonScope(); 18 | rebind(ContextMenuRenderer).toService(ElectronContextMenuRenderer); 19 | rebind(TheiaElectronMenuContribution).toService(ElectronMenuContribution); 20 | bind(ElectronMainMenuFactory).toSelf().inSingletonScope(); 21 | rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory); 22 | }); 23 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/electron-browser/theia/core/electron-window-module.ts: -------------------------------------------------------------------------------- 1 | import { WindowService } from '@theia/core/lib/browser/window/window-service'; 2 | import { ContainerModule } from '@theia/core/shared/inversify'; 3 | import { WindowServiceExt } from '../../../browser/theia/core/window-service-ext'; 4 | import { ElectronWindowService } from './electron-window-service'; 5 | 6 | export default new ContainerModule((bind, unbind, isBound, rebind) => { 7 | bind(ElectronWindowService).toSelf().inSingletonScope(); 8 | rebind(WindowService).toService(ElectronWindowService); 9 | bind(WindowServiceExt).toService(ElectronWindowService); 10 | }); 11 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/electron-common/startup-task.ts: -------------------------------------------------------------------------------- 1 | export const StartupTaskProvider = Symbol('StartupTaskProvider'); 2 | export interface StartupTaskProvider { 3 | tasks(): StartupTask[]; 4 | } 5 | 6 | export interface StartupTask { 7 | readonly command: string; 8 | /** 9 | * Must be JSON serializable. 10 | * See the restrictions [here](https://www.electronjs.org/docs/latest/api/web-contents#contentssendchannel-args). 11 | */ 12 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 13 | readonly args?: any[]; 14 | } 15 | 16 | export interface StartupTasks { 17 | readonly tasks: StartupTask[]; 18 | } 19 | 20 | export function isStartupTask(arg: unknown): arg is StartupTask { 21 | if (typeof arg === 'object') { 22 | if ( 23 | (arg).command !== undefined && 24 | typeof (arg).command === 'string' 25 | ) { 26 | return ( 27 | (arg).args === undefined || 28 | ((arg).args !== undefined && 29 | Array.isArray((arg).args)) 30 | ); 31 | } 32 | } 33 | return false; 34 | } 35 | 36 | export function hasStartupTasks(arg: unknown): arg is unknown & StartupTasks { 37 | if (typeof arg === 'object') { 38 | return ( 39 | (arg).tasks !== undefined && 40 | Array.isArray((arg).tasks) && 41 | Boolean((arg).tasks.length) && 42 | (arg).tasks.every(isStartupTask) 43 | ); 44 | } 45 | return false; 46 | } 47 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/electron-main/fix-app-image-icon.ts: -------------------------------------------------------------------------------- 1 | import { environment } from '@theia/application-package/lib/environment'; 2 | import { isOSX, isWindows } from '@theia/core/lib/common/os'; 3 | import { 4 | ElectronMainApplication, 5 | ElectronMainApplicationContribution, 6 | } from '@theia/core/lib/electron-main/electron-main-application'; 7 | import { injectable } from '@theia/core/shared/inversify'; 8 | import { join } from 'node:path'; 9 | 10 | // Fixes no application icon for the AppImage on Linux (https://github.com/arduino/arduino-ide/issues/131) 11 | // The fix was based on https://github.com/eclipse-theia/theia-blueprint/pull/180. 12 | // Upstream: https://github.com/electron-userland/electron-builder/issues/4617 13 | @injectable() 14 | export class FixAppImageIcon implements ElectronMainApplicationContribution { 15 | onStart(application: ElectronMainApplication): void { 16 | if (isOSX || isWindows || environment.electron.isDevMode()) { 17 | return; 18 | } 19 | const windowOptions = application.config.electron.windowOptions; 20 | if (windowOptions && windowOptions.icon === undefined) { 21 | windowOptions.icon = join( 22 | __dirname, 23 | '..', 24 | '..', 25 | 'resources', 26 | 'icons', 27 | '512x512.png' 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/electron-main/theia/theia-api-main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CHANNEL_INVOKE_MENU, 3 | InternalMenuDto, 4 | } from '@theia/core/lib/electron-common/electron-api'; 5 | import { TheiaMainApi } from '@theia/core/lib/electron-main/electron-api-main'; 6 | import { injectable } from '@theia/core/shared/inversify'; 7 | import { MenuItemConstructorOptions } from '@theia/electron/shared/electron'; 8 | 9 | @injectable() 10 | export class TheiaMainApiFixFalsyHandlerId extends TheiaMainApi { 11 | override fromMenuDto( 12 | sender: Electron.WebContents, 13 | menuId: number, 14 | menuDto: InternalMenuDto[] 15 | ): Electron.MenuItemConstructorOptions[] { 16 | return menuDto.map((dto) => { 17 | const result: MenuItemConstructorOptions = { 18 | id: dto.id, 19 | label: dto.label, 20 | type: dto.type, 21 | checked: dto.checked, 22 | enabled: dto.enabled, 23 | visible: dto.visible, 24 | role: dto.role, 25 | accelerator: dto.accelerator, 26 | }; 27 | if (dto.submenu) { 28 | result.submenu = this.fromMenuDto(sender, menuId, dto.submenu); 29 | } 30 | // Fix for handlerId === 0 31 | // https://github.com/eclipse-theia/theia/pull/12500#issuecomment-1686074836 32 | if (typeof dto.handlerId === 'number') { 33 | result.click = () => { 34 | sender.send(CHANNEL_INVOKE_MENU, menuId, dto.handlerId); 35 | }; 36 | } 37 | return result; 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts: -------------------------------------------------------------------------------- 1 | import { StopReason } from '@theia/core/lib/common/frontend-application-state'; 2 | import { TheiaRendererAPI } from '@theia/core/lib/electron-main/electron-api-main'; 3 | import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window'; 4 | import { injectable } from '@theia/core/shared/inversify'; 5 | import { hasStartupTasks } from '../../electron-common/startup-task'; 6 | import { ElectronArduinoRenderer } from '../electron-arduino'; 7 | 8 | @injectable() 9 | export class TheiaElectronWindow extends DefaultTheiaElectronWindow { 10 | protected override reload(args?: unknown): void { 11 | this.handleStopRequest(async () => { 12 | this.applicationState = 'init'; 13 | if (hasStartupTasks(args)) { 14 | const { webContents } = this._window; 15 | const toDisposeOnReady = TheiaRendererAPI.onApplicationStateChanged( 16 | webContents, 17 | (state) => { 18 | if (state === 'ready') { 19 | ElectronArduinoRenderer.sendStartupTasks(webContents, args); 20 | toDisposeOnReady.dispose(); 21 | } 22 | } 23 | ); 24 | } 25 | this._window.reload(); 26 | }, StopReason.Reload); 27 | } 28 | 29 | protected override attachReloadListener(): void { 30 | this.toDispose.push( 31 | ElectronArduinoRenderer.onRequestReload( 32 | this.window.webContents, 33 | (args?: unknown) => this.reload(args) 34 | ) 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/arduino-core-service-client.ts: -------------------------------------------------------------------------------- 1 | import { credentials, makeClientConstructor } from '@grpc/grpc-js'; 2 | import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; 3 | import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; 4 | 5 | export interface CreateClientOptions { 6 | /** 7 | * The port to the Arduino CLI daemon. 8 | */ 9 | readonly port: number; 10 | /** 11 | * Defaults to `'localhost'`. 12 | */ 13 | readonly host?: string; 14 | 15 | /** 16 | * gRCP channel options. Defaults to `createDefaultChannelOptions` with `'0.0.0'` `appVersion` 17 | */ 18 | readonly channelOptions?: Record; 19 | } 20 | 21 | export function createDefaultChannelOptions( 22 | appVersion = '0.0.0' 23 | ): Record { 24 | return { 25 | 'grpc.max_send_message_length': 512 * 1024 * 1024, 26 | 'grpc.max_receive_message_length': 512 * 1024 * 1024, 27 | 'grpc.primary_user_agent': `arduino-ide/${appVersion}`, 28 | }; 29 | } 30 | 31 | export function createArduinoCoreServiceClient( 32 | options: CreateClientOptions 33 | ): ArduinoCoreServiceClient { 34 | const { 35 | port, 36 | host = 'localhost', 37 | channelOptions = createDefaultChannelOptions(), 38 | } = options; 39 | const address = `${host}:${port}`; 40 | // https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage 41 | const ArduinoCoreServiceClient = makeClientConstructor( 42 | // @ts-expect-error: ignore 43 | commandsGrpcPb['cc.arduino.cli.commands.v1.ArduinoCoreService'], 44 | 'ArduinoCoreServiceService' 45 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 46 | ) as any; 47 | const client = new ArduinoCoreServiceClient( 48 | address, 49 | credentials.createInsecure(), 50 | channelOptions 51 | ) as ArduinoCoreServiceClient; 52 | return client; 53 | } 54 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/auth/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthOptions, 3 | AuthenticationSession, 4 | } from '../../common/protocol/authentication-service'; 5 | export { AuthenticationSession }; 6 | 7 | export interface AuthenticationProviderAuthenticationSessionsChangeEvent { 8 | readonly added?: ReadonlyArray; 9 | readonly removed?: ReadonlyArray; 10 | readonly changed?: ReadonlyArray; 11 | } 12 | 13 | export interface AuthenticationProvider { 14 | readonly onDidChangeSessions: any; // Event; 15 | getSessions(): Promise>; 16 | createSession(): Promise; 17 | removeSession(sessionId: string): Promise; 18 | setOptions(authOptions: AuthOptions): void; 19 | } 20 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/board_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/debug_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: cc.arduino.cli.commands.v1 2 | // file: cc/arduino/cli/commands/v1/port.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | 9 | export class Port extends jspb.Message { 10 | getAddress(): string; 11 | setAddress(value: string): Port; 12 | getLabel(): string; 13 | setLabel(value: string): Port; 14 | getProtocol(): string; 15 | setProtocol(value: string): Port; 16 | getProtocolLabel(): string; 17 | setProtocolLabel(value: string): Port; 18 | 19 | getPropertiesMap(): jspb.Map; 20 | clearPropertiesMap(): void; 21 | getHardwareId(): string; 22 | setHardwareId(value: string): Port; 23 | 24 | serializeBinary(): Uint8Array; 25 | toObject(includeInstance?: boolean): Port.AsObject; 26 | static toObject(includeInstance: boolean, msg: Port): Port.AsObject; 27 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 28 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 29 | static serializeBinaryToWriter(message: Port, writer: jspb.BinaryWriter): void; 30 | static deserializeBinary(bytes: Uint8Array): Port; 31 | static deserializeBinaryFromReader(message: Port, reader: jspb.BinaryReader): Port; 32 | } 33 | 34 | export namespace Port { 35 | export type AsObject = { 36 | address: string, 37 | label: string, 38 | protocol: string, 39 | protocolLabel: string, 40 | 41 | propertiesMap: Array<[string, string]>, 42 | hardwareId: string, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/settings_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/google/rpc/status_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- NO SERVICES IN PROTO -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/cli-protocol/google/rpc/status_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: google.rpc 2 | // file: google/rpc/status.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | import * as google_protobuf_any_pb from "google-protobuf/google/protobuf/any_pb"; 9 | 10 | export class Status extends jspb.Message { 11 | getCode(): number; 12 | setCode(value: number): Status; 13 | getMessage(): string; 14 | setMessage(value: string): Status; 15 | clearDetailsList(): void; 16 | getDetailsList(): Array; 17 | setDetailsList(value: Array): Status; 18 | addDetails(value?: google_protobuf_any_pb.Any, index?: number): google_protobuf_any_pb.Any; 19 | 20 | serializeBinary(): Uint8Array; 21 | toObject(includeInstance?: boolean): Status.AsObject; 22 | static toObject(includeInstance: boolean, msg: Status): Status.AsObject; 23 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 24 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 25 | static serializeBinaryToWriter(message: Status, writer: jspb.BinaryWriter): void; 26 | static deserializeBinary(bytes: Uint8Array): Status; 27 | static deserializeBinaryFromReader(message: Status, reader: jspb.BinaryReader): Status; 28 | } 29 | 30 | export namespace Status { 31 | export type AsObject = { 32 | code: number, 33 | message: string, 34 | detailsList: Array, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/exec-util.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'node:child_process'; 2 | 3 | export function spawnCommand( 4 | command: string, 5 | args: string[], 6 | onError: (error: Error) => void = (error) => console.log(error), 7 | stdIn?: string 8 | ): Promise { 9 | return new Promise((resolve, reject) => { 10 | const cp = spawn(command, args, { windowsHide: true }); 11 | const outBuffers: Buffer[] = []; 12 | const errBuffers: Buffer[] = []; 13 | cp.stdout.on('data', (b: Buffer) => outBuffers.push(b)); 14 | cp.stderr.on('data', (b: Buffer) => errBuffers.push(b)); 15 | cp.on('error', (error) => { 16 | onError(error); 17 | reject(error); 18 | }); 19 | cp.on('exit', (code, signal) => { 20 | if (code === 0) { 21 | const result = Buffer.concat(outBuffers).toString('utf8'); 22 | resolve(result); 23 | return; 24 | } 25 | if (errBuffers.length > 0) { 26 | const message = Buffer.concat(errBuffers).toString('utf8').trim(); 27 | const error = new Error( 28 | `Error executing ${command} ${args.join(' ')}: ${message}` 29 | ); 30 | onError(error); 31 | reject(error); 32 | return; 33 | } 34 | if (signal) { 35 | const error = new Error(`Process exited with signal: ${signal}`); 36 | onError(error); 37 | reject(error); 38 | return; 39 | } 40 | if (code) { 41 | const error = new Error(`Process exited with exit code: ${code}`); 42 | onError(error); 43 | reject(error); 44 | return; 45 | } 46 | }); 47 | if (stdIn !== undefined) { 48 | cp.stdin.write(stdIn); 49 | cp.stdin.end(); 50 | } 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/executable-service-impl.ts: -------------------------------------------------------------------------------- 1 | import { FileUri } from '@theia/core/lib/common/file-uri'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | import { ExecutableService } from '../common/protocol/executable-service'; 4 | import { 5 | arduinoCliPath, 6 | arduinoLanguageServerPath, 7 | clangdPath, 8 | } from './resources'; 9 | 10 | @injectable() 11 | export class ExecutableServiceImpl implements ExecutableService { 12 | async list(): Promise<{ 13 | clangdUri: string; 14 | cliUri: string; 15 | lsUri: string; 16 | }> { 17 | return { 18 | clangdUri: FileUri.create(clangdPath).toString(), 19 | cliUri: FileUri.create(arduinoCliPath).toString(), 20 | lsUri: FileUri.create(arduinoLanguageServerPath).toString(), 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/monitor-service-factory.ts: -------------------------------------------------------------------------------- 1 | import type { Board, Port } from '../common/protocol'; 2 | import type { MonitorService } from './monitor-service'; 3 | 4 | export const MonitorServiceFactory = Symbol('MonitorServiceFactory'); 5 | export interface MonitorServiceFactory { 6 | (options: MonitorServiceFactoryOptions): MonitorService; 7 | } 8 | 9 | export const MonitorServiceFactoryOptions = Symbol( 10 | 'MonitorServiceFactoryOptions' 11 | ); 12 | export interface MonitorServiceFactoryOptions { 13 | board: Board; 14 | port: Port; 15 | monitorID: string; 16 | } 17 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts: -------------------------------------------------------------------------------- 1 | import { PluggableMonitorSettings } from '../../common/protocol'; 2 | 3 | export const MonitorSettingsProvider = Symbol('MonitorSettingsProvider'); 4 | export interface MonitorSettingsProvider { 5 | getSettings( 6 | monitorId: string, 7 | defaultSettings: PluggableMonitorSettings 8 | ): Promise; 9 | setSettings( 10 | monitorId: string, 11 | settings: PluggableMonitorSettings 12 | ): Promise; 13 | } 14 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/node-filesystem-ext.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from '@theia/core/shared/inversify'; 2 | import { FileUri } from '@theia/core/lib/common/file-uri'; 3 | import { FileSystemExt } from '../common/protocol/filesystem-ext'; 4 | 5 | @injectable() 6 | export class NodeFileSystemExt implements FileSystemExt { 7 | async getUri(fsPath: string): Promise { 8 | return FileUri.create(fsPath).toString(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/plotter/plotter-backend-contribution.ts: -------------------------------------------------------------------------------- 1 | import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; 2 | import express from '@theia/core/shared/express'; 3 | import { injectable } from '@theia/core/shared/inversify'; 4 | import path from 'node:path'; 5 | import { arduinoPlotterWebAppPath } from '../resources'; 6 | 7 | @injectable() 8 | export class PlotterBackendContribution 9 | implements BackendApplicationContribution 10 | { 11 | configure(app: express.Application): void { 12 | app.use(express.static(arduinoPlotterWebAppPath)); 13 | app.get('/plotter', (req, res) => { 14 | console.log( 15 | `Serving serial plotter on http://${req.headers.host}${req.url}` 16 | ); 17 | res.sendFile(path.join(arduinoPlotterWebAppPath, 'index.html')); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/resources.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | // When running the tests, the JS files are not yet bundled by webpack. 4 | // Hence, the `resources` folder lookup is different. 5 | const testEnv = process.env.IDE2_TEST === 'true'; 6 | const resourcesPath = path.join( 7 | __dirname, 8 | ...(testEnv ? ['..', '..', 'src', 'node', 'resources'] : ['resources']) 9 | ); 10 | const exe = process.platform === 'win32' ? '.exe' : ''; 11 | 12 | // binaries 13 | export const arduinoCliPath = path.join(resourcesPath, 'arduino-cli' + exe); 14 | export const arduinoFirmwareUploaderPath = path.join( 15 | resourcesPath, 16 | 'arduino-fwuploader' + exe 17 | ); 18 | export const arduinoLanguageServerPath = path.join( 19 | resourcesPath, 20 | 'arduino-language-server' + exe 21 | ); 22 | export const clangdPath = path.join(resourcesPath, 'clangd' + exe); 23 | export const clangFormatPath = path.join(resourcesPath, 'clang-format' + exe); 24 | 25 | // plotter 26 | export const arduinoPlotterWebAppPath = path.join( 27 | resourcesPath, 28 | 'arduino-serial-plotter-webapp' 29 | ); 30 | 31 | // examples 32 | export const examplesPath = path.join(resourcesPath, 'Examples'); 33 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/theia/core/backend-application.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable, named } from '@theia/core/shared/inversify'; 2 | import { ContributionProvider } from '@theia/core/lib/common/contribution-provider'; 3 | import { 4 | BackendApplication as TheiaBackendApplication, 5 | BackendApplicationContribution, 6 | BackendApplicationCliContribution, 7 | } from '@theia/core/lib/node/backend-application'; 8 | 9 | @injectable() 10 | export class BackendApplication extends TheiaBackendApplication { 11 | constructor( 12 | @inject(ContributionProvider) 13 | @named(BackendApplicationContribution) 14 | protected override readonly contributionsProvider: ContributionProvider, 15 | @inject(BackendApplicationCliContribution) 16 | protected override readonly cliParams: BackendApplicationCliContribution 17 | ) { 18 | super(contributionsProvider, cliParams); 19 | // Workaround for Electron not installing a handler to ignore SIGPIPE 20 | // (https://github.com/electron/electron/issues/13254) 21 | // From VS Code: https://github.com/microsoft/vscode/blob/d0c90c9f3ea8d34912194176241503a44b3abd80/src/bootstrap.js#L31-L37 22 | process.on('SIGPIPE', () => 23 | console.error(new Error('Unexpected SIGPIPE signal.')) 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/theia/core/websocket-endpoint.ts: -------------------------------------------------------------------------------- 1 | import { WebsocketEndpoint as TheiaWebsocketEndpoint } from '@theia/core/lib/node/messaging/websocket-endpoint'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | 4 | @injectable() 5 | export class WebsocketEndpoint extends TheiaWebsocketEndpoint { 6 | // https://github.com/eclipse-theia/theia/discussions/11543 7 | protected override checkAliveTimeout = process.argv.includes( 8 | '--no-ping-timeout' 9 | ) 10 | ? 24 * 60 * 60 * 1_000 // one day 11 | : 30_000; 12 | } 13 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/theia/filesystem/parcel-bindings.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | import { interfaces } from '@theia/core/shared/inversify'; 3 | import { 4 | FileSystemWatcherServiceProcessOptions, 5 | WATCHER_SINGLE_THREADED, 6 | spawnParcelFileSystemWatcherServiceProcess, 7 | } from '@theia/filesystem/lib/node/filesystem-backend-module'; 8 | import { FileSystemWatcherService } from '@theia/filesystem/lib/common/filesystem-watcher-protocol'; 9 | import { ParcelFileSystemWatcherServerOptions } from '@theia/filesystem/lib/node/parcel-watcher/parcel-filesystem-service'; 10 | import { FileSystemWatcherServiceDispatcher } from '@theia/filesystem/lib/node/filesystem-watcher-dispatcher'; 11 | import { NoDelayDisposalTimeoutParcelFileSystemWatcherService } from './parcel-watcher/parcel-filesystem-service'; 12 | 13 | export function rebindParcelFileSystemWatcher(rebind: interfaces.Rebind): void { 14 | rebind( 15 | FileSystemWatcherServiceProcessOptions 16 | ).toConstantValue({ 17 | entryPoint: join(__dirname, 'parcel-watcher'), 18 | }); 19 | rebind(FileSystemWatcherService) 20 | .toDynamicValue((context) => 21 | WATCHER_SINGLE_THREADED 22 | ? createParcelFileSystemWatcherService(context) 23 | : spawnParcelFileSystemWatcherServiceProcess(context) 24 | ) 25 | .inSingletonScope(); 26 | } 27 | 28 | function createParcelFileSystemWatcherService({ 29 | container, 30 | }: interfaces.Context): FileSystemWatcherService { 31 | const options = container.get( 32 | ParcelFileSystemWatcherServerOptions 33 | ); 34 | const dispatcher = container.get( 35 | FileSystemWatcherServiceDispatcher 36 | ); 37 | const server = new NoDelayDisposalTimeoutParcelFileSystemWatcherService( 38 | options 39 | ); 40 | server.setClient(dispatcher); 41 | return server; 42 | } 43 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/theia/filesystem/parcel-watcher/index.ts: -------------------------------------------------------------------------------- 1 | import * as yargs from '@theia/core/shared/yargs'; 2 | import { JsonRpcProxyFactory } from '@theia/core/lib/common/messaging/proxy-factory'; 3 | import { NoDelayDisposalTimeoutParcelFileSystemWatcherService } from './parcel-filesystem-service'; 4 | import type { IPCEntryPoint } from '@theia/core/lib/node/messaging/ipc-protocol'; 5 | import type { FileSystemWatcherServiceClient } from '@theia/filesystem/lib/common/filesystem-watcher-protocol'; 6 | 7 | const options: { 8 | verbose: boolean; 9 | } = yargs 10 | .option('verbose', { 11 | default: false, 12 | alias: 'v', 13 | type: 'boolean', 14 | }) 15 | .option('nsfwOptions', { 16 | alias: 'o', 17 | type: 'string', 18 | coerce: JSON.parse, 19 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 20 | }).argv as any; 21 | 22 | export default ((connection) => { 23 | const server = new NoDelayDisposalTimeoutParcelFileSystemWatcherService( 24 | options 25 | ); 26 | const factory = new JsonRpcProxyFactory( 27 | server 28 | ); 29 | server.setClient(factory.createProxy()); 30 | factory.listen(connection); 31 | }); 32 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/theia/filesystem/parcel-watcher/parcel-filesystem-service.ts: -------------------------------------------------------------------------------- 1 | import { Minimatch } from 'minimatch'; 2 | import type { WatchOptions } from '@theia/filesystem/lib/common/filesystem-watcher-protocol'; 3 | import { 4 | ParcelFileSystemWatcherService, 5 | ParcelWatcher, 6 | } from '@theia/filesystem/lib/node/parcel-watcher/parcel-filesystem-service'; 7 | 8 | // Dispose the watcher immediately when the last reference is removed. By default, Theia waits 10 sec. 9 | // https://github.com/eclipse-theia/theia/issues/11639#issuecomment-1238980708 10 | const NoDelay = 0; 11 | 12 | export class NoDelayDisposalTimeoutParcelFileSystemWatcherService extends ParcelFileSystemWatcherService { 13 | protected override createWatcher( 14 | clientId: number, 15 | fsPath: string, 16 | options: WatchOptions 17 | ): ParcelWatcher { 18 | const watcherOptions = { 19 | ignored: options.ignored.map( 20 | (pattern) => new Minimatch(pattern, { dot: true }) 21 | ), 22 | }; 23 | return new ParcelWatcher( 24 | clientId, 25 | fsPath, 26 | watcherOptions, 27 | this.options, 28 | this.maybeClient, 29 | NoDelay 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/theia/plugin-ext-vscode/scanner-vscode.ts: -------------------------------------------------------------------------------- 1 | import { injectable, postConstruct } from '@theia/core/shared/inversify'; 2 | import { VsCodePluginScanner as TheiaVsCodePluginScanner } from '@theia/plugin-ext-vscode/lib/node/scanner-vscode'; 3 | import { 4 | PluginPackageViewWelcome, 5 | ViewWelcome, 6 | } from '@theia/plugin-ext/lib/common/plugin-protocol'; 7 | 8 | @injectable() 9 | export class VsCodePluginScanner extends TheiaVsCodePluginScanner { 10 | @postConstruct() 11 | protected init(): void { 12 | this['readViewWelcome'] = ( 13 | rawViewWelcome: PluginPackageViewWelcome, 14 | pluginViewsIds: string[] 15 | ) => { 16 | const result = { 17 | view: rawViewWelcome.view, 18 | content: rawViewWelcome.contents, 19 | when: rawViewWelcome.when, 20 | // if the plugin contributes Welcome view to its own view - it will be ordered first 21 | order: 22 | pluginViewsIds.findIndex((v) => v === rawViewWelcome.view) > -1 23 | ? 0 24 | : 1, 25 | }; 26 | return maybeSetEnablement(rawViewWelcome, result); 27 | }; 28 | } 29 | } 30 | 31 | // This is not yet supported by Theia but available in Code (https://github.com/microsoft/vscode/issues/114304) 32 | function maybeSetEnablement( 33 | rawViewWelcome: PluginPackageViewWelcome, 34 | result: ViewWelcome 35 | ) { 36 | const enablement = 37 | 'enablement' in rawViewWelcome && 38 | typeof rawViewWelcome['enablement'] === 'string' && 39 | rawViewWelcome['enablement']; 40 | if (enablement) { 41 | Object.assign(result, { enablement }); 42 | } 43 | return result; 44 | } 45 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/theia/plugin-ext/hosted-plugin-localization-service.ts: -------------------------------------------------------------------------------- 1 | import * as fs from '@theia/core/shared/fs-extra'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | import { HostedPluginLocalizationService as TheiaHostedPluginLocalizationService } from '@theia/plugin-ext/lib/hosted/node/hosted-plugin-localization-service'; 4 | 5 | @injectable() 6 | export class HostedPluginLocalizationService extends TheiaHostedPluginLocalizationService { 7 | // Remove when https://github.com/eclipse-theia/theia/pull/11853 is available from Theia. 8 | override async initialize(): Promise { 9 | this.getLocalizationCacheDir() 10 | .then((cacheDir) => fs.emptyDir(cacheDir)) 11 | .then(() => this._ready.resolve()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts: -------------------------------------------------------------------------------- 1 | import { Emitter } from '@theia/core'; 2 | import { injectable } from '@theia/core/shared/inversify'; 3 | import WebSocket from '@theia/core/shared/ws'; 4 | import { WebSocketProvider } from './web-socket-provider'; 5 | 6 | @injectable() 7 | export default class WebSocketProviderImpl implements WebSocketProvider { 8 | protected wsClients: WebSocket[]; 9 | protected server: WebSocket.Server; 10 | 11 | protected readonly onMessage = new Emitter(); 12 | public readonly onMessageReceived = this.onMessage.event; 13 | 14 | protected readonly onConnectedClients = new Emitter(); 15 | public readonly onClientsNumberChanged = this.onConnectedClients.event; 16 | 17 | constructor() { 18 | this.wsClients = []; 19 | this.server = new WebSocket.Server({ port: 0 }); 20 | 21 | const addClient = this.addClient.bind(this); 22 | this.server.on('connection', addClient); 23 | } 24 | 25 | private addClient(ws: WebSocket): void { 26 | this.wsClients.push(ws); 27 | this.onConnectedClients.fire(this.wsClients.length); 28 | 29 | ws.onclose = () => { 30 | this.wsClients.splice(this.wsClients.indexOf(ws), 1); 31 | this.onConnectedClients.fire(this.wsClients.length); 32 | }; 33 | 34 | ws.onmessage = (res) => { 35 | this.onMessage.fire(res.data.toString()); 36 | }; 37 | } 38 | 39 | getConnectedClientsNumber(): number { 40 | return this.wsClients.length; 41 | } 42 | 43 | getAddress(): WebSocket.AddressInfo { 44 | return this.server.address() as WebSocket.AddressInfo; 45 | } 46 | 47 | sendMessage(message: string): void { 48 | this.wsClients.forEach((w) => { 49 | try { 50 | w.send(message); 51 | } catch { 52 | w.close(); 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/node/web-socket/web-socket-provider.ts: -------------------------------------------------------------------------------- 1 | import { Event } from '@theia/core/lib/common/event'; 2 | import type { AddressInfo } from '@theia/core/shared/ws'; 3 | 4 | export const WebSocketProvider = Symbol('WebSocketProvider'); 5 | export interface WebSocketProvider { 6 | getAddress(): AddressInfo; 7 | sendMessage(message: string): void; 8 | onMessageReceived: Event; 9 | onClientsNumberChanged: Event; 10 | getConnectedClientsNumber(): number; 11 | } 12 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/browser/dom.test.ts: -------------------------------------------------------------------------------- 1 | import { splitByBoldTag } from '../../browser/utils/dom'; 2 | import { expect } from 'chai'; 3 | 4 | describe('dom', () => { 5 | describe('splitByBoldTag', () => { 6 | it('should split by bold tags', () => { 7 | const actual = splitByBoldTag('onematchOnetwo'); 8 | const expected = ['one', { textContent: 'matchOne', bold: true }, 'two']; 9 | expect(actual).to.be.deep.equal(expected); 10 | }); 11 | 12 | it('should handle starting bold tags', () => { 13 | const actual = splitByBoldTag( 14 | 'matchOneonematchTwo two matchThree three' 15 | ); 16 | const expected = [ 17 | { textContent: 'matchOne', bold: true }, 18 | 'one', 19 | { textContent: 'matchTwo', bold: true }, 20 | ' two ', 21 | { textContent: 'matchThree', bold: true }, 22 | ' three', 23 | ]; 24 | expect(actual).to.be.deep.equal(expected); 25 | }); 26 | 27 | it('should handle unclosed bold tags', () => { 28 | const actual = splitByBoldTag( 29 | 'matchOneonematchTwo two matchThree three ' 30 | ); 31 | const expected = [ 32 | { textContent: 'matchOne', bold: true }, 33 | 'one', 34 | { textContent: 'matchTwo', bold: true }, 35 | ' two ', 36 | { textContent: 'matchThree', bold: true }, 37 | ' three ', 38 | ]; 39 | expect(actual).to.be.deep.equal(expected); 40 | }); 41 | 42 | it('should handle no matches', () => { 43 | const actual = splitByBoldTag('alma'); 44 | expect(actual).to.be.undefined; 45 | }); 46 | 47 | it('should handle empty strings', () => { 48 | const actual = splitByBoldTag(''); 49 | expect(actual).to.be.undefined; 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/browser/fixtures/boards.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Board, 3 | BoardsConfig, 4 | BoardsPackage, 5 | Port, 6 | } from '../../../common/protocol'; 7 | 8 | export const aBoard: Board = { 9 | fqbn: 'some:board:fqbn', 10 | name: 'Some Arduino Board', 11 | }; 12 | const aPort: Port = { 13 | address: '/lol/port1234', 14 | addressLabel: '/lol/port1234', 15 | protocol: 'serial', 16 | protocolLabel: 'Serial Port (USB)', 17 | }; 18 | export const aBoardsConfig: BoardsConfig = { 19 | selectedBoard: { name: aBoard.name, fqbn: aBoard.fqbn }, 20 | selectedPort: aPort, 21 | }; 22 | export const anotherBoard: Board = { 23 | fqbn: 'another:board:fqbn', 24 | name: 'Another Arduino Board', 25 | }; 26 | export const anotherPort: Port = { 27 | address: '/kek/port5678', 28 | addressLabel: '/kek/port5678', 29 | protocol: 'serial', 30 | protocolLabel: 'Serial Port (USB)', 31 | }; 32 | export const anotherBoardsConfig: BoardsConfig = { 33 | selectedBoard: { name: anotherBoard.name, fqbn: anotherBoard.fqbn }, 34 | selectedPort: anotherPort, 35 | }; 36 | 37 | export const aPackage: BoardsPackage = { 38 | author: 'someAuthor', 39 | availableVersions: ['some.ver.sion', 'some.other.version'], 40 | boards: [aBoard], 41 | deprecated: false, 42 | description: 'Some Arduino Board, Some Other Arduino Board', 43 | id: 'some:arduinoCoreId', 44 | moreInfoLink: 'http://www.some-url.lol/', 45 | name: 'Some Arduino Package', 46 | summary: 'Boards included in this package:', 47 | types: ['Arduino'], 48 | }; 49 | 50 | export const anInstalledPackage: BoardsPackage = { 51 | ...aPackage, 52 | installedVersion: 'some.ver.sion', 53 | }; 54 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/common/arduino-context-mapper.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { toApiBuildProperties } from '../../common/protocol/arduino-context-mapper'; 3 | 4 | describe('arduino-context-mapper', () => { 5 | describe('toApiBuildProperties', () => { 6 | it('should parse an array of build properties string into a record', () => { 7 | const expected = { 8 | foo: 'alma', 9 | bar: '36', 10 | baz: 'false', 11 | }; 12 | const actual = toApiBuildProperties(['foo=alma', 'bar=36', 'baz=false']); 13 | expect(actual).to.be.deep.equal(expected); 14 | }); 15 | 16 | it('should not skip build property key with empty value', () => { 17 | const expected = { 18 | foo: '', 19 | }; 20 | const actual = toApiBuildProperties(['foo=']); 21 | expect(actual).to.be.deep.equal(expected); 22 | }); 23 | 24 | it('should skip invalid entries', () => { 25 | const expected = { 26 | foo: 'alma', 27 | bar: '36', 28 | baz: '-DARDUINO_USB_CDC_ON_BOOT=0', 29 | }; 30 | const actual = toApiBuildProperties([ 31 | 'foo=alma', 32 | 'invalid', 33 | '=invalid2', 34 | '=invalid3=', 35 | '=', 36 | '==', 37 | 'bar=36', 38 | 'baz=-DARDUINO_USB_CDC_ON_BOOT=0', 39 | ]); 40 | expect(actual).to.be.deep.equal(expected); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/common/config-service.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { AdditionalUrls } from '../../common/protocol'; 3 | 4 | describe('config-service', () => { 5 | describe('additionalUrls', () => { 6 | it('should consider additional URLs same as if they differ in case', () => { 7 | expect(AdditionalUrls.sameAs(['aaaa'], ['AAAA'])).to.be.true; 8 | }); 9 | it('should consider additional URLs same as if they have a different order', () => { 10 | expect(AdditionalUrls.sameAs(['bbbb', 'aaaa'], ['aaaa', 'bbbb'])).to.be 11 | .true; 12 | }); 13 | it('should parse an empty string as an empty array', () => { 14 | expect(AdditionalUrls.parse('', ',')).to.be.empty; 15 | }); 16 | it('should parse a blank string as an empty array', () => { 17 | expect(AdditionalUrls.parse(' ', ',')).to.be.empty; 18 | }); 19 | it('should parse urls with commas', () => { 20 | expect(AdditionalUrls.parse(' ,a , b , c, ', ',')).to.be.deep.equal([ 21 | 'a', 22 | 'b', 23 | 'c', 24 | ]); 25 | }); 26 | it("should parse urls with both '\\n' and '\\r\\n' line endings", () => { 27 | expect( 28 | AdditionalUrls.parse( 29 | 'a ' + '\r\n' + ' b ' + '\n' + ' c ' + '\r\n' + ' ' + '\n' + '', 30 | 'newline' 31 | ) 32 | ).to.be.deep.equal(['a', 'b', 'c']); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/a_sketch/a_sketch.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/a_sketch/a_sketch.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/bar++ 2/bar++ 2.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/bar++ 2/bar++ 2.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/bar++/bar++.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/bar++/bar++.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/bar++/foo++/foo++.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/bar++/foo++/foo++.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/defaultIno.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/defaultIno.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/empty/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/empty/.gitkeep -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/libraries/my_library/examples/Ethernet/Example_1/Example_1.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/libraries/my_library/examples/Ethernet/Example_1/Example_1.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/libraries/my_library/examples/WiFi/Example_2/Example_2.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/libraries/my_library/examples/WiFi/Example_2/Example_2.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/mismatchingName/MismatchingName.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/mismatchingName/MismatchingName.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/nested_4/nested_3/nested_2/nested_1/nested_1.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/nested_4/nested_3/nested_2/nested_1/nested_1.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/nested_4/nested_3/nested_2/nested_2.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/nested_4/nested_3/nested_2/nested_2.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/project1/CodeA/version1A/version1A.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/project1/CodeA/version1A/version1A.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/project1/CodeA/version2A/version2A.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/project1/CodeA/version2A/version2A.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/project1/CodeB/version1B/version1B.ino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/project1/CodeB/version1B/version1B.ino -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/__test_sketchbook__/project1/CodeB/version2B/version2B.pde: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/arduino-ide-extension/src/test/node/__test_sketchbook__/project1/CodeB/version2B/version2B.pde -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/cli-config.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { DefaultCliConfig } from '../../node/cli-config'; 3 | 4 | describe('cli-config', () => { 5 | type ConfigProvider = DefaultCliConfig | { (): DefaultCliConfig }; 6 | 7 | ( 8 | [ 9 | [defaultConfig, defaultConfig, true], 10 | [ 11 | () => { 12 | const conf = defaultConfig(); 13 | delete conf.board_manager; 14 | return conf; 15 | }, 16 | defaultConfig, 17 | true, 18 | ], 19 | [ 20 | () => { 21 | const conf = defaultConfig(); 22 | return conf; 23 | }, 24 | defaultConfig, 25 | true, 26 | ], 27 | ] as [ConfigProvider, ConfigProvider, boolean][] 28 | ).forEach(([leftInput, rightInput, expectation]) => { 29 | const left = typeof leftInput === 'function' ? leftInput() : leftInput; 30 | const right = typeof rightInput === 'function' ? rightInput() : rightInput; 31 | it(`${JSON.stringify(left)} should ${ 32 | expectation ? '' : 'not ' 33 | }be the same as ${JSON.stringify(right)}`, () => { 34 | expect(DefaultCliConfig.sameAs(left, right)).to.be.equal(expectation); 35 | }); 36 | }); 37 | 38 | function defaultConfig(): DefaultCliConfig { 39 | return { 40 | board_manager: { 41 | additional_urls: [], 42 | }, 43 | directories: { 44 | data: 'data', 45 | user: 'user', 46 | }, 47 | }; 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/node/settings.reader.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { parse } from '../../node/settings-reader'; 3 | 4 | describe('settings-reader', () => { 5 | describe('parse', () => { 6 | it('should handle comments', () => { 7 | const actual = parse(` 8 | { 9 | "alma": "korte", 10 | // comment 11 | "szilva": false 12 | }`); 13 | expect(actual).to.be.deep.equal({ 14 | alma: 'korte', 15 | szilva: false, 16 | }); 17 | }); 18 | 19 | it('should handle trailing comma', () => { 20 | const actual = parse(` 21 | { 22 | "alma": "korte", 23 | "szilva": 123, 24 | }`); 25 | expect(actual).to.be.deep.equal({ 26 | alma: 'korte', 27 | szilva: 123, 28 | }); 29 | }); 30 | 31 | it('should parse empty', () => { 32 | const actual = parse(''); 33 | expect(actual).to.be.deep.equal({}); 34 | }); 35 | 36 | it('should parse to undefined when parse has failed', () => { 37 | const actual = parse(` 38 | { 39 | alma:: 'korte' 40 | trash 41 | }`); 42 | expect(actual).to.be.undefined; 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /arduino-ide-extension/src/test/utils.ts: -------------------------------------------------------------------------------- 1 | import { Emitter, Event } from '@theia/core/lib/common/event'; 2 | 3 | const neverEmitter = new Emitter(); 4 | export function never(): Event { 5 | return neverEmitter.event as Event; 6 | } 7 | -------------------------------------------------------------------------------- /arduino-ide-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "noEmitOnError": true, 5 | "noImplicitThis": true, 6 | "noUnusedLocals": true, 7 | "noImplicitOverride": true, 8 | "strictNullChecks": true, 9 | "experimentalDecorators": true, 10 | "downlevelIteration": true, 11 | "emitDecoratorMetadata": true, 12 | "module": "Node16", 13 | "moduleResolution": "node16", 14 | "resolveJsonModule": true, 15 | "target": "ES2017", 16 | "outDir": "lib", 17 | "lib": ["ES2017", "dom"], 18 | "jsx": "react", 19 | "sourceMap": true, 20 | "skipLibCheck": true 21 | }, 22 | "include": ["src"], 23 | } 24 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Contributor Guide 4 | 5 | Thanks for your interest in contributing to this project! 6 | 7 | There are several ways you can get involved: 8 | 9 | | Type of contribution | Contribution method | 10 | | ----------------------------------------- | -------------------------------------------------------------------------------- | 11 | | - Support
- Question
- Discussion | Post on the [**Arduino Forum**][forum] | 12 | | - Bug report
- Feature request | Issue report (see the guide [**here**][issues]) | 13 | | Testing | Beta testing, PR review (see the guide [**here**][beta-testing]) | 14 | | Translation | See the guide [**here**][translate] | 15 | | - Bug fix
- Enhancement | Pull request (see the guide [**here**][prs]) | 16 | | Monetary | - [Donate][donate]
- [Sponsor][sponsor]
- [Buy official products][store] | 17 | 18 | [forum]: https://forum.arduino.cc 19 | [issues]: contributor-guide/issues.md#issue-report-guide 20 | [beta-testing]: contributor-guide/beta-testing.md#beta-testing-guide 21 | [translate]: contributor-guide/translation.md#translator-guide 22 | [prs]: contributor-guide/pull-requests.md#pull-request-guide 23 | [donate]: https://www.arduino.cc/en/donate/ 24 | [sponsor]: https://github.com/sponsors/arduino 25 | [store]: https://store.arduino.cc 26 | 27 | ## Resources 28 | 29 | - [**Development Guide**](development.md#development-guide) 30 | -------------------------------------------------------------------------------- /docs/assets/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/docs/assets/preferences.png -------------------------------------------------------------------------------- /docs/assets/remote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/docs/assets/remote.png -------------------------------------------------------------------------------- /docs/contributor-guide/assets/checks-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/docs/contributor-guide/assets/checks-tab.png -------------------------------------------------------------------------------- /docs/contributor-guide/assets/checks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/docs/contributor-guide/assets/checks.png -------------------------------------------------------------------------------- /docs/contributor-guide/assets/tester-build-artifacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/docs/contributor-guide/assets/tester-build-artifacts.png -------------------------------------------------------------------------------- /docs/contributor-guide/assets/tester-build-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/docs/contributor-guide/assets/tester-build-link.png -------------------------------------------------------------------------------- /docs/contributor-guide/translation.md: -------------------------------------------------------------------------------- 1 | # Translator Guide 2 | 3 | The text of the Arduino IDE interface is translated into several languages. The language can be selected in the dialog opened via **File > Preferences** in the Arduino IDE menus (**Arduino IDE > Preferences** for macOS users). 4 | 5 | Translating text and improving on existing translations is a valuable contribution to the project, helping make Arduino accessible to everyone. 6 | 7 | The translations for the text found in the Arduino IDE come from several sources: 8 | 9 | ## Arduino IDE Text 10 | 11 | Translations of Arduino IDE's text is done in the "**Arduino IDE 2.0**" project on the **Transifex** localization platform: 12 | 13 | https://explore.transifex.com/arduino-1/ide2/ 14 | 15 | ## Base Application Text 16 | 17 | Arduino IDE leverages the localization data available for the [**VS Code**](https://code.visualstudio.com/) editor to localize shared UI text. This reduces the translation work required to add a new language to the text specific to the Arduino IDE project. 18 | 19 | For this reason, some of Arduino IDE's text is not found in the **Transifex** project. Suggestions for corrections or improvement to this text are made by submitting an issue to the `microsoft/vscode-loc` GitHub repository. 20 | 21 | Before submitting an issue, please check the existing issues to make sure it wasn't already reported:
22 | https://github.com/microsoft/vscode-loc/issues 23 | 24 | After that, submit an issue here:
25 | https://github.com/microsoft/vscode-loc/issues/new 26 | 27 | ## Arduino CLI Text 28 | 29 | The [**Arduino CLI**](https://arduino.github.io/arduino-cli/latest/) tool handles non-GUI operations for the Arduino IDE. Some of the text printed in the "**Output**" panel and in notifications originates from **Arduino CLI**. 30 | 31 | Translations of Arduino CLI's text is done in the "**Arduino CLI**" Transifex project: 32 | 33 | https://explore.transifex.com/arduino-1/arduino-cli/ 34 | -------------------------------------------------------------------------------- /docs/internal/Ubuntu.md: -------------------------------------------------------------------------------- 1 | ### Building and start the app from the sources on Ubuntu Linux 2 | 3 | Tested and verified on Ubuntu 22.04. The source will be checked out to `~/dev/git/arduino-ide`. 4 | 5 | > ❗ This is an all-in-one script to create production-ready, minified code; you will need ~16GB of RAM to run it. This script will install libraries you might already have on your system and change the default Node.js version you do not want. If you look for documentation on development, please reference [this](../development.md#prerequisites) section instead. 6 | 7 | ``` 8 | #!/bin/bash -i 9 | 10 | sudo apt update \ 11 | && sudo apt install --no-install-recommends --yes \ 12 | git \ 13 | gcc \ 14 | curl \ 15 | make \ 16 | python3 \ 17 | pkg-config \ 18 | libx11-dev \ 19 | libxkbfile-dev \ 20 | build-essential \ 21 | libsecret-1-dev \ 22 | && wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash \ 23 | && source ~/.bashrc \ 24 | && nvm install 18.17 \ 25 | && nvm use 18.17 \ 26 | && nvm alias default 18.17 \ 27 | && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - \ 28 | && echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list \ 29 | && sudo apt update && sudo apt install --no-install-recommends yarn \ 30 | && mkdir -p ~/dev/git/ \ 31 | && rm -rf ~/dev/git/arduino-ide \ 32 | && git clone --depth 1 https://github.com/arduino/arduino-ide.git ~/dev/git/arduino-ide \ 33 | && yarn --cwd ~/dev/git/arduino-ide \ 34 | && yarn --cwd ~/dev/git/arduino-ide/electron-app rebuild \ 35 | && yarn --cwd ~/dev/git/arduino-ide build \ 36 | && yarn --cwd ~/dev/git/arduino-ide start 37 | ``` 38 | -------------------------------------------------------------------------------- /electron-app/arduino-ide-backend-main.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 'use strict'; 3 | 4 | // `true` if the this (backend main) process has been forked. 5 | if (process.send) { 6 | const util = require('util'); 7 | for (const name of ['log', 'trace', 'debug', 'info', 'warn', 'error']) { 8 | console[name] = function () { 9 | // eslint-disable-next-line prefer-rest-params 10 | const args = Object.values(arguments); 11 | const message = util.format(...args); 12 | process.send?.({ severity: name, message }); // send the log message to the parent process (electron main) 13 | }; 14 | } 15 | } 16 | 17 | require('./src-gen/backend/main'); 18 | -------------------------------------------------------------------------------- /electron-app/arduino-ide-electron-main.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 'use strict'; 3 | 4 | const os = require('os'); 5 | const path = require('path'); 6 | const config = require('./package.json').theia.frontend.config; 7 | // `buildDate` is only available in the bundled application. 8 | if (config.buildDate) { 9 | // `plugins` folder inside IDE2. IDE2 is shipped with these VS Code extensions. Such as cortex-debug, vscode-cpp, and translations. 10 | process.env.THEIA_DEFAULT_PLUGINS = `local-dir:${path.resolve( 11 | __dirname, 12 | 'plugins' 13 | )}`; 14 | // `plugins` folder inside the `~/.arduinoIDE` folder. This is for manually installed VS Code extensions. For example, custom themes. 15 | process.env.THEIA_PLUGINS = [ 16 | process.env.THEIA_PLUGINS, 17 | `local-dir:${path.resolve(os.homedir(), '.arduinoIDE', 'plugins')}`, 18 | ] 19 | .filter(Boolean) 20 | .join(','); 21 | } 22 | 23 | require('./lib/backend/electron-main'); 24 | -------------------------------------------------------------------------------- /electron-app/resources/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.disable-library-validation 10 | 11 | com.apple.security.cs.allow-dyld-environment-variables 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /electron-app/resources/eula.txt: -------------------------------------------------------------------------------- 1 | Terms of Service 2 | 3 | The Arduino software is provided to you "as is" and we make no express or implied warranties whatsoever with respect to its functionality, operability, or use, including, without limitation, any implied warranties of merchantability, fitness for a particular purpose, or infringement. We expressly disclaim any liability whatsoever for any direct, indirect, consequential, incidental or special damages, including, without limitation, lost revenues, lost profits, losses resulting from business interruption or loss of data, regardless of the form of action or legal theory under which the liability may be asserted, even if advised of the possibility or likelihood of such damages. 4 | -------------------------------------------------------------------------------- /electron-app/resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/electron-app/resources/icon.icns -------------------------------------------------------------------------------- /electron-app/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/electron-app/resources/icon.ico -------------------------------------------------------------------------------- /electron-app/resources/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/electron-app/resources/icons/512x512.png -------------------------------------------------------------------------------- /electron-app/resources/installerSidebar.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/electron-app/resources/installerSidebar.bmp -------------------------------------------------------------------------------- /electron-app/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 'use strict'; 3 | 4 | const isCI = require('is-ci'); 5 | const { notarize } = require('electron-notarize'); 6 | 7 | exports.default = async function notarizing(context) { 8 | if (!isCI) { 9 | console.log('Skipping notarization: not on CI'); 10 | return; 11 | } 12 | const { electronPlatformName, appOutDir } = context; 13 | if (electronPlatformName !== 'darwin') { 14 | console.log('Skipping notarization: not on macOS'); 15 | return; 16 | } 17 | if (process.env.CAN_SIGN !== 'true') { 18 | console.log('Skipping the app notarization: certificate was not provided'); 19 | return; 20 | } 21 | 22 | if (!process.env.AC_USERNAME) { 23 | throw new Error('AC_USERNAME must be set when notarizing on macOS'); 24 | } 25 | if (!process.env.AC_PASSWORD) { 26 | throw new Error('AC_PASSWORD must be set when notarizing on macOS'); 27 | } 28 | if (!process.env.AC_TEAM_ID) { 29 | throw new Error('AC_TEAM_ID must be set when notarizing on macOS'); 30 | } 31 | 32 | const appName = context.packager.appInfo.productFilename; 33 | const appBundleId = context.packager.config.appId; 34 | console.log( 35 | `>>> Notarizing ${appBundleId} at ${appOutDir}/${appName}.app...` 36 | ); 37 | 38 | await notarize({ 39 | appBundleId, 40 | appPath: `${appOutDir}/${appName}.app`, 41 | appleId: process.env.AC_USERNAME, 42 | appleIdPassword: process.env.AC_PASSWORD, 43 | teamId: process.env.AC_TEAM_ID, 44 | tool: 'notarytool', 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /electron-app/scripts/utils.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 'use strict'; 3 | 4 | // const isElectronPublish = false; // TODO: support auto-updates 5 | const isNightly = process.env.IS_NIGHTLY === 'true'; 6 | const isRelease = process.env.IS_RELEASE === 'true'; 7 | 8 | module.exports = { isNightly, isRelease }; 9 | -------------------------------------------------------------------------------- /electron-app/scripts/windowsCustomSign.js: -------------------------------------------------------------------------------- 1 | const childProcess = require('child_process'); 2 | 3 | exports.default = async function (configuration) { 4 | if (!process.env.GITHUB_ACTIONS || process.env.CAN_SIGN !== 'true') { 5 | return; 6 | } 7 | 8 | const SIGNTOOL_PATH = process.env.SIGNTOOL_PATH; 9 | const INSTALLER_CERT_WINDOWS_CER = process.env.INSTALLER_CERT_WINDOWS_CER; 10 | const CERT_PASSWORD = process.env.WIN_CERT_PASSWORD; 11 | const CONTAINER_NAME = process.env.WIN_CERT_CONTAINER_NAME; 12 | const filePath = configuration.path; 13 | 14 | if ( 15 | SIGNTOOL_PATH && 16 | INSTALLER_CERT_WINDOWS_CER && 17 | CERT_PASSWORD && 18 | CONTAINER_NAME 19 | ) { 20 | childProcess.execSync( 21 | `"${SIGNTOOL_PATH}" sign -d "Arduino IDE" -f "${INSTALLER_CERT_WINDOWS_CER}" -csp "eToken Base Cryptographic Provider" -k "[{{${CERT_PASSWORD}}}]=${CONTAINER_NAME}" -fd sha256 -tr http://timestamp.digicert.com -td SHA256 -v "${filePath}"`, 22 | { stdio: 'inherit' } 23 | ); 24 | } else { 25 | console.warn( 26 | `Custom windows signing was no performed one of the following variables was not provided: SIGNTOOL_PATH (${SIGNTOOL_PATH}), INSTALLER_CERT_WINDOWS_CERT (${INSTALLER_CERT_WINDOWS_CER}), CERT_PASSWORD (${CERT_PASSWORD}), CONTAINER_NAME (${CONTAINER_NAME})` 27 | ); 28 | process.exit(1); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /electron-app/test/test-resources/not-a-zip.dmg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/electron-app/test/test-resources/not-a-zip.dmg -------------------------------------------------------------------------------- /electron-app/test/test-resources/zip with whitespace.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/electron-app/test/test-resources/zip with whitespace.zip -------------------------------------------------------------------------------- /electron-app/test/test-resources/zip-with-base-folder.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/electron-app/test/test-resources/zip-with-base-folder.zip -------------------------------------------------------------------------------- /electron-app/test/test-resources/zip-with-symlink.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/electron-app/test/test-resources/zip-with-symlink.zip -------------------------------------------------------------------------------- /electron-app/test/test-resources/zip-without-symlink.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/electron-app/test/test-resources/zip-without-symlink.zip -------------------------------------------------------------------------------- /electron-app/webpack.dev.js: -------------------------------------------------------------------------------- 1 | // When running in development mode, do not webpack the backend and electron main modules. 2 | // It does not work in watch mode: https://github.com/eclipse-theia/theia/issues/12793. 3 | const path = require('node:path'); 4 | const configs = require('./webpack.config'); 5 | const { createCopyArduinoResourcesPlugins } = require('./webpack.base'); 6 | const [mainWindowConfig, preloadConfig] = configs; 7 | 8 | // Use the frontend's webpack config to copy the required resources to the `./arduino-ide-extension/lib/node/resources` folder. 9 | mainWindowConfig.plugins?.push( 10 | ...createCopyArduinoResourcesPlugins( 11 | path.join( 12 | __dirname, 13 | '..', 14 | 'arduino-ide-extension', 15 | 'lib', 16 | 'node', 17 | 'resources' 18 | ) 19 | ) 20 | ); 21 | 22 | module.exports = [mainWindowConfig, preloadConfig]; 23 | -------------------------------------------------------------------------------- /i18n/README.md: -------------------------------------------------------------------------------- 1 | # Localization Data 2 | 3 | This folder contains the [localization](https://en.wikipedia.org/wiki/Internationalization_and_localization) data for Arduino IDE. 4 | 5 | ❗ These files are automatically generated and so can not be edited directly. If you wish to modify the contents, do it at the source: 6 | 7 | - **en.json** - edit the string in [the source code](../arduino-ide-extension/src) 8 | - **All other files** - the localization is done on **Transifex**:
9 | https://explore.transifex.com/arduino-1/ide2/ 10 | 11 | For more information on translating Arduino IDE, see [the **Translator Guide**](../docs/contributor-guide/translation.md). 12 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.4.0", 3 | "version": "independent", 4 | "npmClient": "yarn", 5 | "command": { 6 | "run": { 7 | "stream": true 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /scripts/i18n/transifex.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const util = require('util'); 4 | 5 | const TRANSIFEX_ENDPOINT = 'https://rest.api.transifex.com/'; 6 | 7 | const apiKey = () => { 8 | const apiKey = process.env.TRANSIFEX_API_KEY; 9 | if (!apiKey) { 10 | console.error('missing TRANSIFEX_API_KEY environment variable'); 11 | process.exit(1); 12 | } 13 | return apiKey 14 | } 15 | 16 | exports.credentials = async () => { 17 | const organization = process.env.TRANSIFEX_ORGANIZATION; 18 | const project = process.env.TRANSIFEX_PROJECT; 19 | const resource = process.env.TRANSIFEX_RESOURCE; 20 | 21 | if (!organization) { 22 | console.error('missing TRANSIFEX_ORGANIZATION environment variable'); 23 | process.exit(1); 24 | } 25 | 26 | if (!project) { 27 | console.error('missing TRANSIFEX_PROJECT environment variable'); 28 | process.exit(1); 29 | } 30 | 31 | if (!resource) { 32 | console.error('missing TRANSIFEX_RESOURCE environment variable'); 33 | process.exit(1); 34 | } 35 | 36 | return { organization, project, resource } 37 | } 38 | 39 | exports.url = (path, queryParameters) => { 40 | let url = util.format('%s%s', TRANSIFEX_ENDPOINT, path); 41 | if (queryParameters) { 42 | url = util.format('%s?%s', url, queryParameters); 43 | } 44 | return url 45 | } 46 | 47 | exports.authHeader = () => { 48 | return { 49 | 'Authorization': util.format("Bearer %s", apiKey()), 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scripts/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -i 2 | 3 | set -e 4 | 5 | yarn install --immutable \ 6 | && yarn --cwd arduino-ide-extension build \ 7 | && yarn test \ 8 | && yarn --cwd arduino-ide-extension test:slow \ 9 | && yarn --cwd arduino-ide-extension lint \ 10 | && yarn --cwd electron-app rebuild \ 11 | && yarn --cwd electron-app build \ 12 | && yarn --cwd electron-app package 13 | -------------------------------------------------------------------------------- /scripts/sort-dependencies: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Sorts the dependencies and devDependencies in the package.json without yarn. 4 | // Usage `./scripts/sort-dependencies ./path/to/package.json` 5 | 6 | const { promises: fs } = require('fs'); 7 | const { join, isAbsolute } = require('path'); 8 | const arg = process.argv.slice(2).pop(); 9 | const path = isAbsolute(arg) ? arg : join(process.cwd(), arg); 10 | fs.readFile(path, { encoding: 'utf8' }).then((raw) => { 11 | const json = JSON.parse(raw); 12 | ['dependencies', 'devDependencies'].forEach((prop) => { 13 | const value = json[prop]; 14 | if (value) { 15 | json[prop] = Array.from(Object.entries(value)) 16 | .sort(([left], [right]) => left.localeCompare(right)) 17 | .reduce((acc, [key, value]) => { 18 | acc[key] = value; 19 | return acc; 20 | }, {}); 21 | } 22 | }); 23 | fs.writeFile(path, JSON.stringify(json, null, 2) + '\n', { 24 | encoding: 'utf8', 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /scripts/themes/theme-tokens-pull.js: -------------------------------------------------------------------------------- 1 | const XMLHttpRequest = require('xhr2'); 2 | const fs = require('fs'); 3 | 4 | const JSONBIN_MASTER_KEY = process.env.JSONBIN_MASTER_KEY; 5 | const JSONBIN_ID = process.env.JSONBIN_ID; 6 | 7 | const destFolder = './scripts/themes/tokens'; 8 | 9 | if (!fs.existsSync(destFolder)) { 10 | fs.mkdirSync(destFolder); 11 | } 12 | 13 | let req = new XMLHttpRequest(); 14 | 15 | req.open('GET', 'https://api.jsonbin.io/v3/b/' + JSONBIN_ID + '/latest', true); 16 | req.setRequestHeader('X-Master-Key', JSONBIN_MASTER_KEY); 17 | req.send(); 18 | 19 | req.onreadystatechange = () => { 20 | if (req.readyState == XMLHttpRequest.DONE) { 21 | const tokens = JSON.parse(req.responseText).record.values; 22 | fs.writeFile( 23 | destFolder + '/arduino-tokens.json', 24 | JSON.stringify(tokens), 25 | (err) => { 26 | if (err) { 27 | console.error(err); 28 | return; 29 | } 30 | console.log('Arduino tokens file saved!'); 31 | } 32 | ); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /static/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino/arduino-ide/0f9f0d07b7d5ff0cdefda3e77eb6b5ce2854c4a8/static/screenshot.png --------------------------------------------------------------------------------