mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-10-19 16:18:32 +00:00
Compare commits
100 Commits
arduino/vs
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
![]() |
91c4ba6239 | ||
![]() |
0f9f0d07b7 | ||
![]() |
2f0414a5a1 | ||
![]() |
e3319dab1a | ||
![]() |
a669a43449 | ||
![]() |
56ab874177 | ||
![]() |
e36f393682 | ||
![]() |
4d52bb2843 | ||
![]() |
39c8db8e90 | ||
![]() |
8aa3c28c50 | ||
![]() |
d293595b89 | ||
![]() |
4b0982ccb3 | ||
![]() |
9b15695c60 | ||
![]() |
0dff87e29c | ||
![]() |
7dafe7b0d3 | ||
![]() |
859d29d41a | ||
![]() |
d298b3ffc9 | ||
![]() |
9ab87bf8b5 | ||
![]() |
5ec1915000 | ||
![]() |
6d96e227eb | ||
![]() |
1712f9ea9d | ||
![]() |
6eef09efd8 | ||
![]() |
1112057979 | ||
![]() |
8e18c47d30 | ||
![]() |
4788bfbc3f | ||
![]() |
71b11ed829 | ||
![]() |
3aedafa306 | ||
![]() |
284dd83d7d | ||
![]() |
c09b5f718a | ||
![]() |
dba57b312c | ||
![]() |
90d3d77ca4 | ||
![]() |
0aec778e84 | ||
![]() |
84d2dfd13e | ||
![]() |
86c7fd7b59 | ||
![]() |
de265694ee | ||
![]() |
8462d8a391 | ||
![]() |
48d6d37539 | ||
![]() |
d1065886ef | ||
![]() |
8773bd67ab | ||
![]() |
4189b086de | ||
![]() |
3fc8474d71 | ||
![]() |
4cf9909a07 | ||
![]() |
41844c9470 | ||
![]() |
7c231fff76 | ||
![]() |
d6235f0a0c | ||
![]() |
d377d00042 | ||
![]() |
f232010bec | ||
![]() |
788017bb99 | ||
![]() |
9331d2ec0d | ||
![]() |
6e695429cc | ||
![]() |
4f8b9800a0 | ||
![]() |
f72d1f0ac8 | ||
![]() |
0fe0feace4 | ||
![]() |
43f0ccb250 | ||
![]() |
c0b0b84d79 | ||
![]() |
3d82cb3525 | ||
![]() |
9cbee0eacf | ||
![]() |
63e9dfd7f5 | ||
![]() |
3ccc864453 | ||
![]() |
44f15238d6 | ||
![]() |
4a3abf542c | ||
![]() |
91bb75ca97 | ||
![]() |
77136687d3 | ||
![]() |
16bc1a4610 | ||
![]() |
2921979678 | ||
![]() |
a5bf56ffa6 | ||
![]() |
2de8bd1717 | ||
![]() |
1ec0a8cc77 | ||
![]() |
c3adde5460 | ||
![]() |
2e78e96b75 | ||
![]() |
aa9b10d68e | ||
![]() |
4217c0001d | ||
![]() |
a088ba99f5 | ||
![]() |
2a325a5b74 | ||
![]() |
347e3d8118 | ||
![]() |
8e09971078 | ||
![]() |
48e7bf6b5d | ||
![]() |
95c4399c07 | ||
![]() |
4a807ab538 | ||
![]() |
1a98485b02 | ||
![]() |
8fe6a81230 | ||
![]() |
547a630598 | ||
![]() |
ff8c646cfa | ||
![]() |
316e0fd8be | ||
![]() |
ca779e5cf2 | ||
![]() |
74c580175b | ||
![]() |
71bd189eb1 | ||
![]() |
0822ed28da | ||
![]() |
1b9c7e93e0 | ||
![]() |
d419a6c6f0 | ||
![]() |
dda7770105 | ||
![]() |
763fde036c | ||
![]() |
0e7b0c9486 | ||
![]() |
b8dd39c729 | ||
![]() |
d6de53780d | ||
![]() |
2f2b19f613 | ||
![]() |
0ca1a31747 | ||
![]() |
d01f95647e | ||
![]() |
074f654457 | ||
![]() |
3eef857b48 |
93
.github/workflows/assets/linux.Dockerfile
vendored
93
.github/workflows/assets/linux.Dockerfile
vendored
@@ -1,43 +1,28 @@
|
||||
# The Arduino IDE Linux build workflow job runs in this container.
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM ubuntu:18.04
|
||||
# See: https://hub.docker.com/_/ubuntu/tags
|
||||
FROM ubuntu:18.10
|
||||
|
||||
# See: https://unofficial-builds.nodejs.org/download/release/
|
||||
ARG node_version="18.17.1"
|
||||
# This is required in order to use the Ubuntu package repositories for EOL Ubuntu versions:
|
||||
# https://help.ubuntu.com/community/EOLUpgrades#Update_sources.list
|
||||
RUN \
|
||||
sed \
|
||||
--in-place \
|
||||
--regexp-extended \
|
||||
--expression='s/([a-z]{2}\.)?archive.ubuntu.com|security.ubuntu.com/old-releases.ubuntu.com/g' \
|
||||
"/etc/apt/sources.list"
|
||||
|
||||
RUN \
|
||||
apt-get \
|
||||
--yes \
|
||||
update
|
||||
|
||||
# This is required to get add-apt-repository
|
||||
RUN \
|
||||
apt-get \
|
||||
--yes \
|
||||
install \
|
||||
"software-properties-common=0.96.24.32.22"
|
||||
|
||||
# Install Git
|
||||
# The PPA is required to get a modern version of Git. The version in the Ubuntu 18.04 package repository is 2.17.1,
|
||||
# while action/checkout@v3 requires 2.18 or higher.
|
||||
RUN \
|
||||
add-apt-repository \
|
||||
--yes \
|
||||
"ppa:git-core/ppa" && \
|
||||
apt-get \
|
||||
--yes \
|
||||
update && \
|
||||
\
|
||||
apt-get \
|
||||
--yes \
|
||||
install \
|
||||
"git" && \
|
||||
\
|
||||
apt-get \
|
||||
--yes \
|
||||
purge \
|
||||
"software-properties-common"
|
||||
"git"
|
||||
|
||||
# The repository path must be added to safe.directory, otherwise any Git operations on it would fail with a
|
||||
# "dubious ownership" error. actions/checkout configures this, but it is not applied to containers.
|
||||
@@ -51,18 +36,12 @@ ENV \
|
||||
|
||||
# Install Python
|
||||
# The Python installed by actions/setup-python has dependency on a higher version of glibc than available in the
|
||||
# ubuntu:18.04 container.
|
||||
# container.
|
||||
RUN \
|
||||
apt-get \
|
||||
--yes \
|
||||
install \
|
||||
"python3.8-minimal=3.8.0-3ubuntu1~18.04.2" && \
|
||||
\
|
||||
ln \
|
||||
--symbolic \
|
||||
--force \
|
||||
"$(which python3.8)" \
|
||||
"/usr/bin/python3"
|
||||
"python3.7-minimal=3.7.3-2~18.10"
|
||||
|
||||
# Install Theia's package dependencies
|
||||
# These are pre-installed in the GitHub Actions hosted runner machines.
|
||||
@@ -70,43 +49,15 @@ RUN \
|
||||
apt-get \
|
||||
--yes \
|
||||
install \
|
||||
"libsecret-1-dev=0.18.6-1" \
|
||||
"libx11-dev=2:1.6.4-3ubuntu0.4" \
|
||||
"libsecret-1-dev=0.18.6-3" \
|
||||
"libx11-dev=2:1.6.7-1" \
|
||||
"libxkbfile-dev=1:1.0.9-2"
|
||||
|
||||
# Install Node.js
|
||||
# It is necessary to use the "unofficial" linux-x64-glibc-217 build because the official Node.js 18.x is dynamically
|
||||
# linked against glibc 2.28, while Ubuntu 18.04 has glibc 2.27.
|
||||
ARG node_installation_path="/tmp/node-installation"
|
||||
ARG artifact_name="node-v${node_version}-linux-x64-glibc-217"
|
||||
# Target python3 symlink to Python 3.7 installation. It would otherwise target version 3.6 due to the installation of
|
||||
# the `python3` package as a transitive dependency.
|
||||
RUN \
|
||||
mkdir "$node_installation_path" && \
|
||||
cd "$node_installation_path" && \
|
||||
\
|
||||
apt-get \
|
||||
--yes \
|
||||
install \
|
||||
"wget=1.19.4-1ubuntu2.2" && \
|
||||
\
|
||||
archive_name="${artifact_name}.tar.xz" && \
|
||||
wget \
|
||||
"https://unofficial-builds.nodejs.org/download/release/v${node_version}/${archive_name}" && \
|
||||
\
|
||||
apt-get \
|
||||
--yes \
|
||||
purge \
|
||||
"wget" && \
|
||||
\
|
||||
tar \
|
||||
--file="$archive_name" \
|
||||
--extract && \
|
||||
rm "$archive_name"
|
||||
ENV PATH="${PATH}:${node_installation_path}/${artifact_name}/bin"
|
||||
|
||||
# Install Yarn
|
||||
# Yarn is pre-installed in the GitHub Actions hosted runner machines.
|
||||
RUN \
|
||||
npm \
|
||||
install \
|
||||
--global \
|
||||
"yarn@1.22.19"
|
||||
ln \
|
||||
--symbolic \
|
||||
--force \
|
||||
"$(which python3.7)" \
|
||||
"/usr/bin/python3"
|
||||
|
258
.github/workflows/build.yml
vendored
258
.github/workflows/build.yml
vendored
@@ -40,36 +40,46 @@ on:
|
||||
- Push Container Images
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
types:
|
||||
- completed
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: '1.19'
|
||||
GO_VERSION: '1.21'
|
||||
# See: https://github.com/actions/setup-node/#readme
|
||||
NODE_VERSION: '18.17'
|
||||
JOB_TRANSFER_ARTIFACT: build-artifacts
|
||||
YARN_VERSION: '1.22'
|
||||
JOB_TRANSFER_ARTIFACT_PREFIX: build-artifacts-
|
||||
CHANGELOG_ARTIFACTS: changelog
|
||||
STAGED_CHANNEL_FILES_ARTIFACT: staged-channel-files
|
||||
STAGED_CHANNEL_FILE_ARTIFACT_PREFIX: staged-channel-file-
|
||||
BASE_BUILD_DATA: |
|
||||
- config:
|
||||
# Human identifier for the job.
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
runs-on: [self-hosted, windows-sign-pc]
|
||||
# The value is a string representing a JSON document.
|
||||
# Setting this to null causes the job to run directly in the runner machine instead of in a container.
|
||||
container: |
|
||||
null
|
||||
# Name of the secret that contains the certificate.
|
||||
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX
|
||||
certificate-secret: INSTALLER_CERT_WINDOWS_CER
|
||||
# Name of the secret that contains the certificate password.
|
||||
certificate-password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD
|
||||
certificate-password-secret: INSTALLER_CERT_WINDOWS_PASSWORD
|
||||
# File extension for the certificate.
|
||||
certificate-extension: pfx
|
||||
# Container for windows cert signing
|
||||
certificate-container: INSTALLER_CERT_WINDOWS_CONTAINER
|
||||
# Arbitrary identifier used to give the workflow artifact uploaded by each "build" matrix job a unique name.
|
||||
job-transfer-artifact-suffix: Windows_64bit
|
||||
# Quoting on the value is required here to allow the same comparison expression syntax to be used for this
|
||||
# and the companion needs.select-targets.outputs.merge-channel-files property (output values always have string
|
||||
# type).
|
||||
mergeable-channel-file: 'false'
|
||||
# as this runs on a self hosted runner, we need to avoid building with the default working directory path,
|
||||
# otherwise paths in the build job will be too long for `light.exe`
|
||||
# we use the below as a Symbolic link (just changing the wd will break the checkout action)
|
||||
# this is a work around (see: https://github.com/actions/checkout/issues/197).
|
||||
working-directory: 'C:\a'
|
||||
artifacts:
|
||||
- path: '*Windows_64bit.exe'
|
||||
name: Windows_X86-64_interactive_installer
|
||||
@@ -84,6 +94,7 @@ env:
|
||||
{
|
||||
\"image\": \"ghcr.io/arduino/arduino-ide/linux:main\"
|
||||
}
|
||||
job-transfer-artifact-suffix: Linux_64bit
|
||||
mergeable-channel-file: 'false'
|
||||
artifacts:
|
||||
- path: '*Linux_64bit.zip'
|
||||
@@ -92,7 +103,7 @@ env:
|
||||
name: Linux_X86-64_app_image
|
||||
- config:
|
||||
name: macOS x86
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-13
|
||||
container: |
|
||||
null
|
||||
# APPLE_SIGNING_CERTIFICATE_P12 secret was produced by following the procedure from:
|
||||
@@ -100,27 +111,32 @@ env:
|
||||
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12
|
||||
certificate-password-secret: KEYCHAIN_PASSWORD
|
||||
certificate-extension: p12
|
||||
job-transfer-artifact-suffix: macOS_64bit
|
||||
mergeable-channel-file: 'true'
|
||||
artifacts:
|
||||
- path: '*macOS_64bit.dmg'
|
||||
name: macOS_X86-64_dmg
|
||||
- path: '*macOS_64bit.zip'
|
||||
name: macOS_X86-64_zip
|
||||
PAID_RUNNER_BUILD_DATA: |
|
||||
- config:
|
||||
name: macOS ARM
|
||||
runs-on: macos-latest-xlarge
|
||||
runs-on: macos-latest
|
||||
container: |
|
||||
null
|
||||
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12
|
||||
certificate-password-secret: KEYCHAIN_PASSWORD
|
||||
certificate-extension: p12
|
||||
job-transfer-artifact-suffix: macOS_arm64
|
||||
mergeable-channel-file: 'true'
|
||||
artifacts:
|
||||
- path: '*macOS_arm64.dmg'
|
||||
name: macOS_arm64_dmg
|
||||
- path: '*macOS_arm64.zip'
|
||||
name: macOS_arm64_zip
|
||||
PAID_RUNNER_BUILD_DATA: |
|
||||
# This system was implemented to allow selective use of paid GitHub-hosted runners, due to the Apple Silicon runner
|
||||
# incurring a charge at that time. Free Apple Silicon runners are now available so the configuration was moved to
|
||||
# `BASE_BUILD_DATA`, but the system was left in place for future use.
|
||||
|
||||
jobs:
|
||||
run-determination:
|
||||
@@ -156,6 +172,7 @@ jobs:
|
||||
is-nightly: ${{ steps.determination.outputs.is-nightly }}
|
||||
channel-name: ${{ steps.determination.outputs.channel-name }}
|
||||
publish-to-s3: ${{ steps.determination.outputs.publish-to-s3 }}
|
||||
environment: production
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Determine the type of build
|
||||
@@ -187,7 +204,7 @@ jobs:
|
||||
echo "is-nightly=$is_nightly" >> $GITHUB_OUTPUT
|
||||
echo "channel-name=$channel_name" >> $GITHUB_OUTPUT
|
||||
# Only attempt upload to Amazon S3 if the credentials are available.
|
||||
echo "publish-to-s3=${{ secrets.AWS_SECRET_ACCESS_KEY != '' }}" >> $GITHUB_OUTPUT
|
||||
echo "publish-to-s3=${{ secrets.AWS_ROLE_ARN != '' }}" >> $GITHUB_OUTPUT
|
||||
|
||||
select-targets:
|
||||
needs: build-type-determination
|
||||
@@ -223,7 +240,7 @@ jobs:
|
||||
) | \
|
||||
yq \
|
||||
--output-format json \
|
||||
'[.[].artifacts.[]]'
|
||||
'map(.artifacts[] + (.config | pick(["job-transfer-artifact-suffix"])))'
|
||||
)"
|
||||
|
||||
# The build matrix produces two macOS jobs (x86 and ARM) so the "channel update info files"
|
||||
@@ -242,7 +259,7 @@ jobs:
|
||||
echo "${{ env.BASE_BUILD_DATA }}" | \
|
||||
yq \
|
||||
--output-format json \
|
||||
'[.[].artifacts.[]]'
|
||||
'map(.artifacts[] + (.config | pick(["job-transfer-artifact-suffix"])))'
|
||||
)"
|
||||
|
||||
merge_channel_files="false"
|
||||
@@ -270,6 +287,16 @@ jobs:
|
||||
env:
|
||||
# Location of artifacts generated by build.
|
||||
BUILD_ARTIFACTS_PATH: electron-app/dist/build-artifacts
|
||||
# to skip passing signing credentials to electron-builder
|
||||
IS_WINDOWS_CONFIG: ${{ matrix.config.name == 'Windows' }}
|
||||
INSTALLER_CERT_WINDOWS_CER: "/tmp/cert.cer"
|
||||
# We are hardcoding the path for signtool because is not present on the windows PATH env var by default.
|
||||
# Keep in mind that this path could change when upgrading to a new runner version
|
||||
SIGNTOOL_PATH: "C:/Program Files (x86)/Windows Kits/10/bin/10.0.19041.0/x86/signtool.exe"
|
||||
WIN_CERT_PASSWORD: ${{ secrets[matrix.config.certificate-password-secret] }}
|
||||
WIN_CERT_CONTAINER_NAME: ${{ secrets[matrix.config.certificate-container] }}
|
||||
PUPPETEER_SKIP_DOWNLOAD: true
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
config: ${{ fromJson(needs.select-targets.outputs.build-matrix) }}
|
||||
@@ -283,44 +310,49 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Symlink custom working directory
|
||||
shell: cmd
|
||||
if: runner.os == 'Windows' && matrix.config.working-directory
|
||||
run: |
|
||||
if not exist "${{ matrix.config.working-directory }}" mklink /d "${{ matrix.config.working-directory }}" "C:\actions-runner\_work\arduino-ide\arduino-ide"
|
||||
|
||||
- name: Checkout
|
||||
if: fromJSON(matrix.config.container) == null
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout
|
||||
# actions/checkout@v4 has dependency on a higher version of glibc than available in the Linux container.
|
||||
if: fromJSON(matrix.config.container) != null
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
if: fromJSON(matrix.config.container) == null
|
||||
if: runner.name != 'WINDOWS-SIGN-PC'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'yarn'
|
||||
# Yarn is a prerequisite for the action's cache feature, so caching should be disabled when running in the
|
||||
# container where Yarn is not pre-installed.
|
||||
cache: ${{ fromJSON(matrix.config.container) == null && 'yarn' || null }}
|
||||
|
||||
- name: Install Yarn
|
||||
if: runner.name != 'WINDOWS-SIGN-PC'
|
||||
run: |
|
||||
npm \
|
||||
install \
|
||||
--global \
|
||||
"yarn@${{ env.YARN_VERSION }}"
|
||||
|
||||
- name: Install Python 3.x
|
||||
if: fromJSON(matrix.config.container) == null
|
||||
if: fromJSON(matrix.config.container) == null && runner.name != 'WINDOWS-SIGN-PC'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11.x'
|
||||
|
||||
- name: Install Go
|
||||
if: fromJSON(matrix.config.container) == null
|
||||
if: runner.name != 'WINDOWS-SIGN-PC'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Go
|
||||
# actions/setup-go@v5 has dependency on a higher version of glibc than available in the Linux container.
|
||||
if: fromJSON(matrix.config.container) != null
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Taskfile
|
||||
uses: arduino/setup-task@v1
|
||||
if: runner.name != 'WINDOWS-SIGN-PC'
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
@@ -331,19 +363,13 @@ jobs:
|
||||
AC_USERNAME: ${{ secrets.AC_USERNAME }}
|
||||
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
|
||||
AC_TEAM_ID: ${{ secrets.AC_TEAM_ID }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
IS_NIGHTLY: ${{ needs.build-type-determination.outputs.is-nightly }}
|
||||
IS_RELEASE: ${{ needs.build-type-determination.outputs.is-release }}
|
||||
CAN_SIGN: ${{ secrets[matrix.config.certificate-secret] != '' }}
|
||||
# The CREATE_* environment vars are only used to run tests. These secrets are optional. Dependent tests will
|
||||
# be skipped if not available.
|
||||
CREATE_USERNAME: ${{ secrets.CREATE_USERNAME }}
|
||||
CREATE_PASSWORD: ${{ secrets.CREATE_PASSWORD }}
|
||||
CREATE_CLIENT_SECRET: ${{ secrets.CREATE_CLIENT_SECRET }}
|
||||
working-directory: ${{ matrix.config.working-directory || './' }}
|
||||
run: |
|
||||
# See: https://www.electron.build/code-signing
|
||||
if [ $CAN_SIGN = false ]; then
|
||||
if [ $CAN_SIGN = false ] || [ $IS_WINDOWS_CONFIG = true ]; then
|
||||
echo "Skipping the app signing: certificate not provided."
|
||||
else
|
||||
export CSC_LINK="${{ runner.temp }}/signing_certificate.${{ matrix.config.certificate-extension }}"
|
||||
@@ -353,13 +379,9 @@ jobs:
|
||||
fi
|
||||
|
||||
npx node-gyp install
|
||||
yarn install --immutable
|
||||
yarn install
|
||||
|
||||
yarn --cwd arduino-ide-extension build
|
||||
yarn test
|
||||
yarn --cwd arduino-ide-extension test:slow
|
||||
yarn --cwd arduino-ide-extension lint
|
||||
|
||||
yarn --cwd electron-app rebuild
|
||||
yarn --cwd electron-app build
|
||||
yarn --cwd electron-app package
|
||||
@@ -370,6 +392,7 @@ jobs:
|
||||
if: >
|
||||
needs.select-targets.outputs.merge-channel-files == 'true' &&
|
||||
matrix.config.mergeable-channel-file == 'true'
|
||||
working-directory: ${{ matrix.config.working-directory || './' }}
|
||||
run: |
|
||||
staged_channel_files_path="${{ runner.temp }}/staged-channel-files"
|
||||
mkdir "$staged_channel_files_path"
|
||||
@@ -382,20 +405,26 @@ jobs:
|
||||
echo "STAGED_CHANNEL_FILES_PATH=$staged_channel_files_path" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Upload staged-for-merge channel file artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: >
|
||||
needs.select-targets.outputs.merge-channel-files == 'true' &&
|
||||
matrix.config.mergeable-channel-file == 'true'
|
||||
with:
|
||||
if-no-files-found: error
|
||||
name: ${{ env.STAGED_CHANNEL_FILES_ARTIFACT }}
|
||||
path: ${{ env.STAGED_CHANNEL_FILES_PATH }}
|
||||
name: ${{ env.STAGED_CHANNEL_FILE_ARTIFACT_PREFIX }}${{ matrix.config.job-transfer-artifact-suffix }}
|
||||
path: ${{ matrix.config.working-directory && format('{0}/{1}', matrix.config.working-directory, env.STAGED_CHANNEL_FILES_PATH) || env.STAGED_CHANNEL_FILES_PATH }}
|
||||
|
||||
- name: Upload [GitHub Actions]
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Upload builds to job transfer artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
path: ${{ env.BUILD_ARTIFACTS_PATH }}
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT_PREFIX }}${{ matrix.config.job-transfer-artifact-suffix }}
|
||||
path: ${{ matrix.config.working-directory && format('{0}/{1}', matrix.config.working-directory, env.BUILD_ARTIFACTS_PATH) || env.BUILD_ARTIFACTS_PATH }}
|
||||
|
||||
- name: Manual Clean up for self-hosted runners
|
||||
if: runner.os == 'Windows' && matrix.config.working-directory
|
||||
shell: cmd
|
||||
run: |
|
||||
rmdir /s /q "${{ matrix.config.working-directory }}\${{ env.BUILD_ARTIFACTS_PATH }}"
|
||||
|
||||
merge-channel-files:
|
||||
needs:
|
||||
@@ -414,16 +443,17 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download staged-for-merge channel files artifact
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Download staged-for-merge channel file artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.STAGED_CHANNEL_FILES_ARTIFACT }}
|
||||
merge-multiple: true
|
||||
path: ${{ env.CHANNEL_FILES_PATH }}
|
||||
pattern: ${{ env.STAGED_CHANNEL_FILE_ARTIFACT_PREFIX }}*
|
||||
|
||||
- name: Remove no longer needed artifact
|
||||
uses: geekyeggo/delete-artifact@v2
|
||||
- name: Remove no longer needed artifacts
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
name: ${{ env.STAGED_CHANNEL_FILES_ARTIFACT }}
|
||||
name: ${{ env.STAGED_CHANNEL_FILE_ARTIFACT_PREFIX }}*
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -438,11 +468,17 @@ jobs:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
@@ -453,11 +489,11 @@ jobs:
|
||||
--channel "${{ needs.build-type-determination.outputs.channel-name }}" \
|
||||
--input "${{ env.CHANNEL_FILES_PATH }}"
|
||||
|
||||
- name: Upload merged channel files to job transfer artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Upload merged channel files job transfer artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
if-no-files-found: error
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT_PREFIX }}channel-files
|
||||
path: ${{ env.CHANNEL_FILES_PATH }}
|
||||
|
||||
artifacts:
|
||||
@@ -468,22 +504,25 @@ jobs:
|
||||
if: always() && needs.build.result != 'skipped'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
BUILD_ARTIFACTS_FOLDER: build-artifacts
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
artifact: ${{ fromJson(needs.select-targets.outputs.artifact-matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Download job transfer artifact
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Download job transfer artifact that contains ${{ matrix.artifact.name }} tester build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
path: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT_PREFIX }}${{ matrix.artifact.job-transfer-artifact-suffix }}
|
||||
path: ${{ env.BUILD_ARTIFACTS_FOLDER }}
|
||||
|
||||
- name: Upload tester build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact.name }}
|
||||
path: ${{ env.JOB_TRANSFER_ARTIFACT }}/${{ matrix.artifact.path }}
|
||||
path: ${{ env.BUILD_ARTIFACTS_FOLDER }}/${{ matrix.artifact.path }}
|
||||
|
||||
changelog:
|
||||
needs:
|
||||
@@ -526,11 +565,11 @@ jobs:
|
||||
|
||||
echo "$BODY" > CHANGELOG.txt
|
||||
|
||||
- name: Upload Changelog [GitHub Actions]
|
||||
- name: Upload changelog job transfer artifact
|
||||
if: needs.build-type-determination.outputs.is-nightly == 'true'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT_PREFIX }}changelog
|
||||
path: CHANGELOG.txt
|
||||
|
||||
publish:
|
||||
@@ -549,22 +588,33 @@ jobs:
|
||||
needs.build-type-determination.outputs.publish-to-s3 == 'true' &&
|
||||
needs.build-type-determination.outputs.is-nightly == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
ARTIFACTS_FOLDER: build-artifacts
|
||||
|
||||
environment: production
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Download [GitHub Actions]
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Download all job transfer artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
path: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
merge-multiple: true
|
||||
path: ${{ env.ARTIFACTS_FOLDER }}
|
||||
pattern: ${{ env.JOB_TRANSFER_ARTIFACT_PREFIX }}*
|
||||
|
||||
- name: Configure AWS Credentials for Nightly [S3]
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Publish Nightly [S3]
|
||||
uses: docker://plugins/s3
|
||||
env:
|
||||
PLUGIN_SOURCE: '${{ env.JOB_TRANSFER_ARTIFACT }}/*'
|
||||
PLUGIN_STRIP_PREFIX: '${{ env.JOB_TRANSFER_ARTIFACT }}/'
|
||||
PLUGIN_TARGET: '/arduino-ide/nightly'
|
||||
PLUGIN_BUCKET: ${{ secrets.DOWNLOADS_BUCKET }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
run: |
|
||||
aws s3 sync ${{ env.ARTIFACTS_FOLDER }} s3://${{ secrets.DOWNLOADS_BUCKET }}/arduino-ide/nightly
|
||||
|
||||
release:
|
||||
needs:
|
||||
@@ -581,12 +631,23 @@ jobs:
|
||||
needs.changelog.result == 'success' &&
|
||||
needs.build-type-determination.outputs.is-release == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
ARTIFACTS_FOLDER: build-artifacts
|
||||
|
||||
environment: production
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download [GitHub Actions]
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Download all job transfer artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
path: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
merge-multiple: true
|
||||
path: ${{ env.ARTIFACTS_FOLDER }}
|
||||
pattern: ${{ env.JOB_TRANSFER_ARTIFACT_PREFIX }}*
|
||||
|
||||
- name: Get Tag
|
||||
id: tag_name
|
||||
@@ -594,25 +655,26 @@ jobs:
|
||||
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish Release [GitHub]
|
||||
uses: svenstaro/upload-release-action@2.7.0
|
||||
uses: svenstaro/upload-release-action@2.11.2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release_name: ${{ steps.tag_name.outputs.TAG_NAME }}
|
||||
file: ${{ env.JOB_TRANSFER_ARTIFACT }}/*
|
||||
file: ${{ env.ARTIFACTS_FOLDER }}/*
|
||||
tag: ${{ github.ref }}
|
||||
file_glob: true
|
||||
body: ${{ needs.changelog.outputs.BODY }}
|
||||
|
||||
- name: Configure AWS Credentials for Release [S3]
|
||||
if: needs.build-type-determination.outputs.publish-to-s3 == 'true'
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Publish Release [S3]
|
||||
if: needs.build-type-determination.outputs.publish-to-s3 == 'true'
|
||||
uses: docker://plugins/s3
|
||||
env:
|
||||
PLUGIN_SOURCE: '${{ env.JOB_TRANSFER_ARTIFACT }}/*'
|
||||
PLUGIN_STRIP_PREFIX: '${{ env.JOB_TRANSFER_ARTIFACT }}/'
|
||||
PLUGIN_TARGET: '/arduino-ide'
|
||||
PLUGIN_BUCKET: ${{ secrets.DOWNLOADS_BUCKET }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
run: |
|
||||
aws s3 sync ${{ env.ARTIFACTS_FOLDER }} s3://${{ secrets.DOWNLOADS_BUCKET }}/arduino-ide
|
||||
|
||||
clean:
|
||||
# This job must run after all jobs that use the transfer artifact.
|
||||
@@ -626,7 +688,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Remove unneeded job transfer artifact
|
||||
uses: geekyeggo/delete-artifact@v2
|
||||
- name: Remove unneeded job transfer artifacts
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT_PREFIX }}*
|
||||
|
65
.github/workflows/check-certificates.yml
vendored
65
.github/workflows/check-certificates.yml
vendored
@@ -74,9 +74,11 @@ jobs:
|
||||
- identifier: macOS signing certificate # Text used to identify certificate in notifications.
|
||||
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12 # Name of the secret that contains the certificate.
|
||||
password-secret: KEYCHAIN_PASSWORD # Name of the secret that contains the certificate password.
|
||||
type: pkcs12
|
||||
- identifier: Windows signing certificate
|
||||
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX
|
||||
password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD
|
||||
certificate-secret: INSTALLER_CERT_WINDOWS_CER
|
||||
# The password for the Windows certificate is not needed, because its not a container, but a single certificate.
|
||||
type: x509
|
||||
|
||||
steps:
|
||||
- name: Set certificate path environment variable
|
||||
@@ -95,7 +97,7 @@ jobs:
|
||||
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }}
|
||||
run: |
|
||||
(
|
||||
openssl pkcs12 \
|
||||
openssl ${{ matrix.certificate.type }} \
|
||||
-in "${{ env.CERTIFICATE_PATH }}" \
|
||||
-legacy \
|
||||
-noout \
|
||||
@@ -122,26 +124,43 @@ jobs:
|
||||
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }}
|
||||
id: get-days-before-expiration
|
||||
run: |
|
||||
EXPIRATION_DATE="$(
|
||||
(
|
||||
openssl pkcs12 \
|
||||
-in "${{ env.CERTIFICATE_PATH }}" \
|
||||
-clcerts \
|
||||
-legacy \
|
||||
-nodes \
|
||||
-passin env:CERTIFICATE_PASSWORD
|
||||
) | (
|
||||
openssl x509 \
|
||||
-noout \
|
||||
-enddate
|
||||
) | (
|
||||
grep \
|
||||
--max-count=1 \
|
||||
--only-matching \
|
||||
--perl-regexp \
|
||||
'notAfter=(\K.*)'
|
||||
)
|
||||
)"
|
||||
if [[ ${{ matrix.certificate.type }} == "pkcs12" ]]; then
|
||||
EXPIRATION_DATE="$(
|
||||
(
|
||||
openssl pkcs12 \
|
||||
-in "${{ env.CERTIFICATE_PATH }}" \
|
||||
-clcerts \
|
||||
-legacy \
|
||||
-nodes \
|
||||
-passin env:CERTIFICATE_PASSWORD
|
||||
) | (
|
||||
openssl x509 \
|
||||
-noout \
|
||||
-enddate
|
||||
) | (
|
||||
grep \
|
||||
--max-count=1 \
|
||||
--only-matching \
|
||||
--perl-regexp \
|
||||
'notAfter=(\K.*)'
|
||||
)
|
||||
)"
|
||||
elif [[ ${{ matrix.certificate.type }} == "x509" ]]; then
|
||||
EXPIRATION_DATE="$(
|
||||
(
|
||||
openssl x509 \
|
||||
-in ${{ env.CERTIFICATE_PATH }} \
|
||||
-noout \
|
||||
-enddate
|
||||
) | (
|
||||
grep \
|
||||
--max-count=1 \
|
||||
--only-matching \
|
||||
--perl-regexp \
|
||||
'notAfter=(\K.*)'
|
||||
)
|
||||
)"
|
||||
fi
|
||||
|
||||
DAYS_BEFORE_EXPIRATION="$((($(date --utc --date="$EXPIRATION_DATE" +%s) - $(date --utc +%s)) / 60 / 60 / 24))"
|
||||
|
||||
|
2
.github/workflows/check-containers.yml
vendored
2
.github/workflows/check-containers.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and push to local registry
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.image.path }}
|
||||
|
10
.github/workflows/check-i18n-task.yml
vendored
10
.github/workflows/check-i18n-task.yml
vendored
@@ -2,7 +2,7 @@ name: Check Internationalization
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: '1.19'
|
||||
GO_VERSION: '1.21'
|
||||
|
||||
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
|
||||
on:
|
||||
@@ -71,11 +71,17 @@ jobs:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Taskfile
|
||||
uses: arduino/setup-task@v1
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
env:
|
||||
|
94
.github/workflows/check-javascript.yml
vendored
Normal file
94
.github/workflows/check-javascript.yml
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-javascript-task.md
|
||||
name: Check JavaScript
|
||||
|
||||
env:
|
||||
# See: https://github.com/actions/setup-node/#readme
|
||||
NODE_VERSION: 18.17
|
||||
|
||||
# See: https://docs.github.com/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows
|
||||
on:
|
||||
create:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/check-javascript.ya?ml'
|
||||
- '**/.eslintignore'
|
||||
- '**/.eslintrc*'
|
||||
- '**/.npmrc'
|
||||
- '**/package.json'
|
||||
- '**/package-lock.json'
|
||||
- '**/yarn.lock'
|
||||
- '**.jsx?'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/check-javascript.ya?ml'
|
||||
- '**/.eslintignore'
|
||||
- '**/.eslintrc*'
|
||||
- '**/.npmrc'
|
||||
- '**/package.json'
|
||||
- '**/package-lock.json'
|
||||
- '**/yarn.lock'
|
||||
- '**.jsx?'
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
|
||||
jobs:
|
||||
run-determination:
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
outputs:
|
||||
result: ${{ steps.determination.outputs.result }}
|
||||
steps:
|
||||
- name: Determine if the rest of the workflow should run
|
||||
id: determination
|
||||
run: |
|
||||
RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x"
|
||||
# The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead.
|
||||
if [[
|
||||
"${{ github.event_name }}" != "create" ||
|
||||
"${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX
|
||||
]]; then
|
||||
# Run the other jobs.
|
||||
RESULT="true"
|
||||
else
|
||||
# There is no need to run the other jobs.
|
||||
RESULT="false"
|
||||
fi
|
||||
|
||||
echo "result=$RESULT" >> $GITHUB_OUTPUT
|
||||
|
||||
check:
|
||||
needs: run-determination
|
||||
if: needs.run-determination.outputs.result == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: yarn
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install Dependencies (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev
|
||||
|
||||
- name: Install npm package dependencies
|
||||
env:
|
||||
# Avoid failure of @vscode/ripgrep installation due to GitHub API rate limiting:
|
||||
# https://github.com/microsoft/vscode-ripgrep#github-api-limit-note
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
yarn install
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
yarn \
|
||||
--cwd arduino-ide-extension \
|
||||
lint
|
97
.github/workflows/check-yarn.yml
vendored
Normal file
97
.github/workflows/check-yarn.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
name: Check Yarn
|
||||
|
||||
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
|
||||
on:
|
||||
create:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/check-yarn.ya?ml"
|
||||
- "**/.yarnrc"
|
||||
- "**/package.json"
|
||||
- "**/package-lock.json"
|
||||
- "**/yarn.lock"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/check-yarn.ya?ml"
|
||||
- "**/.yarnrc"
|
||||
- "**/package.json"
|
||||
- "**/package-lock.json"
|
||||
- "**/yarn.lock"
|
||||
schedule:
|
||||
# Run every Tuesday at 8 AM UTC to catch breakage resulting from changes to the JSON schema.
|
||||
- cron: "0 8 * * TUE"
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
|
||||
jobs:
|
||||
run-determination:
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
outputs:
|
||||
result: ${{ steps.determination.outputs.result }}
|
||||
steps:
|
||||
- name: Determine if the rest of the workflow should run
|
||||
id: determination
|
||||
run: |
|
||||
RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x"
|
||||
# The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead.
|
||||
if [[
|
||||
"${{ github.event_name }}" != "create" ||
|
||||
"${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX
|
||||
]]; then
|
||||
# Run the other jobs.
|
||||
RESULT="true"
|
||||
else
|
||||
# There is no need to run the other jobs.
|
||||
RESULT="false"
|
||||
fi
|
||||
|
||||
echo "result=$RESULT" >> $GITHUB_OUTPUT
|
||||
|
||||
check-sync:
|
||||
name: check-sync (${{ matrix.project.path }})
|
||||
needs: run-determination
|
||||
if: needs.run-determination.outputs.result == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project:
|
||||
- path: .
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: yarn
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install Dependencies (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev
|
||||
|
||||
- name: Install npm package dependencies
|
||||
env:
|
||||
# Avoid failure of @vscode/ripgrep installation due to GitHub API rate limiting:
|
||||
# https://github.com/microsoft/vscode-ripgrep#github-api-limit-note
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
yarn \
|
||||
install \
|
||||
--ignore-scripts
|
||||
|
||||
- name: Check yarn.lock
|
||||
run: |
|
||||
git \
|
||||
diff \
|
||||
--color \
|
||||
--exit-code \
|
||||
"${{ matrix.project.path }}/yarn.lock"
|
27
.github/workflows/compose-full-changelog.yml
vendored
27
.github/workflows/compose-full-changelog.yml
vendored
@@ -14,6 +14,11 @@ jobs:
|
||||
create-changelog:
|
||||
if: github.repository == 'arduino/arduino-ide'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
environment: production
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -24,6 +29,12 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install Dependencies (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev
|
||||
|
||||
- name: Get Tag
|
||||
id: tag_name
|
||||
run: |
|
||||
@@ -44,12 +55,12 @@ jobs:
|
||||
# Compose changelog
|
||||
yarn run compose-changelog "${{ github.workspace }}/${{ env.CHANGELOG_ARTIFACTS }}/$CHANGELOG_FILE_NAME"
|
||||
|
||||
- name: Configure AWS Credentials for Changelog [S3]
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Publish Changelog [S3]
|
||||
uses: docker://plugins/s3
|
||||
env:
|
||||
PLUGIN_SOURCE: '${{ env.CHANGELOG_ARTIFACTS }}/*'
|
||||
PLUGIN_STRIP_PREFIX: '${{ env.CHANGELOG_ARTIFACTS }}/'
|
||||
PLUGIN_TARGET: '/arduino-ide/changelog'
|
||||
PLUGIN_BUCKET: ${{ secrets.DOWNLOADS_BUCKET }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
run: |
|
||||
aws s3 sync ${{ env.CHANGELOG_ARTIFACTS }} s3://${{ secrets.DOWNLOADS_BUCKET }}/arduino-ide/changelog
|
10
.github/workflows/i18n-nightly-push.yml
vendored
10
.github/workflows/i18n-nightly-push.yml
vendored
@@ -2,7 +2,7 @@ name: i18n-nightly-push
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: '1.19'
|
||||
GO_VERSION: '1.21'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -29,11 +29,17 @@ jobs:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
|
12
.github/workflows/i18n-weekly-pull.yml
vendored
12
.github/workflows/i18n-weekly-pull.yml
vendored
@@ -2,7 +2,7 @@ name: i18n-weekly-pull
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: '1.19'
|
||||
GO_VERSION: '1.21'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -29,11 +29,17 @@ jobs:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
@@ -46,7 +52,7 @@ jobs:
|
||||
TRANSIFEX_API_KEY: ${{ secrets.TRANSIFEX_API_KEY }}
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
commit-message: Updated translation files
|
||||
title: Update translation files
|
||||
|
2
.github/workflows/push-container-images.yml
vendored
2
.github/workflows/push-container-images.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
images: ${{ matrix.image.registry }}/${{ matrix.image.name }}
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.image.path }}
|
||||
|
19
.github/workflows/sync-labels.yml
vendored
19
.github/workflows/sync-labels.yml
vendored
@@ -19,7 +19,7 @@ on:
|
||||
|
||||
env:
|
||||
CONFIGURATIONS_FOLDER: .github/label-configuration-files
|
||||
CONFIGURATIONS_ARTIFACT: label-configuration-files
|
||||
CONFIGURATIONS_ARTIFACT_PREFIX: label-configuration-file-
|
||||
|
||||
jobs:
|
||||
check:
|
||||
@@ -71,13 +71,13 @@ jobs:
|
||||
file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }}
|
||||
|
||||
- name: Pass configuration files to next job via workflow artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: |
|
||||
*.yaml
|
||||
*.yml
|
||||
if-no-files-found: error
|
||||
name: ${{ env.CONFIGURATIONS_ARTIFACT }}
|
||||
name: ${{ env.CONFIGURATIONS_ARTIFACT_PREFIX }}${{ matrix.filename }}
|
||||
|
||||
sync:
|
||||
needs: download
|
||||
@@ -108,16 +108,17 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download configuration files artifact
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Download configuration file artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ env.CONFIGURATIONS_ARTIFACT }}
|
||||
merge-multiple: true
|
||||
pattern: ${{ env.CONFIGURATIONS_ARTIFACT_PREFIX }}*
|
||||
path: ${{ env.CONFIGURATIONS_FOLDER }}
|
||||
|
||||
- name: Remove unneeded artifact
|
||||
uses: geekyeggo/delete-artifact@v2
|
||||
- name: Remove unneeded artifacts
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
name: ${{ env.CONFIGURATIONS_ARTIFACT }}
|
||||
name: ${{ env.CONFIGURATIONS_ARTIFACT_PREFIX }}*
|
||||
|
||||
- name: Merge label configuration files
|
||||
run: |
|
||||
|
140
.github/workflows/test-javascript.yml
vendored
Normal file
140
.github/workflows/test-javascript.yml
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
name: Test JavaScript
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: '1.21'
|
||||
# See: https://github.com/actions/setup-node/#readme
|
||||
NODE_VERSION: 18.17
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/test-javascript.ya?ml"
|
||||
- "**/.mocharc.js"
|
||||
- "**/.mocharc.jsonc?"
|
||||
- "**/.mocharc.ya?ml"
|
||||
- "**/package.json"
|
||||
- "**/package-lock.json"
|
||||
- "**/yarn.lock"
|
||||
- "tests/testdata/**"
|
||||
- "**/tsconfig.json"
|
||||
- "**.[jt]sx?"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/test-javascript.ya?ml"
|
||||
- "**/.mocharc.js"
|
||||
- "**/.mocharc.jsonc?"
|
||||
- "**/.mocharc.ya?ml"
|
||||
- "**/package.json"
|
||||
- "**/package-lock.json"
|
||||
- "**/yarn.lock"
|
||||
- "tests/testdata/**"
|
||||
- "**/tsconfig.json"
|
||||
- "**.[jt]sx?"
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
|
||||
jobs:
|
||||
run-determination:
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
outputs:
|
||||
result: ${{ steps.determination.outputs.result }}
|
||||
steps:
|
||||
- name: Determine if the rest of the workflow should run
|
||||
id: determination
|
||||
run: |
|
||||
RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x"
|
||||
# The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead.
|
||||
if [[
|
||||
"${{ github.event_name }}" != "create" ||
|
||||
"${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX
|
||||
]]; then
|
||||
# Run the other jobs.
|
||||
RESULT="true"
|
||||
else
|
||||
# There is no need to run the other jobs.
|
||||
RESULT="false"
|
||||
fi
|
||||
|
||||
echo "result=$RESULT" >> $GITHUB_OUTPUT
|
||||
|
||||
test:
|
||||
name: test (${{ matrix.project.path }}, ${{ matrix.operating-system }})
|
||||
needs: run-determination
|
||||
if: needs.run-determination.outputs.result == 'true'
|
||||
runs-on: ${{ matrix.operating-system }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project:
|
||||
- path: .
|
||||
operating-system:
|
||||
- macos-latest
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: yarn
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
# See: https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md#prerequisites
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11.x'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Taskfile
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install Dependencies (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev
|
||||
|
||||
- name: Install npm package dependencies
|
||||
env:
|
||||
# Avoid failure of @vscode/ripgrep installation due to GitHub API rate limiting:
|
||||
# https://github.com/microsoft/vscode-ripgrep#github-api-limit-note
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
yarn install
|
||||
|
||||
- name: Compile TypeScript
|
||||
run: |
|
||||
yarn \
|
||||
--cwd arduino-ide-extension \
|
||||
build
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
# These secrets are optional. Dependent tests will be skipped if not available.
|
||||
CREATE_USERNAME: ${{ secrets.CREATE_USERNAME }}
|
||||
CREATE_PASSWORD: ${{ secrets.CREATE_PASSWORD }}
|
||||
CREATE_CLIENT_SECRET: ${{ secrets.CREATE_CLIENT_SECRET }}
|
||||
run: |
|
||||
yarn test
|
||||
yarn \
|
||||
--cwd arduino-ide-extension \
|
||||
test:slow
|
12
.github/workflows/themes-weekly-pull.yml
vendored
12
.github/workflows/themes-weekly-pull.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: '1.19'
|
||||
GO_VERSION: '1.21'
|
||||
NODE_VERSION: '18.17'
|
||||
|
||||
jobs:
|
||||
@@ -31,11 +31,17 @@ jobs:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
uses: arduino/setup-task@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxkbfile-dev libsecret-1-dev
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
@@ -55,7 +61,7 @@ jobs:
|
||||
run: yarn run themes:generate
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
commit-message: Updated themes
|
||||
title: Update themes
|
||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -7,6 +7,6 @@
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,9 @@
|
||||
|
||||
# Arduino IDE 2.x
|
||||
|
||||
[](https://github.com/arduino/arduino-ide/actions?query=workflow%3A%22Arduino+IDE%22)
|
||||
[](https://github.com/arduino/arduino-ide/actions/workflows/build.yml)
|
||||
[](https://github.com/arduino/arduino-ide/actions/workflows/check-javascript.yml)
|
||||
[](https://github.com/arduino/arduino-ide/actions/workflows/test-javascript.yml)
|
||||
|
||||
This repository contains the source code of the Arduino IDE 2.x. If you're looking for the old IDE, go to the [repository of the 1.x version](https://github.com/arduino/Arduino).
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "arduino-ide-extension",
|
||||
"version": "2.2.2",
|
||||
"version": "2.3.7",
|
||||
"description": "An extension for Theia building the Arduino IDE",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
@@ -13,7 +13,7 @@
|
||||
"download-ls": "node ./scripts/download-ls.js",
|
||||
"download-examples": "node ./scripts/download-examples.js",
|
||||
"generate-protocol": "node ./scripts/generate-protocol.js",
|
||||
"lint": "eslint",
|
||||
"lint": "eslint .",
|
||||
"prebuild": "rimraf lib",
|
||||
"build": "tsc",
|
||||
"build:dev": "yarn build",
|
||||
@@ -24,27 +24,29 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.8.14",
|
||||
"@theia/application-package": "1.41.0",
|
||||
"@theia/core": "1.41.0",
|
||||
"@theia/debug": "1.41.0",
|
||||
"@theia/editor": "1.41.0",
|
||||
"@theia/electron": "1.41.0",
|
||||
"@theia/filesystem": "1.41.0",
|
||||
"@theia/keymaps": "1.41.0",
|
||||
"@theia/markers": "1.41.0",
|
||||
"@theia/messages": "1.41.0",
|
||||
"@theia/monaco": "1.41.0",
|
||||
"@theia/monaco-editor-core": "1.72.3",
|
||||
"@theia/navigator": "1.41.0",
|
||||
"@theia/outline-view": "1.41.0",
|
||||
"@theia/output": "1.41.0",
|
||||
"@theia/plugin-ext": "1.41.0",
|
||||
"@theia/preferences": "1.41.0",
|
||||
"@theia/scm": "1.41.0",
|
||||
"@theia/search-in-workspace": "1.41.0",
|
||||
"@theia/terminal": "1.41.0",
|
||||
"@theia/typehierarchy": "1.41.0",
|
||||
"@theia/workspace": "1.41.0",
|
||||
"@theia/application-package": "1.57.0",
|
||||
"@theia/core": "1.57.0",
|
||||
"@theia/debug": "1.57.0",
|
||||
"@theia/editor": "1.57.0",
|
||||
"@theia/electron": "1.57.0",
|
||||
"@theia/filesystem": "1.57.0",
|
||||
"@theia/keymaps": "1.57.0",
|
||||
"@theia/markers": "1.57.0",
|
||||
"@theia/messages": "1.57.0",
|
||||
"@theia/monaco": "1.57.0",
|
||||
"@theia/monaco-editor-core": "1.83.101",
|
||||
"@theia/navigator": "1.57.0",
|
||||
"@theia/outline-view": "1.57.0",
|
||||
"@theia/output": "1.57.0",
|
||||
"@theia/plugin-ext": "1.57.0",
|
||||
"@theia/plugin-ext-vscode": "1.57.0",
|
||||
"@theia/preferences": "1.57.0",
|
||||
"@theia/scm": "1.57.0",
|
||||
"@theia/search-in-workspace": "1.57.0",
|
||||
"@theia/terminal": "1.57.0",
|
||||
"@theia/test": "1.57.0",
|
||||
"@theia/typehierarchy": "1.57.0",
|
||||
"@theia/workspace": "1.57.0",
|
||||
"@tippyjs/react": "^4.2.5",
|
||||
"@types/auth0-js": "^9.21.3",
|
||||
"@types/btoa": "^1.2.3",
|
||||
@@ -56,24 +58,24 @@
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/p-queue": "^2.3.1",
|
||||
"@types/ps-tree": "^1.1.0",
|
||||
"@types/react-tabs": "^2.3.2",
|
||||
"@types/temp": "^0.8.34",
|
||||
"arduino-serial-plotter-webapp": "0.2.0",
|
||||
"async-mutex": "^0.3.0",
|
||||
"auth0-js": "^9.23.2",
|
||||
"btoa": "^1.2.1",
|
||||
"classnames": "^2.3.1",
|
||||
"cpy": "^10.0.0",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"dateformat": "^3.0.3",
|
||||
"deepmerge": "^4.2.2",
|
||||
"dompurify": "^2.4.7",
|
||||
"drivelist": "^9.2.4",
|
||||
"electron-updater": "^4.6.5",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-json-stable-stringify": "^2.1.0",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"filename-reserved-regex": "^2.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"fqbn": "^1.0.5",
|
||||
"glob": "10.4.4",
|
||||
"google-protobuf": "^3.20.1",
|
||||
"hash.js": "^1.1.7",
|
||||
"is-online": "^10.0.0",
|
||||
@@ -97,7 +99,7 @@
|
||||
"react-markdown": "^8.0.0",
|
||||
"react-perfect-scrollbar": "^1.5.8",
|
||||
"react-select": "^5.6.0",
|
||||
"react-tabs": "^3.1.2",
|
||||
"react-tabs": "^6.1.0",
|
||||
"react-window": "^1.8.6",
|
||||
"semver": "^7.3.2",
|
||||
"string-natural-compare": "^2.0.3",
|
||||
@@ -124,11 +126,11 @@
|
||||
"mockdate": "^3.0.5",
|
||||
"moment": "^2.24.0",
|
||||
"ncp": "^2.0.0",
|
||||
"rimraf": "^2.6.1"
|
||||
"rimraf": "^5.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"grpc-tools": "^1.12.4",
|
||||
"protoc": "^1.0.4"
|
||||
"@pingghost/protoc": "^1.0.2",
|
||||
"grpc-tools": "^1.12.4"
|
||||
},
|
||||
"mocha": {
|
||||
"require": [
|
||||
@@ -170,13 +172,17 @@
|
||||
],
|
||||
"arduino": {
|
||||
"arduino-cli": {
|
||||
"version": "0.35.0-rc.7"
|
||||
"version": "1.2.0"
|
||||
},
|
||||
"arduino-fwuploader": {
|
||||
"version": "2.4.1"
|
||||
},
|
||||
"arduino-language-server": {
|
||||
"version": "0.7.5"
|
||||
"version": {
|
||||
"owner": "arduino",
|
||||
"repo": "arduino-language-server",
|
||||
"commitish": "05ec308"
|
||||
}
|
||||
},
|
||||
"clangd": {
|
||||
"version": "14.0.0"
|
||||
|
@@ -34,7 +34,7 @@
|
||||
}, '');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length == 0) {
|
||||
if (args.length === 0) {
|
||||
console.error('Missing argument to destination file');
|
||||
process.exit(1);
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
// The version to use.
|
||||
const version = '1.10.0';
|
||||
const version = '1.10.2';
|
||||
|
||||
(async () => {
|
||||
const os = require('node:os');
|
||||
|
@@ -50,7 +50,14 @@
|
||||
const suffix = (() => {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return 'macOS_64bit.tar.gz';
|
||||
switch (arch) {
|
||||
case 'arm64':
|
||||
return 'macOS_ARM64.tar.gz';
|
||||
case 'x64':
|
||||
return 'macOS_64bit.tar.gz';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
case 'win32':
|
||||
return 'Windows_64bit.zip';
|
||||
case 'linux': {
|
||||
|
@@ -3,13 +3,15 @@
|
||||
(async () => {
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { mkdirSync, promises: fs } = require('node:fs');
|
||||
const decompress = require('decompress');
|
||||
const unzip = require('decompress-unzip');
|
||||
const { mkdirSync, promises: fs, rmSync, existsSync } = require('node:fs');
|
||||
const { exec } = require('./utils');
|
||||
const glob = require('glob');
|
||||
const { SemVer, gte, valid: validSemVer } = require('semver');
|
||||
const protoc = path.dirname(require('protoc/protoc'));
|
||||
|
||||
const repository = await fs.mkdtemp(path.join(os.tmpdir(), 'arduino-cli-'));
|
||||
const { glob } = require('glob');
|
||||
const { SemVer, gte, valid: validSemVer, eq } = require('semver');
|
||||
// Use a node-protoc fork until apple arm32 is supported
|
||||
// https://github.com/YePpHa/node-protoc/pull/10
|
||||
const protoc = path.dirname(require('@pingghost/protoc/protoc'));
|
||||
|
||||
const { owner, repo, commitish } = (() => {
|
||||
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
||||
@@ -56,11 +58,6 @@
|
||||
return { owner, repo, commitish };
|
||||
})();
|
||||
|
||||
const url = `https://github.com/${owner}/${repo}.git`;
|
||||
console.log(`>>> Cloning repository from '${url}'...`);
|
||||
exec('git', ['clone', url, repository], { logStdout: true });
|
||||
console.log(`<<< Repository cloned.`);
|
||||
|
||||
const { platform } = process;
|
||||
const resourcesFolder = path.join(
|
||||
__dirname,
|
||||
@@ -86,108 +83,207 @@
|
||||
// - `git-snapshot` for local build executed via `task build`. We do not do this.
|
||||
// - rest, we assume it is a valid semver and has the corresponding tagged code, we use the tag to generate the APIs from the `proto` files.
|
||||
/*
|
||||
{
|
||||
"Application": "arduino-cli",
|
||||
"VersionString": "nightly-20210126",
|
||||
"Commit": "079bb6c6",
|
||||
"Status": "alpha",
|
||||
"Date": "2021-01-26T01:46:31Z"
|
||||
}
|
||||
*/
|
||||
const versionObject = JSON.parse(versionJson);
|
||||
let version = versionObject.VersionString;
|
||||
if (validSemVer(version)) {
|
||||
// https://github.com/arduino/arduino-cli/pull/2374
|
||||
if (gte(new SemVer(version, { loose: true }), new SemVer('0.35.0-rc.1'))) {
|
||||
version = `v${version}`;
|
||||
{
|
||||
"Application": "arduino-cli",
|
||||
"VersionString": "nightly-20210126",
|
||||
"Commit": "079bb6c6",
|
||||
"Status": "alpha",
|
||||
"Date": "2021-01-26T01:46:31Z"
|
||||
}
|
||||
console.log(`>>> Checking out tagged version: '${version}'...`);
|
||||
exec('git', ['-C', repository, 'fetch', '--all', '--tags'], {
|
||||
logStdout: true,
|
||||
});
|
||||
exec(
|
||||
'git',
|
||||
['-C', repository, 'checkout', `tags/${version}`, '-b', version],
|
||||
{ logStdout: true }
|
||||
);
|
||||
console.log(`<<< Checked out tagged version: '${version}'.`);
|
||||
} else if (commitish) {
|
||||
console.log(
|
||||
`>>> Checking out commitish from 'package.json': '${commitish}'...`
|
||||
);
|
||||
exec('git', ['-C', repository, 'checkout', commitish], { logStdout: true });
|
||||
console.log(
|
||||
`<<< Checked out commitish from 'package.json': '${commitish}'.`
|
||||
);
|
||||
} else if (versionObject.Commit) {
|
||||
console.log(
|
||||
`>>> Checking out commitish from the CLI: '${versionObject.Commit}'...`
|
||||
);
|
||||
exec('git', ['-C', repository, 'checkout', versionObject.Commit], {
|
||||
logStdout: true,
|
||||
});
|
||||
console.log(
|
||||
`<<< Checked out commitish from the CLI: '${versionObject.Commit}'.`
|
||||
);
|
||||
} else {
|
||||
console.log(`WARN: no 'git checkout'. Generating from the HEAD revision.`);
|
||||
*/
|
||||
const versionObject = JSON.parse(versionJson);
|
||||
|
||||
async function globProtos(folder, pattern = '**/*.proto') {
|
||||
let protos = [];
|
||||
try {
|
||||
const matches = await glob(pattern, { cwd: folder });
|
||||
protos = matches.map((filename) => path.join(folder, filename));
|
||||
} catch (error) {
|
||||
console.log(error.stack ?? error.message);
|
||||
}
|
||||
return protos;
|
||||
}
|
||||
|
||||
console.log('>>> Generating TS/JS API from:');
|
||||
exec('git', ['-C', repository, 'rev-parse', '--abbrev-ref', 'HEAD'], {
|
||||
logStdout: true,
|
||||
});
|
||||
async function getProtosFromRepo(
|
||||
commitish = '',
|
||||
version = '',
|
||||
owner = 'arduino',
|
||||
repo = 'arduino-cli'
|
||||
) {
|
||||
const repoFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'arduino-cli-'));
|
||||
|
||||
const rpc = path.join(repository, 'rpc');
|
||||
const out = path.join(__dirname, '..', 'src', 'node', 'cli-protocol');
|
||||
mkdirSync(out, { recursive: true });
|
||||
const url = `https://github.com/${owner}/${repo}.git`;
|
||||
console.log(`>>> Cloning repository from '${url}'...`);
|
||||
exec('git', ['clone', url, repoFolder], { logStdout: true });
|
||||
console.log(`<<< Repository cloned.`);
|
||||
|
||||
const protos = await new Promise((resolve) =>
|
||||
glob('**/*.proto', { cwd: rpc }, (error, matches) => {
|
||||
if (error) {
|
||||
console.log(error.stack ?? error.message);
|
||||
resolve([]);
|
||||
return;
|
||||
if (validSemVer(version)) {
|
||||
let versionTag = version;
|
||||
// https://github.com/arduino/arduino-cli/pull/2374
|
||||
if (
|
||||
gte(new SemVer(version, { loose: true }), new SemVer('0.35.0-rc.1'))
|
||||
) {
|
||||
versionTag = `v${version}`;
|
||||
}
|
||||
resolve(matches.map((filename) => path.join(rpc, filename)));
|
||||
})
|
||||
);
|
||||
if (!protos || protos.length === 0) {
|
||||
console.log(`Could not find any .proto files under ${rpc}.`);
|
||||
console.log(`>>> Checking out tagged version: '${versionTag}'...`);
|
||||
exec('git', ['-C', repoFolder, 'fetch', '--all', '--tags'], {
|
||||
logStdout: true,
|
||||
});
|
||||
exec(
|
||||
'git',
|
||||
['-C', repoFolder, 'checkout', `tags/${versionTag}`, '-b', versionTag],
|
||||
{ logStdout: true }
|
||||
);
|
||||
console.log(`<<< Checked out tagged version: '${versionTag}'.`);
|
||||
} else if (commitish) {
|
||||
console.log(`>>> Checking out commitish: '${commitish}'...`);
|
||||
exec('git', ['-C', repoFolder, 'checkout', commitish], {
|
||||
logStdout: true,
|
||||
});
|
||||
console.log(`<<< Checked out commitish: '${commitish}'.`);
|
||||
} else {
|
||||
console.log(
|
||||
`WARN: no 'git checkout'. Generating from the HEAD revision.`
|
||||
);
|
||||
}
|
||||
|
||||
const rpcFolder = await fs.mkdtemp(
|
||||
path.join(os.tmpdir(), 'arduino-cli-rpc')
|
||||
);
|
||||
|
||||
// Copy the the repository rpc folder so we can remove the repository
|
||||
await fs.cp(path.join(repoFolder, 'rpc'), path.join(rpcFolder), {
|
||||
recursive: true,
|
||||
});
|
||||
rmSync(repoFolder, { recursive: true, maxRetries: 5, force: true });
|
||||
|
||||
// Patch for https://github.com/arduino/arduino-cli/issues/2755
|
||||
// Google proto files are removed from source since v1.1.0
|
||||
if (!existsSync(path.join(rpcFolder, 'google'))) {
|
||||
// Include packaged google proto files from v1.1.1
|
||||
// See https://github.com/arduino/arduino-cli/pull/2761
|
||||
console.log(`>>> Missing google proto files. Including from v1.1.1...`);
|
||||
const v111ProtoFolder = await getProtosFromZip('1.1.1');
|
||||
|
||||
// Create an return a folder name google in rpcFolder
|
||||
const googleFolder = path.join(rpcFolder, 'google');
|
||||
await fs.cp(path.join(v111ProtoFolder, 'google'), googleFolder, {
|
||||
recursive: true,
|
||||
});
|
||||
console.log(`<<< Included google proto files from v1.1.1.`);
|
||||
}
|
||||
|
||||
return rpcFolder;
|
||||
}
|
||||
|
||||
async function getProtosFromZip(version) {
|
||||
if (!version) {
|
||||
console.log(`Could not download proto files: CLI version not provided.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`>>> Downloading proto files from zip for ${version}.`);
|
||||
|
||||
const url = `https://downloads.arduino.cc/arduino-cli/arduino-cli_${version}_proto.zip`;
|
||||
const protos = await fs.mkdtemp(
|
||||
path.join(os.tmpdir(), 'arduino-cli-proto')
|
||||
);
|
||||
|
||||
const { default: download } = await import('@xhmikosr/downloader');
|
||||
/** @type {import('node:buffer').Buffer} */
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const data = await download(url);
|
||||
|
||||
await decompress(data, protos, {
|
||||
plugins: [unzip()],
|
||||
filter: (file) => file.path.endsWith('.proto'),
|
||||
});
|
||||
|
||||
console.log(
|
||||
`<<< Finished downloading and extracting proto files for ${version}.`
|
||||
);
|
||||
|
||||
return protos;
|
||||
}
|
||||
|
||||
let protosFolder;
|
||||
|
||||
if (commitish) {
|
||||
protosFolder = await getProtosFromRepo(commitish, undefined, owner, repo);
|
||||
} else if (
|
||||
versionObject.VersionString &&
|
||||
validSemVer(versionObject.VersionString)
|
||||
) {
|
||||
const version = versionObject.VersionString;
|
||||
// v1.1.0 does not contains google proto files in zip
|
||||
// See https://github.com/arduino/arduino-cli/issues/2755
|
||||
const isV110 = eq(new SemVer(version, { loose: true }), '1.1.0');
|
||||
protosFolder = isV110
|
||||
? await getProtosFromRepo(undefined, version)
|
||||
: await getProtosFromZip(version);
|
||||
} else if (versionObject.Commit) {
|
||||
protosFolder = await getProtosFromRepo(versionObject.Commit);
|
||||
}
|
||||
|
||||
if (!protosFolder) {
|
||||
console.log(`Could not get proto files: missing commitish or version.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Generate JS code from the `.proto` files.
|
||||
exec(
|
||||
'grpc_tools_node_protoc',
|
||||
[
|
||||
`--js_out=import_style=commonjs,binary:${out}`,
|
||||
`--grpc_out=generate_package_definition:${out}`,
|
||||
'-I',
|
||||
rpc,
|
||||
...protos,
|
||||
],
|
||||
{ logStdout: true }
|
||||
);
|
||||
const protos = await globProtos(protosFolder);
|
||||
|
||||
// Generate the `.d.ts` files for JS.
|
||||
exec(
|
||||
path.join(protoc, `protoc${platform === 'win32' ? '.exe' : ''}`),
|
||||
[
|
||||
`--plugin=protoc-gen-ts=${path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'node_modules',
|
||||
'.bin',
|
||||
`protoc-gen-ts${platform === 'win32' ? '.cmd' : ''}`
|
||||
)}`,
|
||||
`--ts_out=generate_package_definition:${out}`,
|
||||
'-I',
|
||||
rpc,
|
||||
...protos,
|
||||
],
|
||||
{ logStdout: true }
|
||||
);
|
||||
if (!protos || protos.length === 0) {
|
||||
rmSync(protosFolder, { recursive: true, maxRetries: 5, force: true });
|
||||
console.log(`Could not find any .proto files under ${protosFolder}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('>>> Generating TS/JS API from:');
|
||||
|
||||
const out = path.join(__dirname, '..', 'src', 'node', 'cli-protocol');
|
||||
// Must wipe the gen output folder. Otherwise, dangling service implementation remain in IDE2 code,
|
||||
// although it has been removed from the proto file.
|
||||
// For example, https://github.com/arduino/arduino-cli/commit/50a8bf5c3e61d5b661ccfcd6a055e82eeb510859.
|
||||
// rmSync(out, { recursive: true, maxRetries: 5, force: true });
|
||||
mkdirSync(out, { recursive: true });
|
||||
|
||||
try {
|
||||
// Generate JS code from the `.proto` files.
|
||||
exec(
|
||||
'grpc_tools_node_protoc',
|
||||
[
|
||||
`--js_out=import_style=commonjs,binary:${out}`,
|
||||
`--grpc_out=generate_package_definition:${out}`,
|
||||
'-I',
|
||||
protosFolder,
|
||||
...protos,
|
||||
],
|
||||
{ logStdout: true }
|
||||
);
|
||||
|
||||
// Generate the `.d.ts` files for JS.
|
||||
exec(
|
||||
path.join(protoc, `protoc${platform === 'win32' ? '.exe' : ''}`),
|
||||
[
|
||||
`--plugin=protoc-gen-ts=${path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'node_modules',
|
||||
'.bin',
|
||||
`protoc-gen-ts${platform === 'win32' ? '.cmd' : ''}`
|
||||
)}`,
|
||||
`--ts_out=generate_package_definition:${out}`,
|
||||
'-I',
|
||||
protosFolder,
|
||||
...protos,
|
||||
],
|
||||
{ logStdout: true }
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
rmSync(protosFolder, { recursive: true, maxRetries: 5, force: true });
|
||||
}
|
||||
|
||||
console.log('<<< Generation was successful.');
|
||||
})();
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
||||
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import {
|
||||
TabBarToolbarContribution,
|
||||
|
@@ -5,10 +5,8 @@ import { CommandContribution } from '@theia/core/lib/common/command';
|
||||
import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
|
||||
import {
|
||||
FrontendApplicationContribution,
|
||||
FrontendApplication as TheiaFrontendApplication,
|
||||
} from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { LibraryListWidget } from './library/library-list-widget';
|
||||
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
|
||||
import {
|
||||
@@ -91,7 +89,6 @@ import {
|
||||
ArduinoDaemonPath,
|
||||
ArduinoDaemon,
|
||||
} from '../common/protocol/arduino-daemon';
|
||||
import { EditorCommandContribution as TheiaEditorCommandContribution } from '@theia/editor/lib/browser';
|
||||
import {
|
||||
FrontendConnectionStatusService,
|
||||
ApplicationConnectionStatusContribution,
|
||||
@@ -125,7 +122,10 @@ import { OpenSketch } from './contributions/open-sketch';
|
||||
import { Close } from './contributions/close';
|
||||
import { SaveAsSketch } from './contributions/save-as-sketch';
|
||||
import { SaveSketch } from './contributions/save-sketch';
|
||||
import { VerifySketch } from './contributions/verify-sketch';
|
||||
import {
|
||||
CompileSummaryProvider,
|
||||
VerifySketch,
|
||||
} from './contributions/verify-sketch';
|
||||
import { UploadSketch } from './contributions/upload-sketch';
|
||||
import { CommonFrontendContribution } from './theia/core/common-frontend-contribution';
|
||||
import { EditContributions } from './contributions/edit-contributions';
|
||||
@@ -177,10 +177,9 @@ import {
|
||||
import { About } from './contributions/about';
|
||||
import { IconThemeService } from '@theia/core/lib/browser/icon-theme-service';
|
||||
import { TabBarRenderer } from './theia/core/tab-bars';
|
||||
import { EditorCommandContribution } from './theia/editor/editor-command';
|
||||
import { NavigatorTabBarDecorator as TheiaNavigatorTabBarDecorator } from '@theia/navigator/lib/browser/navigator-tab-bar-decorator';
|
||||
import { NavigatorTabBarDecorator } from './theia/navigator/navigator-tab-bar-decorator';
|
||||
import { Debug } from './contributions/debug';
|
||||
import { Debug, DebugDisabledStatusMessageSource } from './contributions/debug';
|
||||
import { Sketchbook } from './contributions/sketchbook';
|
||||
import { DebugFrontendApplicationContribution } from './theia/debug/debug-frontend-application-contribution';
|
||||
import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||
@@ -266,7 +265,7 @@ import {
|
||||
IDEUpdaterDialog,
|
||||
IDEUpdaterDialogProps,
|
||||
} from './dialogs/ide-updater/ide-updater-dialog';
|
||||
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider';
|
||||
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-source';
|
||||
import { MonitorModel } from './monitor-model';
|
||||
import { MonitorManagerProxyClientImpl } from './monitor-manager-proxy-client-impl';
|
||||
import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||
@@ -286,10 +285,6 @@ import { PreferenceTreeGenerator } from './theia/preferences/preference-tree-gen
|
||||
import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator';
|
||||
import { AboutDialog } from './theia/core/about-dialog';
|
||||
import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog';
|
||||
import {
|
||||
SurveyNotificationService,
|
||||
SurveyNotificationServicePath,
|
||||
} from '../common/protocol/survey-service';
|
||||
import { WindowContribution } from './theia/core/window-contribution';
|
||||
import { WindowContribution as TheiaWindowContribution } from '@theia/core/lib/browser/window-contribution';
|
||||
import { CoreErrorHandler } from './contributions/core-error-handler';
|
||||
@@ -365,13 +360,20 @@ import { AutoSelectProgrammer } from './contributions/auto-select-programmer';
|
||||
import { HostedPluginSupport } from './hosted/hosted-plugin-support';
|
||||
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||
import { DebugSessionManager } from './theia/debug/debug-session-manager';
|
||||
import { DebugWidget } from '@theia/debug/lib/browser/view/debug-widget';
|
||||
import { DebugWidget as TheiaDebugWidget } from '@theia/debug/lib/browser/view/debug-widget';
|
||||
import { DebugWidget } from './theia/debug/debug-widget';
|
||||
import { DebugViewModel } from '@theia/debug/lib/browser/view/debug-view-model';
|
||||
import { DebugSessionWidget } from '@theia/debug/lib/browser/view/debug-session-widget';
|
||||
import { DebugConfigurationWidget } from './theia/debug/debug-configuration-widget';
|
||||
import { DebugConfigurationWidget as TheiaDebugConfigurationWidget } from '@theia/debug/lib/browser/view/debug-configuration-widget';
|
||||
import { DebugToolBar } from '@theia/debug/lib/browser/view/debug-toolbar-widget';
|
||||
import { InoHighlight } from './contributions/ino-highlight';
|
||||
|
||||
import {
|
||||
VersionWelcomeDialog,
|
||||
VersionWelcomeDialogProps,
|
||||
} from './dialogs/version-welcome-dialog';
|
||||
import { TestViewContribution as TheiaTestViewContribution } from '@theia/test/lib/browser/view/test-view-contribution';
|
||||
import { TestViewContribution } from './theia/test/test-view-contribution';
|
||||
|
||||
// Hack to fix copy/cut/paste issue after electron version update in Theia.
|
||||
// https://github.com/eclipse-theia/theia/issues/12487
|
||||
@@ -552,15 +554,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
WorkspaceVariableContribution
|
||||
);
|
||||
|
||||
bind(SurveyNotificationService)
|
||||
.toDynamicValue((context) => {
|
||||
return ElectronIpcConnectionProvider.createProxy(
|
||||
context.container,
|
||||
SurveyNotificationServicePath
|
||||
);
|
||||
})
|
||||
.inSingletonScope();
|
||||
|
||||
// Layout and shell customizations.
|
||||
rebind(TheiaOutlineViewContribution)
|
||||
.to(OutlineViewContribution)
|
||||
@@ -768,11 +761,14 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
Contribution.configure(bind, UpdateArduinoState);
|
||||
Contribution.configure(bind, BoardsDataMenuUpdater);
|
||||
Contribution.configure(bind, AutoSelectProgrammer);
|
||||
Contribution.configure(bind, InoHighlight);
|
||||
|
||||
bind(CompileSummaryProvider).toService(VerifySketch);
|
||||
|
||||
bindContributionProvider(bind, StartupTaskProvider);
|
||||
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
|
||||
|
||||
bind(DebugDisabledStatusMessageSource).toService(Debug);
|
||||
|
||||
// Disabled the quick-pick customization from Theia when multiple formatters are available.
|
||||
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
|
||||
bind(MonacoFormattingConflictsContribution).toSelf().inSingletonScope();
|
||||
@@ -831,13 +827,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
);
|
||||
});
|
||||
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/8722
|
||||
// Do not trigger a save on IDE startup if `"editor.autoSave": "on"` was set as a preference.
|
||||
// Note: `"editor.autoSave" was renamed to `"files.autoSave" and `"on"` was replaced with three
|
||||
// different cases, but we treat `!== 'off'` as auto save enabled. (https://github.com/eclipse-theia/theia/issues/10812)
|
||||
bind(EditorCommandContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaEditorCommandContribution).toService(EditorCommandContribution);
|
||||
|
||||
// Silent the badge decoration in the Explorer view.
|
||||
bind(NavigatorTabBarDecorator).toSelf().inSingletonScope();
|
||||
rebind(TheiaNavigatorTabBarDecorator).toService(NavigatorTabBarDecorator);
|
||||
@@ -876,7 +865,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
// Customized debug widget with its customized config <select> to update it programmatically.
|
||||
bind(WidgetFactory)
|
||||
.toDynamicValue(({ container }) => ({
|
||||
id: DebugWidget.ID,
|
||||
id: TheiaDebugWidget.ID,
|
||||
createWidget: () => {
|
||||
const child = new Container({ defaultScope: 'Singleton' });
|
||||
child.parent = container;
|
||||
@@ -998,6 +987,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
title: 'IDEUpdater',
|
||||
});
|
||||
|
||||
bind(VersionWelcomeDialog).toSelf().inSingletonScope();
|
||||
bind(VersionWelcomeDialogProps).toConstantValue({
|
||||
title: 'VersionWelcomeDialog',
|
||||
});
|
||||
|
||||
bind(UserFieldsDialog).toSelf().inSingletonScope();
|
||||
bind(UserFieldsDialogProps).toConstantValue({
|
||||
title: 'UserFields',
|
||||
@@ -1081,4 +1075,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
rebind(TheiaTerminalFrontendContribution).toService(
|
||||
TerminalFrontendContribution
|
||||
);
|
||||
|
||||
// Hides the Test Explorer from the side-bar
|
||||
bind(TestViewContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaTestViewContribution).toService(TestViewContribution);
|
||||
});
|
||||
|
@@ -54,6 +54,10 @@ export function isMonitorWidgetDockPanel(
|
||||
return arg === 'bottom' || arg === 'right';
|
||||
}
|
||||
|
||||
export const defaultAsyncWorkers = 0 as const;
|
||||
export const minAsyncWorkers = defaultAsyncWorkers;
|
||||
export const maxAsyncWorkers = 8 as const;
|
||||
|
||||
type StrictPreferenceSchemaProperties<T extends object> = {
|
||||
[p in keyof T]: PreferenceSchemaProperty;
|
||||
};
|
||||
@@ -79,6 +83,16 @@ const properties: ArduinoPreferenceSchemaProperties = {
|
||||
),
|
||||
default: false,
|
||||
},
|
||||
'arduino.language.asyncWorkers': {
|
||||
type: 'number',
|
||||
description: nls.localize(
|
||||
'arduino/preferences/language.asyncWorkers',
|
||||
'Number of async workers used by the Arduino Language Server (clangd). Background index also uses this many workers. The minimum value is 0, and the maximum is 8. When it is 0, the language server uses all available cores. The default value is 0.'
|
||||
),
|
||||
minimum: minAsyncWorkers,
|
||||
maximum: maxAsyncWorkers,
|
||||
default: defaultAsyncWorkers,
|
||||
},
|
||||
'arduino.compile.verbose': {
|
||||
type: 'boolean',
|
||||
description: nls.localize(
|
||||
@@ -123,6 +137,18 @@ const properties: ArduinoPreferenceSchemaProperties = {
|
||||
'arduino.upload.verify': {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: nls.localize(
|
||||
'arduino/preferences/upload.verify',
|
||||
'After upload, verify that the contents of the memory on the board match the uploaded binary.'
|
||||
),
|
||||
},
|
||||
'arduino.upload.autoVerify': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: nls.localize(
|
||||
'arduino/preferences/upload.autoVerify',
|
||||
"True if the IDE should automatically verify the code before the upload. True by default. When this value is false, IDE does not recompile the code before uploading the binary to the board. It's highly advised to only set this value to false if you know what you are doing."
|
||||
),
|
||||
},
|
||||
'arduino.window.autoScale': {
|
||||
type: 'boolean',
|
||||
@@ -214,6 +240,14 @@ const properties: ArduinoPreferenceSchemaProperties = {
|
||||
),
|
||||
default: 'https://api2.arduino.cc/create',
|
||||
},
|
||||
'arduino.cloud.sharedSpaceID': {
|
||||
type: 'string',
|
||||
description: nls.localize(
|
||||
'arduino/preferences/cloud.sharedSpaceId',
|
||||
'The ID of the Arduino Cloud shared space to load the sketchbook from. If empty, your private space is selected.'
|
||||
),
|
||||
default: '',
|
||||
},
|
||||
'arduino.auth.clientID': {
|
||||
type: 'string',
|
||||
description: nls.localize(
|
||||
@@ -246,14 +280,6 @@ const properties: ArduinoPreferenceSchemaProperties = {
|
||||
),
|
||||
default: 'https://auth.arduino.cc/login#/register',
|
||||
},
|
||||
'arduino.survey.notification': {
|
||||
type: 'boolean',
|
||||
description: nls.localize(
|
||||
'arduino/preferences/survey.notification',
|
||||
'True if users should be notified if a survey is available. True by default.'
|
||||
),
|
||||
default: true,
|
||||
},
|
||||
'arduino.cli.daemon.debug': {
|
||||
type: 'boolean',
|
||||
description: nls.localize(
|
||||
@@ -298,12 +324,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
|
||||
export interface ArduinoConfiguration {
|
||||
'arduino.language.log': boolean;
|
||||
'arduino.language.realTimeDiagnostics': boolean;
|
||||
'arduino.language.asyncWorkers': number;
|
||||
'arduino.compile.verbose': boolean;
|
||||
'arduino.compile.experimental': boolean;
|
||||
'arduino.compile.revealRange': ErrorRevealStrategy;
|
||||
'arduino.compile.warnings': CompilerWarnings;
|
||||
'arduino.upload.verbose': boolean;
|
||||
'arduino.upload.verify': boolean;
|
||||
'arduino.upload.autoVerify': boolean;
|
||||
'arduino.window.autoScale': boolean;
|
||||
'arduino.ide.updateChannel': UpdateChannel;
|
||||
'arduino.ide.updateBaseUrl': string;
|
||||
@@ -314,11 +342,11 @@ export interface ArduinoConfiguration {
|
||||
'arduino.cloud.push.warn': boolean;
|
||||
'arduino.cloud.pushpublic.warn': boolean;
|
||||
'arduino.cloud.sketchSyncEndpoint': string;
|
||||
'arduino.cloud.sharedSpaceID': string;
|
||||
'arduino.auth.clientID': string;
|
||||
'arduino.auth.domain': string;
|
||||
'arduino.auth.audience': string;
|
||||
'arduino.auth.registerUri': string;
|
||||
'arduino.survey.notification': boolean;
|
||||
'arduino.cli.daemon.debug': boolean;
|
||||
'arduino.sketch.inoBlueprint': string;
|
||||
'arduino.checkForUpdates': boolean;
|
||||
|
@@ -3,7 +3,7 @@ import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import {
|
||||
CommandRegistry,
|
||||
CommandContribution,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
|
||||
|
@@ -98,6 +98,7 @@ export class BoardsConfigDialog extends ReactDialog<BoardsConfigDialogState> {
|
||||
}
|
||||
|
||||
override async open(
|
||||
disposeOnResolve = true,
|
||||
params?: EditBoardsConfigActionParams
|
||||
): Promise<BoardsConfig | undefined> {
|
||||
this._searchSet = undefined;
|
||||
@@ -119,7 +120,7 @@ export class BoardsConfigDialog extends ReactDialog<BoardsConfigDialogState> {
|
||||
this._searchSet = params.searchSet.slice();
|
||||
}
|
||||
}
|
||||
return super.open();
|
||||
return super.open(disposeOnResolve);
|
||||
}
|
||||
|
||||
protected override onAfterAttach(msg: Message): void {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { StorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import type {
|
||||
@@ -12,6 +12,7 @@ import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { deepClone, deepFreeze } from '@theia/core/lib/common/objects';
|
||||
import type { Mutable } from '@theia/core/lib/common/types';
|
||||
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
||||
import { FQBN } from 'fqbn';
|
||||
import {
|
||||
BoardDetails,
|
||||
BoardsService,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
Programmer,
|
||||
isBoardIdentifierChangeEvent,
|
||||
isProgrammer,
|
||||
sanitizeFqbn,
|
||||
} from '../../common/protocol';
|
||||
import { notEmpty } from '../../common/utils';
|
||||
import type {
|
||||
@@ -29,6 +31,14 @@ import type {
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { BoardsServiceProvider } from './boards-service-provider';
|
||||
|
||||
export interface SelectConfigOptionParams {
|
||||
readonly fqbn: string;
|
||||
readonly optionsToUpdate: readonly Readonly<{
|
||||
option: string;
|
||||
selectedValue: string;
|
||||
}>[];
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class BoardsDataStore
|
||||
implements
|
||||
@@ -64,7 +74,12 @@ export class BoardsDataStore
|
||||
this.toDispose.pushAll([
|
||||
this.boardsServiceProvider.onBoardsConfigDidChange((event) => {
|
||||
if (isBoardIdentifierChangeEvent(event)) {
|
||||
this.updateSelectedBoardData(event.selectedBoard?.fqbn);
|
||||
this.updateSelectedBoardData(
|
||||
event.selectedBoard?.fqbn,
|
||||
// If the change event comes from toolbar and the FQBN contains custom board options, change the currently selected options
|
||||
// https://github.com/arduino/arduino-ide/issues/1588
|
||||
event.reason === 'toolbar'
|
||||
);
|
||||
}
|
||||
}),
|
||||
this.notificationCenter.onPlatformDidInstall(async ({ item }) => {
|
||||
@@ -116,7 +131,7 @@ export class BoardsDataStore
|
||||
if (!fqbn) {
|
||||
return undefined;
|
||||
} else {
|
||||
const data = await this.getData(fqbn);
|
||||
const data = await this.getData(sanitizeFqbn(fqbn));
|
||||
if (data === BoardsDataStore.Data.EMPTY) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -125,9 +140,22 @@ export class BoardsDataStore
|
||||
}
|
||||
|
||||
private async updateSelectedBoardData(
|
||||
fqbn: string | undefined
|
||||
fqbn: string | undefined,
|
||||
updateConfigOptions = false
|
||||
): Promise<void> {
|
||||
this._selectedBoardData = await this.getSelectedBoardData(fqbn);
|
||||
if (fqbn && updateConfigOptions) {
|
||||
const { options } = new FQBN(fqbn);
|
||||
if (options) {
|
||||
const optionsToUpdate = Object.entries(options).map(([key, value]) => ({
|
||||
option: key,
|
||||
selectedValue: value,
|
||||
}));
|
||||
const params = { fqbn, optionsToUpdate };
|
||||
await this.selectConfigOption(params);
|
||||
this._selectedBoardData = await this.getSelectedBoardData(fqbn); // reload the updated data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onStop(): void {
|
||||
@@ -168,7 +196,7 @@ export class BoardsDataStore
|
||||
return undefined;
|
||||
}
|
||||
const { configOptions } = await this.getData(fqbn);
|
||||
return ConfigOption.decorate(fqbn, configOptions);
|
||||
return new FQBN(fqbn).withConfigOptions(...configOptions).toString();
|
||||
}
|
||||
|
||||
async getData(fqbn: string | undefined): Promise<BoardsDataStore.Data> {
|
||||
@@ -194,6 +222,20 @@ export class BoardsDataStore
|
||||
return data;
|
||||
}
|
||||
|
||||
async reloadBoardData(fqbn: string | undefined): Promise<void> {
|
||||
if (!fqbn) {
|
||||
return;
|
||||
}
|
||||
const key = this.getStorageKey(fqbn);
|
||||
const details = await this.loadBoardDetails(fqbn, true);
|
||||
if (!details) {
|
||||
return;
|
||||
}
|
||||
const data = createDataStoreEntry(details);
|
||||
await this.storageService.setData(key, data);
|
||||
this.fireChanged({ fqbn, data });
|
||||
}
|
||||
|
||||
async selectProgrammer({
|
||||
fqbn,
|
||||
selectedProgrammer,
|
||||
@@ -201,48 +243,63 @@ export class BoardsDataStore
|
||||
fqbn: string;
|
||||
selectedProgrammer: Programmer;
|
||||
}): Promise<boolean> {
|
||||
const storedData = deepClone(await this.getData(fqbn));
|
||||
const sanitizedFQBN = sanitizeFqbn(fqbn);
|
||||
const storedData = deepClone(await this.getData(sanitizedFQBN));
|
||||
const { programmers } = storedData;
|
||||
if (!programmers.find((p) => Programmer.equals(selectedProgrammer, p))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = { ...storedData, selectedProgrammer };
|
||||
await this.setData({ fqbn, data });
|
||||
this.fireChanged({ fqbn, data });
|
||||
const change: BoardsDataStoreChange = {
|
||||
fqbn: sanitizedFQBN,
|
||||
data: { ...storedData, selectedProgrammer },
|
||||
};
|
||||
await this.setData(change);
|
||||
this.fireChanged(change);
|
||||
return true;
|
||||
}
|
||||
|
||||
async selectConfigOption({
|
||||
fqbn,
|
||||
option,
|
||||
selectedValue,
|
||||
}: {
|
||||
fqbn: string;
|
||||
option: string;
|
||||
selectedValue: string;
|
||||
}): Promise<boolean> {
|
||||
const data = deepClone(await this.getData(fqbn));
|
||||
const { configOptions } = data;
|
||||
const configOption = configOptions.find((c) => c.option === option);
|
||||
if (!configOption) {
|
||||
async selectConfigOption(params: SelectConfigOptionParams): Promise<boolean> {
|
||||
const { fqbn, optionsToUpdate } = params;
|
||||
if (!optionsToUpdate.length) {
|
||||
return false;
|
||||
}
|
||||
let updated = false;
|
||||
for (const value of configOption.values) {
|
||||
const mutable: Mutable<ConfigValue> = value;
|
||||
if (mutable.value === selectedValue) {
|
||||
mutable.selected = true;
|
||||
updated = true;
|
||||
} else {
|
||||
mutable.selected = false;
|
||||
|
||||
const sanitizedFQBN = sanitizeFqbn(fqbn);
|
||||
const mutableData = deepClone(await this.getData(sanitizedFQBN));
|
||||
let didChange = false;
|
||||
|
||||
for (const { option, selectedValue } of optionsToUpdate) {
|
||||
const { configOptions } = mutableData;
|
||||
const configOption = configOptions.find((c) => c.option === option);
|
||||
if (configOption) {
|
||||
const configOptionValueIndex = configOption.values.findIndex(
|
||||
(configOptionValue) => configOptionValue.value === selectedValue
|
||||
);
|
||||
if (configOptionValueIndex >= 0) {
|
||||
// unselect all
|
||||
configOption.values
|
||||
.map((value) => value as Mutable<ConfigValue>)
|
||||
.forEach((value) => (value.selected = false));
|
||||
const mutableConfigValue: Mutable<ConfigValue> =
|
||||
configOption.values[configOptionValueIndex];
|
||||
// make the new value `selected`
|
||||
mutableConfigValue.selected = true;
|
||||
didChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!updated) {
|
||||
|
||||
if (!didChange) {
|
||||
return false;
|
||||
}
|
||||
await this.setData({ fqbn, data });
|
||||
this.fireChanged({ fqbn, data });
|
||||
|
||||
const change: BoardsDataStoreChange = {
|
||||
fqbn: sanitizedFQBN,
|
||||
data: mutableData,
|
||||
};
|
||||
await this.setData(change);
|
||||
this.fireChanged(change);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -256,9 +313,15 @@ export class BoardsDataStore
|
||||
return `.arduinoIDE-configOptions-${fqbn}`;
|
||||
}
|
||||
|
||||
async loadBoardDetails(fqbn: string): Promise<BoardDetails | undefined> {
|
||||
async loadBoardDetails(
|
||||
fqbn: string,
|
||||
forceRefresh = false
|
||||
): Promise<BoardDetails | undefined> {
|
||||
try {
|
||||
const details = await this.boardsService.getBoardDetails({ fqbn });
|
||||
const details = await this.boardsService.getBoardDetails({
|
||||
fqbn,
|
||||
forceRefresh,
|
||||
});
|
||||
return details;
|
||||
} catch (err) {
|
||||
if (
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { StorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import {
|
||||
@@ -12,6 +12,7 @@ import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { deepClone } from '@theia/core/lib/common/objects';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import type { Mutable } from '@theia/core/lib/common/types';
|
||||
import { inject, injectable, optional } from '@theia/core/shared/inversify';
|
||||
@@ -21,31 +22,32 @@ import {
|
||||
} from '@theia/output/lib/browser/output-channel';
|
||||
import {
|
||||
BoardIdentifier,
|
||||
boardIdentifierEquals,
|
||||
BoardUserField,
|
||||
BoardWithPackage,
|
||||
BoardsConfig,
|
||||
BoardsConfigChangeEvent,
|
||||
BoardsPackage,
|
||||
BoardsService,
|
||||
BoardUserField,
|
||||
BoardWithPackage,
|
||||
DetectedPorts,
|
||||
Port,
|
||||
PortIdentifier,
|
||||
boardIdentifierEquals,
|
||||
emptyBoardsConfig,
|
||||
isBoardIdentifier,
|
||||
isBoardIdentifierChangeEvent,
|
||||
isPortIdentifier,
|
||||
isPortIdentifierChangeEvent,
|
||||
Port,
|
||||
PortIdentifier,
|
||||
portIdentifierEquals,
|
||||
sanitizeFqbn,
|
||||
serializePlatformIdentifier,
|
||||
} from '../../common/protocol';
|
||||
import {
|
||||
BoardList,
|
||||
BoardListHistory,
|
||||
createBoardList,
|
||||
EditBoardsConfigActionParams,
|
||||
isBoardListHistory,
|
||||
SelectBoardsConfigActionParams,
|
||||
createBoardList,
|
||||
isBoardListHistory,
|
||||
} from '../../common/protocol/board-list';
|
||||
import type { Defined } from '../../common/types';
|
||||
import type {
|
||||
@@ -104,6 +106,21 @@ type BoardListHistoryUpdateResult =
|
||||
type BoardToSelect = BoardIdentifier | undefined | 'ignore-board';
|
||||
type PortToSelect = PortIdentifier | undefined | 'ignore-port';
|
||||
|
||||
function sanitizeBoardToSelectFQBN(board: BoardToSelect): BoardToSelect {
|
||||
if (isBoardIdentifier(board)) {
|
||||
return sanitizeBoardIdentifierFQBN(board);
|
||||
}
|
||||
return board;
|
||||
}
|
||||
function sanitizeBoardIdentifierFQBN(board: BoardIdentifier): BoardIdentifier {
|
||||
if (board.fqbn) {
|
||||
const copy: Mutable<BoardIdentifier> = deepClone(board);
|
||||
copy.fqbn = sanitizeFqbn(board.fqbn);
|
||||
return copy;
|
||||
}
|
||||
return board;
|
||||
}
|
||||
|
||||
interface UpdateBoardListHistoryParams {
|
||||
readonly portToSelect: PortToSelect;
|
||||
readonly boardToSelect: BoardToSelect;
|
||||
@@ -136,6 +153,9 @@ export interface BoardListUIActions {
|
||||
}
|
||||
export type BoardListUI = BoardList & BoardListUIActions;
|
||||
|
||||
export type BoardsConfigChangeEventUI = BoardsConfigChangeEvent &
|
||||
Readonly<{ reason?: UpdateBoardsConfigReason }>;
|
||||
|
||||
@injectable()
|
||||
export class BoardListDumper implements Disposable {
|
||||
@inject(OutputChannelManager)
|
||||
@@ -190,7 +210,7 @@ export class BoardsServiceProvider
|
||||
private _ready = new Deferred<void>();
|
||||
|
||||
private readonly boardsConfigDidChangeEmitter =
|
||||
new Emitter<BoardsConfigChangeEvent>();
|
||||
new Emitter<BoardsConfigChangeEventUI>();
|
||||
readonly onBoardsConfigDidChange = this.boardsConfigDidChangeEmitter.event;
|
||||
|
||||
private readonly boardListDidChangeEmitter = new Emitter<BoardListUI>();
|
||||
@@ -353,7 +373,8 @@ export class BoardsServiceProvider
|
||||
portToSelect !== 'ignore-port' &&
|
||||
!portIdentifierEquals(portToSelect, previousSelectedPort);
|
||||
const boardDidChangeEvent = boardDidChange
|
||||
? { selectedBoard: boardToSelect, previousSelectedBoard }
|
||||
? // The change event must always contain any custom board options. Hence the board to select is not sanitized.
|
||||
{ selectedBoard: boardToSelect, previousSelectedBoard }
|
||||
: undefined;
|
||||
const portDidChangeEvent = portDidChange
|
||||
? { selectedPort: portToSelect, previousSelectedPort }
|
||||
@@ -374,16 +395,31 @@ export class BoardsServiceProvider
|
||||
return false;
|
||||
}
|
||||
|
||||
this.maybeUpdateBoardListHistory({ portToSelect, boardToSelect });
|
||||
this.maybeUpdateBoardsData({ boardToSelect, reason });
|
||||
// unlike for the board change event, every persistent state must not contain custom board config options in the FQBN
|
||||
const sanitizedBoardToSelect = sanitizeBoardToSelectFQBN(boardToSelect);
|
||||
|
||||
this.maybeUpdateBoardListHistory({
|
||||
portToSelect,
|
||||
boardToSelect: sanitizedBoardToSelect,
|
||||
});
|
||||
this.maybeUpdateBoardsData({
|
||||
boardToSelect: sanitizedBoardToSelect,
|
||||
reason,
|
||||
});
|
||||
|
||||
if (isBoardIdentifierChangeEvent(event)) {
|
||||
this._boardsConfig.selectedBoard = event.selectedBoard;
|
||||
this._boardsConfig.selectedBoard = event.selectedBoard
|
||||
? sanitizeBoardIdentifierFQBN(event.selectedBoard)
|
||||
: event.selectedBoard;
|
||||
}
|
||||
if (isPortIdentifierChangeEvent(event)) {
|
||||
this._boardsConfig.selectedPort = event.selectedPort;
|
||||
}
|
||||
|
||||
if (reason) {
|
||||
event = Object.assign(event, { reason });
|
||||
}
|
||||
|
||||
this.boardsConfigDidChangeEmitter.fire(event);
|
||||
this.refreshBoardList();
|
||||
this.saveState();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
|
@@ -20,6 +20,7 @@ import {
|
||||
} from '../../common/protocol';
|
||||
import type { BoardList } from '../../common/protocol/board-list';
|
||||
import { BoardsListWidget } from '../boards/boards-list-widget';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import {
|
||||
ArduinoMenus,
|
||||
@@ -39,6 +40,8 @@ export class BoardSelection extends SketchContribution {
|
||||
private readonly menuModelRegistry: MenuModelRegistry;
|
||||
@inject(NotificationCenter)
|
||||
private readonly notificationCenter: NotificationCenter;
|
||||
@inject(BoardsDataStore)
|
||||
private readonly boardsDataStore: BoardsDataStore;
|
||||
@inject(BoardsService)
|
||||
private readonly boardsService: BoardsService;
|
||||
@inject(BoardsServiceProvider)
|
||||
@@ -74,6 +77,29 @@ SN: ${SN}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
registry.registerCommand(BoardSelection.Commands.RELOAD_BOARD_DATA, {
|
||||
execute: async () => {
|
||||
const selectedFqbn =
|
||||
this.boardsServiceProvider.boardList.boardsConfig.selectedBoard?.fqbn;
|
||||
let message: string;
|
||||
|
||||
if (selectedFqbn) {
|
||||
await this.boardsDataStore.reloadBoardData(selectedFqbn);
|
||||
message = nls.localize(
|
||||
'arduino/board/boardDataReloaded',
|
||||
'Board data reloaded.'
|
||||
);
|
||||
} else {
|
||||
message = nls.localize(
|
||||
'arduino/board/selectBoardToReload',
|
||||
'Please select a board first.'
|
||||
);
|
||||
}
|
||||
|
||||
this.messageService.info(message, { timeout: 2000 });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override onStart(): void {
|
||||
@@ -151,6 +177,21 @@ SN: ${SN}
|
||||
)
|
||||
);
|
||||
|
||||
const reloadBoardData = {
|
||||
commandId: BoardSelection.Commands.RELOAD_BOARD_DATA.id,
|
||||
label: nls.localize('arduino/board/reloadBoardData', 'Reload Board Data'),
|
||||
order: '102',
|
||||
};
|
||||
this.menuModelRegistry.registerMenuAction(
|
||||
ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP,
|
||||
reloadBoardData
|
||||
);
|
||||
this.toDisposeBeforeMenuRebuild.push(
|
||||
Disposable.create(() =>
|
||||
this.menuModelRegistry.unregisterMenuAction(reloadBoardData)
|
||||
)
|
||||
);
|
||||
|
||||
const getBoardInfo = {
|
||||
commandId: BoardSelection.Commands.GET_BOARD_INFO.id,
|
||||
label: nls.localize('arduino/board/getBoardInfo', 'Get Board Info'),
|
||||
@@ -361,5 +402,8 @@ SN: ${SN}
|
||||
export namespace BoardSelection {
|
||||
export namespace Commands {
|
||||
export const GET_BOARD_INFO: Command = { id: 'arduino-get-board-info' };
|
||||
export const RELOAD_BOARD_DATA: Command = {
|
||||
id: 'arduino-reload-board-data',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -87,8 +87,7 @@ export class BoardsDataMenuUpdater extends Contribution {
|
||||
execute: () =>
|
||||
this.boardsDataStore.selectConfigOption({
|
||||
fqbn,
|
||||
option,
|
||||
selectedValue: value.value,
|
||||
optionsToUpdate: [{ option, selectedValue: value.value }],
|
||||
}),
|
||||
isToggled: () => value.selected,
|
||||
};
|
||||
|
@@ -37,11 +37,15 @@ export class BurnBootloader extends CoreServiceContribution {
|
||||
'arduino/bootloader/burningBootloader',
|
||||
'Burning bootloader...'
|
||||
),
|
||||
task: (progressId, coreService) =>
|
||||
coreService.burnBootloader({
|
||||
...options,
|
||||
progressId,
|
||||
}),
|
||||
task: (progressId, coreService, token) =>
|
||||
coreService.burnBootloader(
|
||||
{
|
||||
...options,
|
||||
progressId,
|
||||
},
|
||||
token
|
||||
),
|
||||
cancelable: true,
|
||||
});
|
||||
this.messageService.info(
|
||||
nls.localize(
|
||||
|
@@ -3,10 +3,14 @@ import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
IDEUpdater,
|
||||
LAST_USED_IDE_VERSION,
|
||||
SKIP_IDE_VERSION,
|
||||
} from '../../common/protocol/ide-updater';
|
||||
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
|
||||
import { Contribution } from './contribution';
|
||||
import { VersionWelcomeDialog } from '../dialogs/version-welcome-dialog';
|
||||
import { AppService } from '../app-service';
|
||||
import { SemVer } from 'semver';
|
||||
|
||||
@injectable()
|
||||
export class CheckForIDEUpdates extends Contribution {
|
||||
@@ -16,9 +20,15 @@ export class CheckForIDEUpdates extends Contribution {
|
||||
@inject(IDEUpdaterDialog)
|
||||
private readonly updaterDialog: IDEUpdaterDialog;
|
||||
|
||||
@inject(VersionWelcomeDialog)
|
||||
private readonly versionWelcomeDialog: VersionWelcomeDialog;
|
||||
|
||||
@inject(LocalStorageService)
|
||||
private readonly localStorage: LocalStorageService;
|
||||
|
||||
@inject(AppService)
|
||||
private readonly appService: AppService;
|
||||
|
||||
override onStart(): void {
|
||||
this.preferences.onPreferenceChanged(
|
||||
({ preferenceName, newValue, oldValue }) => {
|
||||
@@ -36,7 +46,7 @@ export class CheckForIDEUpdates extends Contribution {
|
||||
);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
override async onReady(): Promise<void> {
|
||||
this.updater
|
||||
.init(
|
||||
this.preferences.get('arduino.ide.updateChannel'),
|
||||
@@ -49,12 +59,18 @@ export class CheckForIDEUpdates extends Contribution {
|
||||
return this.updater.checkForUpdates(true);
|
||||
})
|
||||
.then(async (updateInfo) => {
|
||||
if (!updateInfo) return;
|
||||
if (!updateInfo) {
|
||||
const isNewVersion = await this.isNewStableVersion();
|
||||
if (isNewVersion) {
|
||||
this.versionWelcomeDialog.open();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const versionToSkip = await this.localStorage.getData<string>(
|
||||
SKIP_IDE_VERSION
|
||||
);
|
||||
if (versionToSkip === updateInfo.version) return;
|
||||
this.updaterDialog.open(updateInfo);
|
||||
this.updaterDialog.open(true, updateInfo);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.messageService.error(
|
||||
@@ -64,6 +80,44 @@ export class CheckForIDEUpdates extends Contribution {
|
||||
e.message
|
||||
)
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
this.setCurrentIDEVersion();
|
||||
});
|
||||
}
|
||||
|
||||
private async setCurrentIDEVersion(): Promise<void> {
|
||||
try {
|
||||
const { appVersion } = await this.appService.info();
|
||||
const currSemVer = new SemVer(appVersion ?? '');
|
||||
this.localStorage.setData(LAST_USED_IDE_VERSION, currSemVer.format());
|
||||
} catch {
|
||||
// ignore invalid versions
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is running a new IDE version for the first time.
|
||||
* @returns true if the current IDE version is greater than the last used version
|
||||
* and both are non-prerelease versions.
|
||||
*/
|
||||
private async isNewStableVersion(): Promise<boolean> {
|
||||
try {
|
||||
const { appVersion } = await this.appService.info();
|
||||
const prevVersion = await this.localStorage.getData<string>(
|
||||
LAST_USED_IDE_VERSION
|
||||
);
|
||||
|
||||
const prevSemVer = new SemVer(prevVersion ?? '');
|
||||
const currSemVer = new SemVer(appVersion ?? '');
|
||||
|
||||
if (prevSemVer.prerelease.length || currSemVer.prerelease.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currSemVer.compare(prevSemVer) === 1;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import { Dialog } from '@theia/core/lib/browser/dialogs';
|
||||
import type {
|
||||
FrontendApplication,
|
||||
OnWillStopAction,
|
||||
} from '@theia/core/lib/browser/frontend-application';
|
||||
import type { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import type { OnWillStopAction } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import type { MaybePromise } from '@theia/core/lib/common/types';
|
||||
|
@@ -779,7 +779,7 @@ export class CompilerErrors
|
||||
return undefined;
|
||||
} else {
|
||||
return this.editorManager
|
||||
.getByUri(new URI(uriOrWidget))
|
||||
.getByUri(new URI(uriOrWidget.toString()))
|
||||
.then((editor) => {
|
||||
if (editor) {
|
||||
return this.monacoEditor(editor);
|
||||
|
@@ -1,83 +1,87 @@
|
||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import {
|
||||
KeybindingContribution,
|
||||
KeybindingRegistry,
|
||||
} from '@theia/core/lib/browser/keybinding';
|
||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||
import { OpenerService, open } from '@theia/core/lib/browser/opener-service';
|
||||
import { Saveable } from '@theia/core/lib/browser/saveable';
|
||||
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
||||
import {
|
||||
TabBarToolbarContribution,
|
||||
TabBarToolbarRegistry,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||
import {
|
||||
Command,
|
||||
CommandContribution,
|
||||
CommandRegistry,
|
||||
CommandService,
|
||||
} from '@theia/core/lib/common/command';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import {
|
||||
MenuContribution,
|
||||
MenuModelRegistry,
|
||||
} from '@theia/core/lib/common/menu';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { MaybePromise, isObject } from '@theia/core/lib/common/types';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
interfaces,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { Saveable } from '@theia/core/lib/browser/saveable';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||
import { OutputChannelSeverity } from '@theia/output/lib/browser/output-channel';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { userAbort } from '../../common/nls';
|
||||
import {
|
||||
MenuModelRegistry,
|
||||
MenuContribution,
|
||||
} from '@theia/core/lib/common/menu';
|
||||
CoreError,
|
||||
CoreService,
|
||||
FileSystemExt,
|
||||
ResponseServiceClient,
|
||||
Sketch,
|
||||
SketchesService,
|
||||
} from '../../common/protocol';
|
||||
import {
|
||||
KeybindingRegistry,
|
||||
KeybindingContribution,
|
||||
} from '@theia/core/lib/browser/keybinding';
|
||||
import {
|
||||
TabBarToolbarContribution,
|
||||
TabBarToolbarRegistry,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import {
|
||||
FrontendApplicationContribution,
|
||||
FrontendApplication,
|
||||
} from '@theia/core/lib/browser/frontend-application';
|
||||
import {
|
||||
Command,
|
||||
CommandRegistry,
|
||||
CommandContribution,
|
||||
CommandService,
|
||||
} from '@theia/core/lib/common/command';
|
||||
ExecuteWithProgress,
|
||||
UserAbortApplicationError,
|
||||
} from '../../common/protocol/progressible';
|
||||
import { ArduinoPreferences } from '../arduino-preferences';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { ConfigServiceClient } from '../config/config-service-client';
|
||||
import { DialogService } from '../dialog-service';
|
||||
import { SettingsService } from '../dialogs/settings/settings';
|
||||
import {
|
||||
CurrentSketch,
|
||||
SketchesServiceClientImpl,
|
||||
} from '../sketches-service-client-impl';
|
||||
import {
|
||||
SketchesService,
|
||||
FileSystemExt,
|
||||
Sketch,
|
||||
CoreService,
|
||||
CoreError,
|
||||
ResponseServiceClient,
|
||||
} from '../../common/protocol';
|
||||
import { ArduinoPreferences } from '../arduino-preferences';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { nls } from '@theia/core';
|
||||
import { OutputChannelManager } from '../theia/output/output-channel';
|
||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||
import { ExecuteWithProgress } from '../../common/protocol/progressible';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
|
||||
import { WorkspaceService } from '../theia/workspace/workspace-service';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { ConfigServiceClient } from '../config/config-service-client';
|
||||
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
||||
import { DialogService } from '../dialog-service';
|
||||
import { ApplicationConnectionStatusContribution } from '../theia/core/connection-status-service';
|
||||
import { OutputChannelManager } from '../theia/output/output-channel';
|
||||
import { WorkspaceService } from '../theia/workspace/workspace-service';
|
||||
|
||||
export {
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
MenuModelRegistry,
|
||||
Sketch,
|
||||
TabBarToolbarRegistry,
|
||||
URI,
|
||||
Sketch,
|
||||
open,
|
||||
};
|
||||
|
||||
@@ -247,6 +251,12 @@ export abstract class CoreServiceContribution extends SketchContribution {
|
||||
}
|
||||
|
||||
protected handleError(error: unknown): void {
|
||||
if (isObject(error) && UserAbortApplicationError.is(error)) {
|
||||
this.outputChannelManager
|
||||
.getChannel('Arduino')
|
||||
.appendLine(userAbort, OutputChannelSeverity.Warning);
|
||||
return;
|
||||
}
|
||||
this.tryToastErrorMessage(error);
|
||||
}
|
||||
|
||||
@@ -293,7 +303,13 @@ export abstract class CoreServiceContribution extends SketchContribution {
|
||||
protected async doWithProgress<T>(options: {
|
||||
progressText: string;
|
||||
keepOutput?: boolean;
|
||||
task: (progressId: string, coreService: CoreService) => Promise<T>;
|
||||
task: (
|
||||
progressId: string,
|
||||
coreService: CoreService,
|
||||
cancellationToken?: CancellationToken
|
||||
) => Promise<T>;
|
||||
// false by default
|
||||
cancelable?: boolean;
|
||||
}): Promise<T> {
|
||||
const toDisposeOnComplete = new DisposableCollection(
|
||||
this.maybeActivateMonitorWidget()
|
||||
@@ -306,8 +322,10 @@ export abstract class CoreServiceContribution extends SketchContribution {
|
||||
messageService: this.messageService,
|
||||
responseService: this.responseService,
|
||||
progressText,
|
||||
run: ({ progressId }) => task(progressId, this.coreService),
|
||||
run: ({ progressId, cancellationToken }) =>
|
||||
task(progressId, this.coreService, cancellationToken),
|
||||
keepOutput,
|
||||
cancelable: options.cancelable,
|
||||
});
|
||||
toDisposeOnComplete.dispose();
|
||||
return result;
|
||||
|
@@ -64,8 +64,26 @@ interface StartDebugParams {
|
||||
}
|
||||
type StartDebugResult = boolean;
|
||||
|
||||
export const DebugDisabledStatusMessageSource = Symbol(
|
||||
'DebugDisabledStatusMessageSource'
|
||||
);
|
||||
export interface DebugDisabledStatusMessageSource {
|
||||
/**
|
||||
* `undefined` if debugging is enabled (for the currently selected board + programmer + config options).
|
||||
* Otherwise, it's the human readable message why it's disabled.
|
||||
*/
|
||||
get message(): string | undefined;
|
||||
/**
|
||||
* Emits an event when {@link message} changes.
|
||||
*/
|
||||
get onDidChangeMessage(): Event<string | undefined>;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class Debug extends SketchContribution {
|
||||
export class Debug
|
||||
extends SketchContribution
|
||||
implements DebugDisabledStatusMessageSource
|
||||
{
|
||||
@inject(HostedPluginSupport)
|
||||
private readonly hostedPluginSupport: HostedPluginSupport;
|
||||
@inject(NotificationCenter)
|
||||
@@ -83,10 +101,10 @@ export class Debug extends SketchContribution {
|
||||
* If `undefined`, debugging is enabled. Otherwise, the human-readable reason why it's disabled.
|
||||
*/
|
||||
private _message?: string = noBoardSelected; // Initial pessimism.
|
||||
private didChangeMessageEmitter = new Emitter<string | undefined>();
|
||||
private onDidChangeMessage = this.didChangeMessageEmitter.event;
|
||||
private readonly didChangeMessageEmitter = new Emitter<string | undefined>();
|
||||
readonly onDidChangeMessage = this.didChangeMessageEmitter.event;
|
||||
|
||||
private get message(): string | undefined {
|
||||
get message(): string | undefined {
|
||||
return this._message;
|
||||
}
|
||||
private set message(message: string | undefined) {
|
||||
@@ -271,8 +289,8 @@ export class Debug extends SketchContribution {
|
||||
): Promise<boolean> {
|
||||
if (err instanceof Error) {
|
||||
try {
|
||||
const tempBuildPaths = await this.sketchesService.tempBuildPath(sketch);
|
||||
return tempBuildPaths.some((tempBuildPath) =>
|
||||
const buildPaths = await this.sketchesService.getBuildPath(sketch);
|
||||
return buildPaths.some((tempBuildPath) =>
|
||||
err.message.includes(tempBuildPath)
|
||||
);
|
||||
} catch {
|
||||
@@ -349,6 +367,8 @@ export namespace Debug {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves with the FQBN to use for the `debug --info --programmer p --fqbn $FQBN` command. Otherwise, rejects.
|
||||
*
|
||||
* (non-API)
|
||||
*/
|
||||
export async function isDebugEnabled(
|
||||
@@ -378,12 +398,9 @@ export async function isDebugEnabled(
|
||||
`Failed to append boards config to the FQBN. Original FQBN was: ${fqbn}`
|
||||
);
|
||||
}
|
||||
if (!data.selectedProgrammer) {
|
||||
throw new Error(noProgrammerSelectedFor(board.name));
|
||||
}
|
||||
const params = {
|
||||
fqbn: fqbnWithConfig,
|
||||
programmer: data.selectedProgrammer.id,
|
||||
programmer: data.selectedProgrammer?.id,
|
||||
};
|
||||
try {
|
||||
const debugFqbn = await checkDebugEnabled(params);
|
||||
@@ -423,13 +440,3 @@ export function debuggingNotSupported(boardName: string): string {
|
||||
boardName
|
||||
);
|
||||
}
|
||||
/**
|
||||
* (non-API)
|
||||
*/
|
||||
export function noProgrammerSelectedFor(boardName: string): string {
|
||||
return nls.localize(
|
||||
'arduino/debug/noProgrammerSelectedFor',
|
||||
"No programmer selected for '{0}'",
|
||||
boardName
|
||||
);
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
|
||||
import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
|
||||
import { ICodeEditorService } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
|
||||
import type { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import type { StandaloneCodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditor';
|
||||
import {
|
||||
Contribution,
|
||||
Command,
|
||||
@@ -10,17 +14,11 @@ import {
|
||||
CommandRegistry,
|
||||
} from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import type { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
|
||||
import type { StandaloneCodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditor';
|
||||
|
||||
// TODO: [macOS]: to remove `Start Dictation...` and `Emoji & Symbol` see this thread: https://github.com/electron/electron/issues/8283#issuecomment-269522072
|
||||
// Depends on https://github.com/eclipse-theia/theia/pull/7964
|
||||
@injectable()
|
||||
export class EditContributions extends Contribution {
|
||||
@inject(MonacoEditorService)
|
||||
private readonly codeEditorService: MonacoEditorService;
|
||||
|
||||
@inject(ClipboardService)
|
||||
private readonly clipboardService: ClipboardService;
|
||||
|
||||
@@ -208,9 +206,10 @@ ${value}
|
||||
protected async current(): Promise<
|
||||
ICodeEditor | StandaloneCodeEditor | undefined
|
||||
> {
|
||||
const codeEditorService = StandaloneServices.get(ICodeEditorService);
|
||||
return (
|
||||
this.codeEditorService.getFocusedCodeEditor() ||
|
||||
this.codeEditorService.getActiveCodeEditor() ||
|
||||
codeEditorService.getFocusedCodeEditor() ||
|
||||
codeEditorService.getActiveCodeEditor() ||
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { MaybePromise } from '@theia/core';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { Formatter } from '../../common/protocol/formatter';
|
||||
@@ -14,14 +15,13 @@ export class Format
|
||||
@inject(Formatter)
|
||||
private readonly formatter: Formatter;
|
||||
|
||||
override onStart(): void {
|
||||
override onStart(): MaybePromise<void> {
|
||||
monaco.languages.registerDocumentRangeFormattingEditProvider(
|
||||
InoSelector,
|
||||
this
|
||||
);
|
||||
monaco.languages.registerDocumentFormattingEditProvider(InoSelector, this);
|
||||
}
|
||||
|
||||
async provideDocumentRangeFormattingEdits(
|
||||
model: monaco.editor.ITextModel,
|
||||
range: monaco.Range,
|
||||
|
@@ -1,558 +0,0 @@
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { DEFAULT_WORD_REGEXP } from '@theia/monaco-editor-core/esm/vs/editor/common/core/wordHelper';
|
||||
import { StandardTokenType } from '@theia/monaco-editor-core/esm/vs/editor/common/encodedTokenAttributes';
|
||||
import { TokenizationTextModelPart } from '@theia/monaco-editor-core/esm/vs/editor/common/model/tokenizationTextModelPart';
|
||||
import { ITokenizationTextModelPart } from '@theia/monaco-editor-core/esm/vs/editor/common/tokenizationTextModelPart';
|
||||
import { SemanticTokensBuilder } from '@theia/plugin-ext/lib/plugin/types-impl';
|
||||
import { HostedPluginSupport } from '../hosted/hosted-plugin-support';
|
||||
import { InoSelector } from '../selectors';
|
||||
import { SketchContribution } from './contribution';
|
||||
|
||||
interface TokenizationOwner {
|
||||
readonly tokenization: ITokenizationTextModelPart;
|
||||
}
|
||||
|
||||
function hasTokenization(arg: unknown): arg is TokenizationOwner {
|
||||
return (
|
||||
typeof arg === 'object' &&
|
||||
arg !== null &&
|
||||
(<TokenizationOwner>arg).tokenization !== undefined &&
|
||||
(<TokenizationOwner>arg).tokenization instanceof TokenizationTextModelPart
|
||||
);
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class InoHighlight
|
||||
extends SketchContribution
|
||||
implements monaco.languages.DocumentSemanticTokensProvider
|
||||
{
|
||||
@inject(HostedPluginSupport)
|
||||
private readonly hostedPluginSupport: HostedPluginSupport;
|
||||
|
||||
private readonly _legend: monaco.languages.SemanticTokensLegend = {
|
||||
tokenModifiers: [],
|
||||
tokenTypes: vsCodeTokenTypeLiterals.slice(),
|
||||
};
|
||||
|
||||
override onStart(): void {
|
||||
monaco.languages.registerDocumentSemanticTokensProvider(InoSelector, this);
|
||||
}
|
||||
|
||||
getLegend(): monaco.languages.SemanticTokensLegend {
|
||||
return this._legend;
|
||||
}
|
||||
|
||||
async provideDocumentSemanticTokens(
|
||||
model: monaco.editor.ITextModel,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
|
||||
lastResultId: string | null,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
|
||||
token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.SemanticTokens> {
|
||||
await this.hostedPluginSupport.didStart;
|
||||
const start = performance.now();
|
||||
const builder = new SemanticTokensBuilder();
|
||||
if (!hasTokenization(model)) {
|
||||
return builder.build();
|
||||
}
|
||||
const parsedTokens = getHighlightedTokens(model);
|
||||
for (const parsedToken of parsedTokens) {
|
||||
builder.push(
|
||||
parsedToken.line,
|
||||
parsedToken.startCharacter,
|
||||
parsedToken.length,
|
||||
vsCodeTokenIndex[parsedToken.tokenType]
|
||||
);
|
||||
}
|
||||
const tokens = builder.build();
|
||||
console.log(
|
||||
'provideDocumentSemanticTokens',
|
||||
performance.now() - start + 'ms',
|
||||
'lastResultId',
|
||||
lastResultId
|
||||
);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
releaseDocumentSemanticTokens(lastResultId: string | undefined): void {
|
||||
console.log('releaseDocumentSemanticTokens', 'lastResultId', lastResultId);
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
|
||||
interface IParsedToken {
|
||||
line: number;
|
||||
startCharacter: number;
|
||||
length: number;
|
||||
tokenType: VSCodeTokenType;
|
||||
tokenModifiers: string[];
|
||||
}
|
||||
|
||||
const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long tokenization is skipped
|
||||
function getHighlightedTokens(
|
||||
model: (monaco.editor.ITextModel & TokenizationOwner) | null
|
||||
): IParsedToken[] {
|
||||
const result: IParsedToken[] = [];
|
||||
if (!model) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// For every word in every line, map its ranges for fast lookup
|
||||
for (
|
||||
let lineNumber = 1, len = model.getLineCount();
|
||||
lineNumber <= len;
|
||||
++lineNumber
|
||||
) {
|
||||
const lineLength = model.getLineLength(lineNumber);
|
||||
// If line is too long then skip the line
|
||||
if (lineLength > MAX_TOKENIZATION_LINE_LEN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const lineContent = model.getLineContent(lineNumber);
|
||||
model.tokenization.resetTokenization();
|
||||
model.tokenization.forceTokenization(lineNumber);
|
||||
const lineTokens = model.tokenization.getLineTokens(lineNumber);
|
||||
for (
|
||||
let tokenIndex = 0, tokenCount = lineTokens.getCount();
|
||||
tokenIndex < tokenCount;
|
||||
tokenIndex++
|
||||
) {
|
||||
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
|
||||
|
||||
// Token is a word and not a comment
|
||||
if (tokenType === StandardTokenType.Other) {
|
||||
// reset the stateful regex
|
||||
DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
|
||||
|
||||
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
|
||||
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
|
||||
const tokenStr = lineContent.substring(
|
||||
tokenStartOffset,
|
||||
tokenEndOffset
|
||||
);
|
||||
const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr);
|
||||
|
||||
if (wordMatch) {
|
||||
const word = wordMatch[0];
|
||||
const tokenType = getTokenType(word);
|
||||
if (tokenType) {
|
||||
result.push({
|
||||
line: lineNumber - 1, // map monaco 1 index to protocol 0 index
|
||||
startCharacter: tokenStartOffset + wordMatch.index,
|
||||
length: word.length,
|
||||
tokenModifiers: [],
|
||||
tokenType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const arduinoTokenTypeLiterals = [
|
||||
'type',
|
||||
'built_in',
|
||||
'_hints',
|
||||
'literal',
|
||||
] as const;
|
||||
type ArduinoTokenType = (typeof arduinoTokenTypeLiterals)[number];
|
||||
|
||||
// https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide#standard-token-types-and-modifiers
|
||||
const vsCodeTokenTypeLiterals = ['type', 'event', 'label', 'macro'] as const;
|
||||
type VSCodeTokenType = (typeof vsCodeTokenTypeLiterals)[number];
|
||||
const vsCodeTokenIndex = vsCodeTokenTypeLiterals.reduce((acc, curr, index) => {
|
||||
acc[curr] = index;
|
||||
return acc;
|
||||
}, {} as Record<VSCodeTokenType, number>);
|
||||
|
||||
const arduinoToVSCodeMappings: Record<ArduinoTokenType, VSCodeTokenType> = {
|
||||
_hints: 'event',
|
||||
type: 'type',
|
||||
built_in: 'type',
|
||||
literal: 'macro',
|
||||
};
|
||||
|
||||
let _tokens: Map<string, ArduinoTokenType> | undefined;
|
||||
function getTokenType(word: string): VSCodeTokenType | undefined {
|
||||
if (!_tokens) {
|
||||
const tokens = new Map();
|
||||
for (const [type, words] of Object.entries(arduinoKeywords)) {
|
||||
words.forEach((w) => tokens.set(w, type));
|
||||
}
|
||||
_tokens = tokens;
|
||||
}
|
||||
const token = _tokens.get(word);
|
||||
if (!token) {
|
||||
return undefined;
|
||||
}
|
||||
return arduinoToVSCodeMappings[token];
|
||||
}
|
||||
|
||||
// Based on https://github.com/highlightjs/highlight.js/blob/6317acd780bfe448f75393ea42d53c0149013274/src/languages/arduino.js#L13-L378
|
||||
const arduinoKeywords = {
|
||||
type: ['boolean', 'byte', 'word', 'String'],
|
||||
built_in: [
|
||||
'KeyboardController',
|
||||
'MouseController',
|
||||
'SoftwareSerial',
|
||||
'EthernetServer',
|
||||
'EthernetClient',
|
||||
'LiquidCrystal',
|
||||
'RobotControl',
|
||||
'GSMVoiceCall',
|
||||
'EthernetUDP',
|
||||
'EsploraTFT',
|
||||
'HttpClient',
|
||||
'RobotMotor',
|
||||
'WiFiClient',
|
||||
'GSMScanner',
|
||||
'FileSystem',
|
||||
'Scheduler',
|
||||
'GSMServer',
|
||||
'YunClient',
|
||||
'YunServer',
|
||||
'IPAddress',
|
||||
'GSMClient',
|
||||
'GSMModem',
|
||||
'Keyboard',
|
||||
'Ethernet',
|
||||
'Console',
|
||||
'GSMBand',
|
||||
'Esplora',
|
||||
'Stepper',
|
||||
'Process',
|
||||
'WiFiUDP',
|
||||
'GSM_SMS',
|
||||
'Mailbox',
|
||||
'USBHost',
|
||||
'Firmata',
|
||||
'PImage',
|
||||
'Client',
|
||||
'Server',
|
||||
'GSMPIN',
|
||||
'FileIO',
|
||||
'Bridge',
|
||||
'Serial',
|
||||
'EEPROM',
|
||||
'Stream',
|
||||
'Mouse',
|
||||
'Audio',
|
||||
'Servo',
|
||||
'File',
|
||||
'Task',
|
||||
'GPRS',
|
||||
'WiFi',
|
||||
'Wire',
|
||||
'TFT',
|
||||
'GSM',
|
||||
'SPI',
|
||||
'SD',
|
||||
],
|
||||
_hints: [
|
||||
'setup',
|
||||
'loop',
|
||||
'runShellCommandAsynchronously',
|
||||
'analogWriteResolution',
|
||||
'retrieveCallingNumber',
|
||||
'printFirmwareVersion',
|
||||
'analogReadResolution',
|
||||
'sendDigitalPortPair',
|
||||
'noListenOnLocalhost',
|
||||
'readJoystickButton',
|
||||
'setFirmwareVersion',
|
||||
'readJoystickSwitch',
|
||||
'scrollDisplayRight',
|
||||
'getVoiceCallStatus',
|
||||
'scrollDisplayLeft',
|
||||
'writeMicroseconds',
|
||||
'delayMicroseconds',
|
||||
'beginTransmission',
|
||||
'getSignalStrength',
|
||||
'runAsynchronously',
|
||||
'getAsynchronously',
|
||||
'listenOnLocalhost',
|
||||
'getCurrentCarrier',
|
||||
'readAccelerometer',
|
||||
'messageAvailable',
|
||||
'sendDigitalPorts',
|
||||
'lineFollowConfig',
|
||||
'countryNameWrite',
|
||||
'runShellCommand',
|
||||
'readStringUntil',
|
||||
'rewindDirectory',
|
||||
'readTemperature',
|
||||
'setClockDivider',
|
||||
'readLightSensor',
|
||||
'endTransmission',
|
||||
'analogReference',
|
||||
'detachInterrupt',
|
||||
'countryNameRead',
|
||||
'attachInterrupt',
|
||||
'encryptionType',
|
||||
'readBytesUntil',
|
||||
'robotNameWrite',
|
||||
'readMicrophone',
|
||||
'robotNameRead',
|
||||
'cityNameWrite',
|
||||
'userNameWrite',
|
||||
'readJoystickY',
|
||||
'readJoystickX',
|
||||
'mouseReleased',
|
||||
'openNextFile',
|
||||
'scanNetworks',
|
||||
'noInterrupts',
|
||||
'digitalWrite',
|
||||
'beginSpeaker',
|
||||
'mousePressed',
|
||||
'isActionDone',
|
||||
'mouseDragged',
|
||||
'displayLogos',
|
||||
'noAutoscroll',
|
||||
'addParameter',
|
||||
'remoteNumber',
|
||||
'getModifiers',
|
||||
'keyboardRead',
|
||||
'userNameRead',
|
||||
'waitContinue',
|
||||
'processInput',
|
||||
'parseCommand',
|
||||
'printVersion',
|
||||
'readNetworks',
|
||||
'writeMessage',
|
||||
'blinkVersion',
|
||||
'cityNameRead',
|
||||
'readMessage',
|
||||
'setDataMode',
|
||||
'parsePacket',
|
||||
'isListening',
|
||||
'setBitOrder',
|
||||
'beginPacket',
|
||||
'isDirectory',
|
||||
'motorsWrite',
|
||||
'drawCompass',
|
||||
'digitalRead',
|
||||
'clearScreen',
|
||||
'serialEvent',
|
||||
'rightToLeft',
|
||||
'setTextSize',
|
||||
'leftToRight',
|
||||
'requestFrom',
|
||||
'keyReleased',
|
||||
'compassRead',
|
||||
'analogWrite',
|
||||
'interrupts',
|
||||
'WiFiServer',
|
||||
'disconnect',
|
||||
'playMelody',
|
||||
'parseFloat',
|
||||
'autoscroll',
|
||||
'getPINUsed',
|
||||
'setPINUsed',
|
||||
'setTimeout',
|
||||
'sendAnalog',
|
||||
'readSlider',
|
||||
'analogRead',
|
||||
'beginWrite',
|
||||
'createChar',
|
||||
'motorsStop',
|
||||
'keyPressed',
|
||||
'tempoWrite',
|
||||
'readButton',
|
||||
'subnetMask',
|
||||
'debugPrint',
|
||||
'macAddress',
|
||||
'writeGreen',
|
||||
'randomSeed',
|
||||
'attachGPRS',
|
||||
'readString',
|
||||
'sendString',
|
||||
'remotePort',
|
||||
'releaseAll',
|
||||
'mouseMoved',
|
||||
'background',
|
||||
'getXChange',
|
||||
'getYChange',
|
||||
'answerCall',
|
||||
'getResult',
|
||||
'voiceCall',
|
||||
'endPacket',
|
||||
'constrain',
|
||||
'getSocket',
|
||||
'writeJSON',
|
||||
'getButton',
|
||||
'available',
|
||||
'connected',
|
||||
'findUntil',
|
||||
'readBytes',
|
||||
'exitValue',
|
||||
'readGreen',
|
||||
'writeBlue',
|
||||
'startLoop',
|
||||
'IPAddress',
|
||||
'isPressed',
|
||||
'sendSysex',
|
||||
'pauseMode',
|
||||
'gatewayIP',
|
||||
'setCursor',
|
||||
'getOemKey',
|
||||
'tuneWrite',
|
||||
'noDisplay',
|
||||
'loadImage',
|
||||
'switchPIN',
|
||||
'onRequest',
|
||||
'onReceive',
|
||||
'changePIN',
|
||||
'playFile',
|
||||
'noBuffer',
|
||||
'parseInt',
|
||||
'overflow',
|
||||
'checkPIN',
|
||||
'knobRead',
|
||||
'beginTFT',
|
||||
'bitClear',
|
||||
'updateIR',
|
||||
'bitWrite',
|
||||
'position',
|
||||
'writeRGB',
|
||||
'highByte',
|
||||
'writeRed',
|
||||
'setSpeed',
|
||||
'readBlue',
|
||||
'noStroke',
|
||||
'remoteIP',
|
||||
'transfer',
|
||||
'shutdown',
|
||||
'hangCall',
|
||||
'beginSMS',
|
||||
'endWrite',
|
||||
'attached',
|
||||
'maintain',
|
||||
'noCursor',
|
||||
'checkReg',
|
||||
'checkPUK',
|
||||
'shiftOut',
|
||||
'isValid',
|
||||
'shiftIn',
|
||||
'pulseIn',
|
||||
'connect',
|
||||
'println',
|
||||
'localIP',
|
||||
'pinMode',
|
||||
'getIMEI',
|
||||
'display',
|
||||
'noBlink',
|
||||
'process',
|
||||
'getBand',
|
||||
'running',
|
||||
'beginSD',
|
||||
'drawBMP',
|
||||
'lowByte',
|
||||
'setBand',
|
||||
'release',
|
||||
'bitRead',
|
||||
'prepare',
|
||||
'pointTo',
|
||||
'readRed',
|
||||
'setMode',
|
||||
'noFill',
|
||||
'remove',
|
||||
'listen',
|
||||
'stroke',
|
||||
'detach',
|
||||
'attach',
|
||||
'noTone',
|
||||
'exists',
|
||||
'buffer',
|
||||
'height',
|
||||
'bitSet',
|
||||
'circle',
|
||||
'config',
|
||||
'cursor',
|
||||
'random',
|
||||
'IRread',
|
||||
'setDNS',
|
||||
'endSMS',
|
||||
'getKey',
|
||||
'micros',
|
||||
'millis',
|
||||
'begin',
|
||||
'print',
|
||||
'write',
|
||||
'ready',
|
||||
'flush',
|
||||
'width',
|
||||
'isPIN',
|
||||
'blink',
|
||||
'clear',
|
||||
'press',
|
||||
'mkdir',
|
||||
'rmdir',
|
||||
'close',
|
||||
'point',
|
||||
'yield',
|
||||
'image',
|
||||
'BSSID',
|
||||
'click',
|
||||
'delay',
|
||||
'read',
|
||||
'text',
|
||||
'move',
|
||||
'peek',
|
||||
'beep',
|
||||
'rect',
|
||||
'line',
|
||||
'open',
|
||||
'seek',
|
||||
'fill',
|
||||
'size',
|
||||
'turn',
|
||||
'stop',
|
||||
'home',
|
||||
'find',
|
||||
'step',
|
||||
'tone',
|
||||
'sqrt',
|
||||
'RSSI',
|
||||
'SSID',
|
||||
'end',
|
||||
'bit',
|
||||
'tan',
|
||||
'cos',
|
||||
'sin',
|
||||
'pow',
|
||||
'map',
|
||||
'abs',
|
||||
'max',
|
||||
'min',
|
||||
'get',
|
||||
'run',
|
||||
'put',
|
||||
],
|
||||
literal: [
|
||||
'DIGITAL_MESSAGE',
|
||||
'FIRMATA_STRING',
|
||||
'ANALOG_MESSAGE',
|
||||
'REPORT_DIGITAL',
|
||||
'REPORT_ANALOG',
|
||||
'INPUT_PULLUP',
|
||||
'SET_PIN_MODE',
|
||||
'INTERNAL2V56',
|
||||
'SYSTEM_RESET',
|
||||
'LED_BUILTIN',
|
||||
'INTERNAL1V1',
|
||||
'SYSEX_START',
|
||||
'INTERNAL',
|
||||
'EXTERNAL',
|
||||
'DEFAULT',
|
||||
'OUTPUT',
|
||||
'INPUT',
|
||||
'HIGH',
|
||||
'LOW',
|
||||
],
|
||||
} as const;
|
@@ -6,19 +6,25 @@ import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import {
|
||||
ArduinoDaemon,
|
||||
assertSanitizedFqbn,
|
||||
BoardIdentifier,
|
||||
BoardsService,
|
||||
CompileSummary,
|
||||
ExecutableService,
|
||||
isBoardIdentifierChangeEvent,
|
||||
sanitizeFqbn,
|
||||
} from '../../common/protocol';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import {
|
||||
defaultAsyncWorkers,
|
||||
maxAsyncWorkers,
|
||||
minAsyncWorkers,
|
||||
} from '../arduino-preferences';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { HostedPluginEvents } from '../hosted/hosted-plugin-events';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { SketchContribution, URI } from './contribution';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { CompileSummaryProvider } from './verify-sketch';
|
||||
|
||||
interface DaemonAddress {
|
||||
/**
|
||||
@@ -76,6 +82,10 @@ interface StartLanguageServerParams {
|
||||
* If `true`, the logging is not forwarded to the _Output_ view via the language client.
|
||||
*/
|
||||
readonly silentOutput?: boolean;
|
||||
/**
|
||||
* Number of async workers used by `clangd`. Background index also uses this many workers. If `0`, `clangd` uses all available cores. It's `0` by default.
|
||||
*/
|
||||
readonly jobs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +109,8 @@ export class InoLanguage extends SketchContribution {
|
||||
private readonly notificationCenter: NotificationCenter;
|
||||
@inject(BoardsDataStore)
|
||||
private readonly boardDataStore: BoardsDataStore;
|
||||
@inject(CompileSummaryProvider)
|
||||
private readonly compileSummaryProvider: CompileSummaryProvider;
|
||||
|
||||
private readonly toDispose = new DisposableCollection();
|
||||
private readonly languageServerStartMutex = new Mutex();
|
||||
@@ -137,6 +149,7 @@ export class InoLanguage extends SketchContribution {
|
||||
switch (preferenceName) {
|
||||
case 'arduino.language.log':
|
||||
case 'arduino.language.realTimeDiagnostics':
|
||||
case 'arduino.language.asyncWorkers':
|
||||
forceRestart();
|
||||
}
|
||||
}
|
||||
@@ -149,14 +162,11 @@ export class InoLanguage extends SketchContribution {
|
||||
this.notificationCenter.onDidReinitialize(() => forceRestart()),
|
||||
this.boardDataStore.onDidChange((event) => {
|
||||
if (this.languageServerFqbn) {
|
||||
const sanitizedFqbn = sanitizeFqbn(this.languageServerFqbn);
|
||||
if (!sanitizeFqbn) {
|
||||
throw new Error(
|
||||
`Failed to sanitize the FQBN of the running language server. FQBN with the board settings was: ${this.languageServerFqbn}`
|
||||
);
|
||||
}
|
||||
const sanitizedFQBN = sanitizeFqbn(this.languageServerFqbn);
|
||||
// The incoming FQBNs might contain custom boards configs, sanitize them before the comparison.
|
||||
// https://github.com/arduino/arduino-ide/pull/2113#pullrequestreview-1499998328
|
||||
const matchingChange = event.changes.find(
|
||||
(change) => change.fqbn === sanitizedFqbn
|
||||
(change) => sanitizedFQBN === sanitizeFqbn(change.fqbn)
|
||||
);
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
if (
|
||||
@@ -167,10 +177,20 @@ export class InoLanguage extends SketchContribution {
|
||||
}
|
||||
}
|
||||
}),
|
||||
this.compileSummaryProvider.onDidChangeCompileSummary(
|
||||
(compileSummary) => {
|
||||
if (compileSummary) {
|
||||
this.fireBuildDidComplete(compileSummary);
|
||||
}
|
||||
}
|
||||
),
|
||||
]);
|
||||
this.boardsServiceProvider.ready.then(() =>
|
||||
start(this.boardsServiceProvider.boardsConfig.selectedBoard)
|
||||
);
|
||||
Promise.all([
|
||||
this.boardsServiceProvider.ready,
|
||||
this.preferences.ready,
|
||||
]).then(() => {
|
||||
start(this.boardsServiceProvider.boardsConfig.selectedBoard);
|
||||
});
|
||||
}
|
||||
|
||||
onStop(): void {
|
||||
@@ -183,11 +203,7 @@ export class InoLanguage extends SketchContribution {
|
||||
forceStart = false
|
||||
): Promise<void> {
|
||||
const port = await this.daemon.tryGetPort();
|
||||
if (!port) {
|
||||
return;
|
||||
}
|
||||
const portNumber = Number.parseInt(port, 10); // TODO: IDE2 APIs should provide a number and not string
|
||||
if (Number.isNaN(portNumber)) {
|
||||
if (typeof port !== 'number') {
|
||||
return;
|
||||
}
|
||||
const release = await this.languageServerStartMutex.acquire();
|
||||
@@ -219,7 +235,6 @@ export class InoLanguage extends SketchContribution {
|
||||
}
|
||||
return;
|
||||
}
|
||||
assertSanitizedFqbn(fqbn);
|
||||
const fqbnWithConfig = await this.boardDataStore.appendConfigToFqbn(fqbn);
|
||||
if (!fqbnWithConfig) {
|
||||
throw new Error(
|
||||
@@ -230,11 +245,16 @@ export class InoLanguage extends SketchContribution {
|
||||
// NOOP
|
||||
return;
|
||||
}
|
||||
this.logger.info(`Starting language server: ${fqbnWithConfig}`);
|
||||
const log = this.preferences.get('arduino.language.log');
|
||||
const realTimeDiagnostics = this.preferences.get(
|
||||
'arduino.language.realTimeDiagnostics'
|
||||
);
|
||||
const jobs = this.getAsyncWorkersPreferenceSafe();
|
||||
this.logger.info(
|
||||
`Starting language server: ${fqbnWithConfig}${
|
||||
jobs ? ` (async worker count: ${jobs})` : ''
|
||||
}`
|
||||
);
|
||||
let currentSketchPath: string | undefined = undefined;
|
||||
if (log) {
|
||||
const currentSketch = await this.sketchServiceClient.currentSketch();
|
||||
@@ -262,7 +282,7 @@ export class InoLanguage extends SketchContribution {
|
||||
lsPath,
|
||||
daemonAddress: {
|
||||
hostname: 'localhost',
|
||||
port: portNumber,
|
||||
port,
|
||||
instance: 1, // TODO: get it from the backend
|
||||
},
|
||||
clangdPath,
|
||||
@@ -273,6 +293,7 @@ export class InoLanguage extends SketchContribution {
|
||||
},
|
||||
realTimeDiagnostics,
|
||||
silentOutput: true,
|
||||
jobs,
|
||||
}),
|
||||
]);
|
||||
} catch (e) {
|
||||
@@ -283,6 +304,21 @@ export class InoLanguage extends SketchContribution {
|
||||
release();
|
||||
}
|
||||
}
|
||||
// The Theia preference UI validation is bogus.
|
||||
// To restrict the number of jobs to a valid value.
|
||||
private getAsyncWorkersPreferenceSafe(): number {
|
||||
const jobs = this.preferences.get(
|
||||
'arduino.language.asyncWorkers',
|
||||
defaultAsyncWorkers
|
||||
);
|
||||
if (jobs < minAsyncWorkers) {
|
||||
return minAsyncWorkers;
|
||||
}
|
||||
if (jobs > maxAsyncWorkers) {
|
||||
return maxAsyncWorkers;
|
||||
}
|
||||
return jobs;
|
||||
}
|
||||
|
||||
private async start(
|
||||
params: StartLanguageServerParams
|
||||
@@ -292,4 +328,32 @@ export class InoLanguage extends SketchContribution {
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
// Execute the a command contributed by the Arduino Tools VSIX to send the `ino/buildDidComplete` notification to the language server
|
||||
private async fireBuildDidComplete(
|
||||
compileSummary: CompileSummary
|
||||
): Promise<void> {
|
||||
const params = {
|
||||
...compileSummary,
|
||||
};
|
||||
console.info(
|
||||
`Executing 'arduino.languageserver.notifyBuildDidComplete' with ${JSON.stringify(
|
||||
params.buildOutputUri
|
||||
)}`
|
||||
);
|
||||
|
||||
try {
|
||||
await this.commandService.executeCommand(
|
||||
'arduino.languageserver.notifyBuildDidComplete',
|
||||
params
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Unexpected error when firing event on build did complete. ${JSON.stringify(
|
||||
params.buildOutputUri
|
||||
)}`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ export class OpenBoardsConfig extends Contribution {
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(OpenBoardsConfig.Commands.OPEN_DIALOG, {
|
||||
execute: async (params?: EditBoardsConfigActionParams) =>
|
||||
this.boardsConfigDialog.open(params),
|
||||
this.boardsConfigDialog.open(true, params),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -3,10 +3,12 @@ import { NavigatableWidget } from '@theia/core/lib/browser/navigatable';
|
||||
import { Saveable } from '@theia/core/lib/browser/saveable';
|
||||
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { ApplicationError } from '@theia/core/lib/common/application-error';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||
import { WorkspaceInput } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { SketchesError } from '../../common/protocol';
|
||||
import { StartupTasks } from '../../electron-common/startup-task';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
@@ -35,7 +37,29 @@ export class SaveAsSketch extends CloudSketchContribution {
|
||||
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, {
|
||||
execute: (args) => this.saveAs(args),
|
||||
execute: async (args) => {
|
||||
try {
|
||||
return await this.saveAs(args);
|
||||
} catch (err) {
|
||||
let message = String(err);
|
||||
if (ApplicationError.is(err)) {
|
||||
if (SketchesError.SketchAlreadyContainsThisFile.is(err)) {
|
||||
message = nls.localize(
|
||||
'arduino/sketch/sketchAlreadyContainsThisFileMessage',
|
||||
'Failed to save sketch "{0}" as "{1}". {2}',
|
||||
err.data.sourceSketchName,
|
||||
err.data.targetSketchName,
|
||||
err.message
|
||||
);
|
||||
} else {
|
||||
message = err.message;
|
||||
}
|
||||
} else if (err instanceof Error) {
|
||||
message = err.message;
|
||||
}
|
||||
this.messageService.error(message);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,13 +82,14 @@ export class SaveAsSketch extends CloudSketchContribution {
|
||||
* Resolves `true` if the sketch was successfully saved as something.
|
||||
*/
|
||||
private async saveAs(
|
||||
{
|
||||
params = SaveAsSketch.Options.DEFAULT
|
||||
): Promise<boolean> {
|
||||
const {
|
||||
execOnlyIfTemp,
|
||||
openAfterMove,
|
||||
wipeOriginal,
|
||||
markAsRecentlyOpened,
|
||||
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
|
||||
): Promise<boolean> {
|
||||
} = params;
|
||||
assertConnectedToBackend({
|
||||
connectionStatusService: this.connectionStatusService,
|
||||
messageService: this.messageService,
|
||||
|
@@ -1,78 +0,0 @@
|
||||
import { MessageService } from '@theia/core';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { LocalStorageService } from '@theia/core/lib/browser';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { ArduinoPreferences } from '../arduino-preferences';
|
||||
import { SurveyNotificationService } from '../../common/protocol/survey-service';
|
||||
|
||||
const SURVEY_MESSAGE = nls.localize(
|
||||
'arduino/survey/surveyMessage',
|
||||
'Please help us improve by answering this super short survey. We value our community and would like to get to know our supporters a little better.'
|
||||
);
|
||||
const DO_NOT_SHOW_AGAIN = nls.localize(
|
||||
'arduino/survey/dismissSurvey',
|
||||
"Don't show again"
|
||||
);
|
||||
const GO_TO_SURVEY = nls.localize(
|
||||
'arduino/survey/answerSurvey',
|
||||
'Answer survey'
|
||||
);
|
||||
|
||||
const SURVEY_BASE_URL = 'https://surveys.hotjar.com/';
|
||||
const surveyId = '17887b40-e1f0-4bd6-b9f0-a37f229ccd8b';
|
||||
|
||||
@injectable()
|
||||
export class SurveyNotification implements FrontendApplicationContribution {
|
||||
@inject(MessageService)
|
||||
private readonly messageService: MessageService;
|
||||
|
||||
@inject(LocalStorageService)
|
||||
private readonly localStorageService: LocalStorageService;
|
||||
|
||||
@inject(WindowService)
|
||||
private readonly windowService: WindowService;
|
||||
|
||||
@inject(ArduinoPreferences)
|
||||
private readonly arduinoPreferences: ArduinoPreferences;
|
||||
|
||||
@inject(SurveyNotificationService)
|
||||
private readonly surveyNotificationService: SurveyNotificationService;
|
||||
|
||||
onStart(): void {
|
||||
this.arduinoPreferences.ready.then(async () => {
|
||||
if (
|
||||
(await this.surveyNotificationService.isFirstInstance()) &&
|
||||
this.arduinoPreferences.get('arduino.survey.notification')
|
||||
) {
|
||||
const surveyAnswered = await this.localStorageService.getData(
|
||||
this.surveyKey(surveyId)
|
||||
);
|
||||
if (surveyAnswered !== undefined) {
|
||||
return;
|
||||
}
|
||||
const answer = await this.messageService.info(
|
||||
SURVEY_MESSAGE,
|
||||
DO_NOT_SHOW_AGAIN,
|
||||
GO_TO_SURVEY
|
||||
);
|
||||
switch (answer) {
|
||||
case GO_TO_SURVEY:
|
||||
this.windowService.openNewWindow(SURVEY_BASE_URL + surveyId, {
|
||||
external: true,
|
||||
});
|
||||
this.localStorageService.setData(this.surveyKey(surveyId), true);
|
||||
break;
|
||||
case DO_NOT_SHOW_AGAIN:
|
||||
this.localStorageService.setData(this.surveyKey(surveyId), false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private surveyKey(id: string): string {
|
||||
return `answered_survey:${id}`;
|
||||
}
|
||||
}
|
@@ -1,13 +1,11 @@
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { HostedPluginSupport } from '../hosted/hosted-plugin-support';
|
||||
import type { ArduinoState } from 'vscode-arduino-api';
|
||||
import {
|
||||
BoardsConfig,
|
||||
BoardsService,
|
||||
CompileSummary,
|
||||
isCompileSummary,
|
||||
BoardsConfig,
|
||||
PortIdentifier,
|
||||
resolveDetectedPort,
|
||||
} from '../../common/protocol';
|
||||
@@ -18,8 +16,10 @@ import {
|
||||
} from '../../common/protocol/arduino-context-mapper';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { HostedPluginSupport } from '../hosted/hosted-plugin-support';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { SketchContribution } from './contribution';
|
||||
import { CompileSummaryProvider } from './verify-sketch';
|
||||
|
||||
/**
|
||||
* (non-API) exported for tests
|
||||
@@ -43,6 +43,8 @@ export class UpdateArduinoState extends SketchContribution {
|
||||
private readonly boardsDataStore: BoardsDataStore;
|
||||
@inject(HostedPluginSupport)
|
||||
private readonly hostedPluginSupport: HostedPluginSupport;
|
||||
@inject(CompileSummaryProvider)
|
||||
private readonly compileSummaryProvider: CompileSummaryProvider;
|
||||
|
||||
private readonly toDispose = new DisposableCollection();
|
||||
|
||||
@@ -60,14 +62,13 @@ export class UpdateArduinoState extends SketchContribution {
|
||||
this.configService.onDidChangeSketchDirUri((userDirUri) =>
|
||||
this.updateUserDirPath(userDirUri)
|
||||
),
|
||||
this.commandService.onDidExecuteCommand(({ commandId, args }) => {
|
||||
if (
|
||||
commandId === 'arduino.languageserver.notifyBuildDidComplete' &&
|
||||
isCompileSummary(args[0])
|
||||
) {
|
||||
this.updateCompileSummary(args[0]);
|
||||
this.compileSummaryProvider.onDidChangeCompileSummary(
|
||||
(compilerSummary) => {
|
||||
if (compilerSummary) {
|
||||
this.updateCompileSummary(compilerSummary);
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
this.boardsDataStore.onDidChange((event) => {
|
||||
const selectedFqbn =
|
||||
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn;
|
||||
@@ -88,6 +89,10 @@ export class UpdateArduinoState extends SketchContribution {
|
||||
this.updateSketchPath(this.sketchServiceClient.tryGetCurrentSketch());
|
||||
this.updateUserDirPath(this.configService.tryGetSketchDirUri());
|
||||
this.updateDataDirPath(this.configService.tryGetDataDirUri());
|
||||
const { compileSummary } = this.compileSummaryProvider;
|
||||
if (compileSummary) {
|
||||
this.updateCompileSummary(compileSummary);
|
||||
}
|
||||
}
|
||||
|
||||
onStop(): void {
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { CoreService, sanitizeFqbn } from '../../common/protocol';
|
||||
import { FQBN } from 'fqbn';
|
||||
import { CoreService } from '../../common/protocol';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
@@ -103,6 +104,7 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
}
|
||||
|
||||
try {
|
||||
const autoVerify = this.preferences['arduino.upload.autoVerify'];
|
||||
// toggle the toolbar button and menu item state.
|
||||
// uploadInProgress will be set to false whether the upload fails or not
|
||||
this.uploadInProgress = true;
|
||||
@@ -115,7 +117,7 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
'arduino-verify-sketch',
|
||||
<VerifySketchParams>{
|
||||
exportBinaries: false,
|
||||
silent: true,
|
||||
mode: autoVerify ? 'auto' : 'dry-run',
|
||||
}
|
||||
);
|
||||
if (!verifyOptions) {
|
||||
@@ -126,6 +128,7 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
usingProgrammer,
|
||||
verifyOptions
|
||||
);
|
||||
|
||||
if (!uploadOptions) {
|
||||
return;
|
||||
}
|
||||
@@ -136,10 +139,37 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
|
||||
const uploadResponse = await this.doWithProgress({
|
||||
progressText: nls.localize('arduino/sketch/uploading', 'Uploading...'),
|
||||
task: (progressId, coreService) =>
|
||||
coreService.upload({ ...uploadOptions, progressId }),
|
||||
task: async (progressId, coreService, token) => {
|
||||
try {
|
||||
return await coreService.upload(
|
||||
{ ...uploadOptions, progressId },
|
||||
token
|
||||
);
|
||||
} catch (err) {
|
||||
if (err.code === 4005) {
|
||||
const uploadWithProgrammerOptions = await this.uploadOptions(
|
||||
true,
|
||||
verifyOptions
|
||||
);
|
||||
if (uploadWithProgrammerOptions) {
|
||||
return coreService.upload(
|
||||
{ ...uploadWithProgrammerOptions, progressId },
|
||||
token
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
},
|
||||
keepOutput: true,
|
||||
cancelable: true,
|
||||
});
|
||||
|
||||
if (!uploadResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// the port update is NOOP if nothing has changed
|
||||
this.boardsServiceProvider.updateConfig(uploadResponse.portAfterUpload);
|
||||
|
||||
@@ -172,7 +202,11 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
|
||||
await Promise.all([
|
||||
verifyOptions.fqbn, // already decorated FQBN
|
||||
this.boardsDataStore.getData(sanitizeFqbn(verifyOptions.fqbn)),
|
||||
this.boardsDataStore.getData(
|
||||
verifyOptions.fqbn
|
||||
? new FQBN(verifyOptions.fqbn).toString(true)
|
||||
: undefined
|
||||
),
|
||||
this.preferences.get('arduino.upload.verify'),
|
||||
this.preferences.get('arduino.upload.verbose'),
|
||||
]);
|
||||
|
@@ -1,46 +1,71 @@
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import type { CompileSummary, CoreService } from '../../common/protocol';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import {
|
||||
CoreServiceContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
CoreServiceContribution,
|
||||
KeybindingRegistry,
|
||||
MenuModelRegistry,
|
||||
TabBarToolbarRegistry,
|
||||
} from './contribution';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { CoreService } from '../../common/protocol';
|
||||
import { CoreErrorHandler } from './core-error-handler';
|
||||
|
||||
export const CompileSummaryProvider = Symbol('CompileSummaryProvider');
|
||||
export interface CompileSummaryProvider {
|
||||
readonly compileSummary: CompileSummary | undefined;
|
||||
readonly onDidChangeCompileSummary: Event<CompileSummary | undefined>;
|
||||
}
|
||||
|
||||
export type VerifySketchMode =
|
||||
/**
|
||||
* When the user explicitly triggers the verify command from the primary UI: menu, toolbar, or keybinding. The UI shows the output, updates the toolbar items state, etc.
|
||||
*/
|
||||
| 'explicit'
|
||||
/**
|
||||
* When the verify phase automatically runs as part of the upload but there is no UI indication of the command: the toolbar items do not update.
|
||||
*/
|
||||
| 'auto'
|
||||
/**
|
||||
* The verify does not run. There is no UI indication of the command. For example, when the user decides to disable the auto verify (`'arduino.upload.autoVerify'`) to skips the code recompilation phase.
|
||||
*/
|
||||
| 'dry-run';
|
||||
|
||||
export interface VerifySketchParams {
|
||||
/**
|
||||
* Same as `CoreService.Options.Compile#exportBinaries`
|
||||
*/
|
||||
readonly exportBinaries?: boolean;
|
||||
/**
|
||||
* If `true`, there won't be any UI indication of the verify command in the toolbar. It's `false` by default.
|
||||
* The mode specifying how verify should run. It's `'explicit'` by default.
|
||||
*/
|
||||
readonly silent?: boolean;
|
||||
readonly mode?: VerifySketchMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* - `"idle"` when neither verify, nor upload is running,
|
||||
* - `"explicit-verify"` when only verify is running triggered by the user, and
|
||||
* - `"automatic-verify"` is when the automatic verify phase is running as part of an upload triggered by the user.
|
||||
* - `"idle"` when neither verify, nor upload is running
|
||||
*/
|
||||
type VerifyProgress = 'idle' | 'explicit-verify' | 'automatic-verify';
|
||||
type VerifyProgress = 'idle' | VerifySketchMode;
|
||||
|
||||
@injectable()
|
||||
export class VerifySketch extends CoreServiceContribution {
|
||||
export class VerifySketch
|
||||
extends CoreServiceContribution
|
||||
implements CompileSummaryProvider
|
||||
{
|
||||
@inject(CoreErrorHandler)
|
||||
private readonly coreErrorHandler: CoreErrorHandler;
|
||||
|
||||
private readonly onDidChangeEmitter = new Emitter<void>();
|
||||
private readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
private readonly onDidChangeCompileSummaryEmitter = new Emitter<
|
||||
CompileSummary | undefined
|
||||
>();
|
||||
private verifyProgress: VerifyProgress = 'idle';
|
||||
private _compileSummary: CompileSummary | undefined;
|
||||
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
|
||||
@@ -54,10 +79,10 @@ export class VerifySketch extends CoreServiceContribution {
|
||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isEnabled: () => this.verifyProgress !== 'explicit-verify',
|
||||
isEnabled: () => this.verifyProgress !== 'explicit',
|
||||
// toggled only when verify is running, but not toggled when automatic verify is running before the upload
|
||||
// https://github.com/arduino/arduino-ide/pull/1750#pullrequestreview-1214762975
|
||||
isToggled: () => this.verifyProgress === 'explicit-verify',
|
||||
isToggled: () => this.verifyProgress === 'explicit',
|
||||
execute: () =>
|
||||
registry.executeCommand(VerifySketch.Commands.VERIFY_SKETCH.id),
|
||||
});
|
||||
@@ -105,6 +130,21 @@ export class VerifySketch extends CoreServiceContribution {
|
||||
super.handleError(error);
|
||||
}
|
||||
|
||||
get compileSummary(): CompileSummary | undefined {
|
||||
return this._compileSummary;
|
||||
}
|
||||
|
||||
private updateCompileSummary(
|
||||
compileSummary: CompileSummary | undefined
|
||||
): void {
|
||||
this._compileSummary = compileSummary;
|
||||
this.onDidChangeCompileSummaryEmitter.fire(this._compileSummary);
|
||||
}
|
||||
|
||||
get onDidChangeCompileSummary(): Event<CompileSummary | undefined> {
|
||||
return this.onDidChangeCompileSummaryEmitter.event;
|
||||
}
|
||||
|
||||
private async verifySketch(
|
||||
params?: VerifySketchParams
|
||||
): Promise<CoreService.Options.Compile | undefined> {
|
||||
@@ -113,34 +153,44 @@ export class VerifySketch extends CoreServiceContribution {
|
||||
}
|
||||
|
||||
try {
|
||||
this.verifyProgress = params?.silent
|
||||
? 'automatic-verify'
|
||||
: 'explicit-verify';
|
||||
this.verifyProgress = params?.mode ?? 'explicit';
|
||||
this.onDidChangeEmitter.fire();
|
||||
this.menuManager.update();
|
||||
this.clearVisibleNotification();
|
||||
this.coreErrorHandler.reset();
|
||||
const dryRun = this.verifyProgress === 'dry-run';
|
||||
|
||||
const options = await this.options(params?.exportBinaries);
|
||||
if (!options) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await this.doWithProgress({
|
||||
if (dryRun) {
|
||||
return options;
|
||||
}
|
||||
|
||||
const compileSummary = await this.doWithProgress({
|
||||
progressText: nls.localize(
|
||||
'arduino/sketch/compile',
|
||||
'Compiling sketch...'
|
||||
),
|
||||
task: (progressId, coreService) =>
|
||||
coreService.compile({
|
||||
...options,
|
||||
progressId,
|
||||
}),
|
||||
task: (progressId, coreService, token) =>
|
||||
coreService.compile(
|
||||
{
|
||||
...options,
|
||||
progressId,
|
||||
},
|
||||
token
|
||||
),
|
||||
cancelable: true,
|
||||
});
|
||||
this.messageService.info(
|
||||
nls.localize('arduino/sketch/doneCompiling', 'Done compiling.'),
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
|
||||
this.updateCompileSummary(compileSummary);
|
||||
|
||||
// Returns with the used options for the compilation
|
||||
// so that follow-up tasks (such as upload) can reuse the compiled code.
|
||||
// Note that the `fqbn` is already decorated with the board settings, if any.
|
||||
|
@@ -509,11 +509,19 @@ export class CreateApi {
|
||||
|
||||
private async headers(): Promise<Record<string, string>> {
|
||||
const token = await this.token();
|
||||
return {
|
||||
const headers: Record<string, string> = {
|
||||
'content-type': 'application/json',
|
||||
accept: 'application/json',
|
||||
authorization: `Bearer ${token}`,
|
||||
};
|
||||
|
||||
const sharedSpaceID =
|
||||
this.arduinoPreferences['arduino.cloud.sharedSpaceID'];
|
||||
if (sharedSpaceID) {
|
||||
headers['x-organization'] = sharedSpaceID;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
private domain(apiVersion = 'v2'): string {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
|
@@ -5,7 +5,7 @@ import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import {
|
||||
Stat,
|
||||
FileType,
|
||||
|
@@ -89,8 +89,7 @@
|
||||
"scope": [
|
||||
"storage",
|
||||
"support",
|
||||
"string.quoted.single.c",
|
||||
"macro"
|
||||
"string.quoted.single.c"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0ca1a6"
|
||||
@@ -102,8 +101,7 @@
|
||||
"meta.function.c",
|
||||
"entity.name.function",
|
||||
"meta.function-call.c",
|
||||
"variable.other",
|
||||
"label"
|
||||
"variable.other"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#F39C12"
|
||||
@@ -148,8 +146,7 @@
|
||||
"name": "meta keywords",
|
||||
"scope": [
|
||||
"keyword.control",
|
||||
"meta.preprocessor.c",
|
||||
"event"
|
||||
"meta.preprocessor.c"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#C586C0"
|
||||
|
@@ -89,8 +89,7 @@
|
||||
"scope": [
|
||||
"storage",
|
||||
"support",
|
||||
"string.quoted.single.c",
|
||||
"macro"
|
||||
"string.quoted.single.c"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#00979D"
|
||||
@@ -102,8 +101,7 @@
|
||||
"meta.function.c",
|
||||
"entity.name.function",
|
||||
"meta.function-call.c",
|
||||
"variable.other",
|
||||
"label"
|
||||
"variable.other"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#D35400"
|
||||
@@ -148,8 +146,7 @@
|
||||
"name": "meta keywords",
|
||||
"scope": [
|
||||
"keyword.control",
|
||||
"meta.preprocessor.c",
|
||||
"event"
|
||||
"meta.preprocessor.c"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#728E00"
|
||||
|
@@ -69,6 +69,7 @@ export const CertificateUploaderComponent = ({
|
||||
const onItemSelect = React.useCallback(
|
||||
(item: BoardOptionValue | null) => {
|
||||
if (!item) {
|
||||
setSelectedItem(null);
|
||||
return;
|
||||
}
|
||||
const board = item.board;
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import React from '@theia/core/shared/react';
|
||||
import type {
|
||||
BoardList,
|
||||
BoardListItemWithBoard,
|
||||
import {
|
||||
boardListItemEquals,
|
||||
type BoardList,
|
||||
type BoardListItemWithBoard,
|
||||
} from '../../../common/protocol/board-list';
|
||||
import { ArduinoSelect } from '../../widgets/arduino-select';
|
||||
|
||||
@@ -75,7 +76,9 @@ export const SelectBoardComponent = ({
|
||||
setSelectOptions(boardOptions);
|
||||
|
||||
if (selectedItem) {
|
||||
selBoard = updatableBoards.indexOf(selectedItem);
|
||||
selBoard = updatableBoards.findIndex((board) =>
|
||||
boardListItemEquals(board, selectedItem)
|
||||
);
|
||||
}
|
||||
|
||||
selectOption(boardOptions[selBoard] || null);
|
||||
|
@@ -104,6 +104,7 @@ export const FirmwareUploaderComponent = ({
|
||||
const onItemSelect = React.useCallback(
|
||||
(item: BoardListItemWithBoard | null) => {
|
||||
if (!item) {
|
||||
setSelectedItem(null);
|
||||
return;
|
||||
}
|
||||
const board = item.board;
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
} from '../../../common/protocol/ide-updater';
|
||||
import { LocalStorageService } from '@theia/core/lib/browser';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { sanitize } from 'dompurify';
|
||||
|
||||
@injectable()
|
||||
export class IDEUpdaterDialogProps extends DialogProps {}
|
||||
@@ -165,6 +166,51 @@ export class IDEUpdaterDialog extends ReactDialog<UpdateInfo | undefined> {
|
||||
goToDownloadPageButton.focus();
|
||||
}
|
||||
|
||||
private appendDonateFooter() {
|
||||
const footer = document.createElement('div');
|
||||
footer.classList.add('ide-updater-dialog--footer');
|
||||
const footerContent = document.createElement('div');
|
||||
footerContent.classList.add('ide-updater-dialog--footer-content');
|
||||
footer.appendChild(footerContent);
|
||||
|
||||
const footerLink = document.createElement('a');
|
||||
footerLink.innerText = sanitize(
|
||||
nls.localize('arduino/ide-updater/donateLinkText', 'donate to support us')
|
||||
);
|
||||
footerLink.classList.add('ide-updater-dialog--footer-link');
|
||||
footerLink.onclick = () =>
|
||||
this.openExternal('https://www.arduino.cc/en/donate');
|
||||
|
||||
const footerLinkIcon = document.createElement('span');
|
||||
footerLinkIcon.title = nls.localize(
|
||||
'arduino/ide-updater/donateLinkIconTitle',
|
||||
'open donation page'
|
||||
);
|
||||
footerLinkIcon.classList.add('ide-updater-dialog--footer-link-icon');
|
||||
footerLink.appendChild(footerLinkIcon);
|
||||
|
||||
const placeholderKey = '%%link%%';
|
||||
const footerText = sanitize(
|
||||
nls.localize(
|
||||
'arduino/ide-updater/donateText',
|
||||
'Open source is love, {0}',
|
||||
placeholderKey
|
||||
)
|
||||
);
|
||||
const placeholder = footerText.indexOf(placeholderKey);
|
||||
if (placeholder !== -1) {
|
||||
const parts = footerText.split(placeholderKey);
|
||||
footerContent.appendChild(document.createTextNode(parts[0]));
|
||||
footerContent.appendChild(footerLink);
|
||||
footerContent.appendChild(document.createTextNode(parts[1]));
|
||||
} else {
|
||||
footerContent.appendChild(document.createTextNode(footerText));
|
||||
footerContent.appendChild(footerLink);
|
||||
}
|
||||
|
||||
this.controlPanel.insertAdjacentElement('afterend', footer);
|
||||
}
|
||||
|
||||
private openDownloadPage(): void {
|
||||
this.openExternal('https://www.arduino.cc/en/software');
|
||||
this.close();
|
||||
@@ -187,6 +233,7 @@ export class IDEUpdaterDialog extends ReactDialog<UpdateInfo | undefined> {
|
||||
downloadStarted: true,
|
||||
});
|
||||
this.clearButtons();
|
||||
this.appendDonateFooter();
|
||||
this.updater.downloadUpdate();
|
||||
}
|
||||
|
||||
@@ -214,6 +261,7 @@ export class IDEUpdaterDialog extends ReactDialog<UpdateInfo | undefined> {
|
||||
}
|
||||
|
||||
override async open(
|
||||
disposeOnResolve = true,
|
||||
data: UpdateInfo | undefined = undefined
|
||||
): Promise<UpdateInfo | undefined> {
|
||||
if (data && data.version) {
|
||||
@@ -224,7 +272,7 @@ export class IDEUpdaterDialog extends ReactDialog<UpdateInfo | undefined> {
|
||||
error: undefined,
|
||||
});
|
||||
this.updateInfo = data;
|
||||
return super.open();
|
||||
return super.open(disposeOnResolve);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,107 @@
|
||||
import React from '@theia/core/shared/react';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||
import { ReactDialog } from '../theia/dialogs/dialogs';
|
||||
import { nls } from '@theia/core';
|
||||
import { DialogProps } from '@theia/core/lib/browser';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { AppService } from '../app-service';
|
||||
import { sanitize } from 'dompurify';
|
||||
|
||||
@injectable()
|
||||
export class VersionWelcomeDialogProps extends DialogProps {}
|
||||
|
||||
@injectable()
|
||||
export class VersionWelcomeDialog extends ReactDialog<void> {
|
||||
@inject(AppService)
|
||||
private readonly appService: AppService;
|
||||
|
||||
@inject(WindowService)
|
||||
private readonly windowService: WindowService;
|
||||
|
||||
constructor(
|
||||
@inject(VersionWelcomeDialogProps)
|
||||
protected override readonly props: VersionWelcomeDialogProps
|
||||
) {
|
||||
super({
|
||||
title: nls.localize(
|
||||
'arduino/versionWelcome/title',
|
||||
'Welcome to a new version of the Arduino IDE!'
|
||||
),
|
||||
});
|
||||
this.node.id = 'version-welcome-dialog-container';
|
||||
this.contentNode.classList.add('version-welcome-dialog');
|
||||
}
|
||||
|
||||
protected render(): React.ReactNode {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
{nls.localize(
|
||||
'arduino/versionWelcome/donateMessage',
|
||||
'Arduino is committed to keeping software free and open-source for everyone. Your donation helps us develop new features, improve libraries, and support millions of users worldwide.'
|
||||
)}
|
||||
</p>
|
||||
<p className="bold">
|
||||
{nls.localize(
|
||||
'arduino/versionWelcome/donateMessage2',
|
||||
'Please consider supporting our work on the free open source Arduino IDE.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
override get value(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
private appendButtons(): void {
|
||||
const cancelButton = this.createButton(
|
||||
nls.localize('arduino/versionWelcome/cancelButton', 'Maybe later')
|
||||
);
|
||||
cancelButton.classList.add('secondary');
|
||||
cancelButton.classList.add('cancel-button');
|
||||
this.addAction(cancelButton, this.close.bind(this), 'click');
|
||||
this.controlPanel.appendChild(cancelButton);
|
||||
|
||||
const donateButton = this.createButton(
|
||||
nls.localize('arduino/versionWelcome/donateButton', 'Donate now')
|
||||
);
|
||||
this.addAction(donateButton, this.onDonateButtonClick.bind(this), 'click');
|
||||
this.controlPanel.appendChild(donateButton);
|
||||
donateButton.focus();
|
||||
}
|
||||
|
||||
private onDonateButtonClick(): void {
|
||||
this.openDonationPage();
|
||||
this.close();
|
||||
}
|
||||
|
||||
private readonly openDonationPage = () => {
|
||||
const url = 'https://www.arduino.cc/en/donate';
|
||||
this.windowService.openNewWindow(url, { external: true });
|
||||
};
|
||||
|
||||
private async updateTitleVersion(): Promise<void> {
|
||||
const appInfo = await this.appService.info();
|
||||
const { appVersion } = appInfo;
|
||||
|
||||
if (appVersion) {
|
||||
this.titleNode.innerText = sanitize(
|
||||
nls.localize(
|
||||
'arduino/versionWelcome/titleWithVersion',
|
||||
'Welcome to the new Arduino IDE {0}!',
|
||||
appVersion
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected override onAfterAttach(msg: Message): void {
|
||||
this.update();
|
||||
this.appendButtons();
|
||||
this.updateTitleVersion();
|
||||
super.onAfterAttach(msg);
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.878141 10.6219C0.960188 10.7039 1.07147 10.75 1.1875 10.75H10.8125C10.9285 10.75 11.0398 10.7039 11.1219 10.6219C11.2039 10.5398 11.25 10.4285 11.25 10.3125V6.81252C11.25 6.69648 11.2039 6.5852 11.1219 6.50316C11.0398 6.42111 10.9285 6.37502 10.8125 6.37502C10.6965 6.37502 10.5852 6.42111 10.5031 6.50316C10.4211 6.5852 10.375 6.69648 10.375 6.81252V9.87502H1.625V1.12502H4.6875C4.80353 1.12502 4.91481 1.07892 4.99686 0.996874C5.07891 0.914827 5.125 0.803548 5.125 0.687515C5.125 0.571483 5.07891 0.460203 4.99686 0.378156C4.91481 0.296109 4.80353 0.250015 4.6875 0.250015H1.1875C1.07147 0.250015 0.960188 0.296109 0.878141 0.378156C0.796094 0.460203 0.75 0.571483 0.75 0.687515V10.3125C0.75 10.4285 0.796094 10.5398 0.878141 10.6219ZM11.25 4.62502V0.687515C11.25 0.571483 11.2039 0.460203 11.1219 0.378156C11.0398 0.296109 10.9285 0.250015 10.8125 0.250015H6.875C6.75897 0.250015 6.64769 0.296109 6.56564 0.378156C6.48359 0.460203 6.4375 0.571483 6.4375 0.687515C6.4375 0.803548 6.48359 0.914827 6.56564 0.996874C6.64769 1.07892 6.75897 1.12502 6.875 1.12502H9.75375L5.68937 5.18939C5.64837 5.23006 5.61582 5.27845 5.59361 5.33176C5.5714 5.38508 5.55996 5.44226 5.55996 5.50002C5.55996 5.55777 5.5714 5.61495 5.59361 5.66827C5.61582 5.72158 5.64837 5.76997 5.68937 5.81064C5.73005 5.85165 5.77843 5.88419 5.83175 5.90641C5.88506 5.92862 5.94224 5.94005 6 5.94005C6.05776 5.94005 6.11494 5.92862 6.16825 5.90641C6.22157 5.88419 6.26995 5.85165 6.31062 5.81064L10.375 1.74627V4.62502C10.375 4.74105 10.4211 4.85233 10.5031 4.93437C10.5852 5.01642 10.6965 5.06252 10.8125 5.06252C10.9285 5.06252 11.0398 5.01642 11.1219 4.93437C11.2039 4.85233 11.25 4.74105 11.25 4.62502Z" fill="#008184"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@@ -30,7 +30,7 @@ export class IDEUpdaterCommands implements CommandContribution {
|
||||
try {
|
||||
const updateInfo = await this.updater.checkForUpdates(initialCheck);
|
||||
if (!!updateInfo) {
|
||||
this.updaterDialog.open(updateInfo);
|
||||
this.updaterDialog.open(true, updateInfo);
|
||||
} else {
|
||||
this.messageService.info(
|
||||
nls.localize(
|
||||
|
@@ -12,15 +12,13 @@ import {
|
||||
LibrarySearch,
|
||||
LibraryService,
|
||||
} from '../../common/protocol/library-service';
|
||||
import {
|
||||
ListWidget,
|
||||
UserAbortError,
|
||||
} from '../widgets/component-list/list-widget';
|
||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||
import { Installable } from '../../common/protocol';
|
||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';
|
||||
import { findChildTheiaButton, splitByBoldTag } from '../utils/dom';
|
||||
import { UserAbortError } from '../../common/protocol/progressible';
|
||||
|
||||
@injectable()
|
||||
export class LibraryListWidget extends ListWidget<
|
||||
|
@@ -6,7 +6,7 @@ import {
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import {
|
||||
IndexUpdateDidCompleteParams,
|
||||
IndexUpdateDidFailParams,
|
||||
@@ -46,7 +46,7 @@ export class NotificationCenter
|
||||
new Emitter<ProgressMessage>();
|
||||
private readonly indexUpdateDidFailEmitter =
|
||||
new Emitter<IndexUpdateDidFailParams>();
|
||||
private readonly daemonDidStartEmitter = new Emitter<string>();
|
||||
private readonly daemonDidStartEmitter = new Emitter<number>();
|
||||
private readonly daemonDidStopEmitter = new Emitter<void>();
|
||||
private readonly configDidChangeEmitter = new Emitter<ConfigState>();
|
||||
private readonly platformDidInstallEmitter = new Emitter<{
|
||||
@@ -136,7 +136,7 @@ export class NotificationCenter
|
||||
this.indexUpdateDidFailEmitter.fire(params);
|
||||
}
|
||||
|
||||
notifyDaemonDidStart(port: string): void {
|
||||
notifyDaemonDidStart(port: number): void {
|
||||
this.daemonDidStartEmitter.fire(port);
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { Sketch, SketchesService } from '../common/protocol';
|
||||
import { ConfigServiceClient } from './config/config-service-client';
|
||||
import {
|
||||
@@ -74,6 +74,7 @@ export class SketchesServiceClientImpl
|
||||
const sketchDirUri = this.configService.tryGetSketchDirUri();
|
||||
this.watchSketchbookDir(sketchDirUri);
|
||||
const refreshCurrentSketch = async () => {
|
||||
await this.workspaceService.ready;
|
||||
const currentSketch = await this.loadCurrentSketch();
|
||||
const ideTempFolderUri = await this.getIdeTempFolderUriForSketch(
|
||||
currentSketch
|
||||
@@ -287,7 +288,7 @@ export class SketchesServiceClientImpl
|
||||
* `true` if the `uri` is not contained in any of the opened workspaces. Otherwise, `false`.
|
||||
*/
|
||||
isReadOnly(uri: URI | monaco.Uri | string): boolean {
|
||||
const toCheck = uri instanceof URI ? uri : new URI(uri);
|
||||
const toCheck = uri instanceof URI ? uri : new URI(uri.toString());
|
||||
if (toCheck.scheme === 'user-storage') {
|
||||
return false;
|
||||
}
|
||||
|
@@ -98,16 +98,12 @@
|
||||
color: var(--theia-textLink-foreground);
|
||||
}
|
||||
|
||||
.account-icon {
|
||||
img.arduino-account-picture {
|
||||
width: var(--theia-private-sidebar-icon-size);
|
||||
height: var(--theia-private-sidebar-icon-size);
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.account-icon > img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.connected-status-icon {
|
||||
|
@@ -1,36 +1,13 @@
|
||||
/* TODO: remove after https://github.com/eclipse-theia/theia/pull/9256/ */
|
||||
|
||||
/* To fix colors in Theia. */
|
||||
.theia-debug-hover-title.number,
|
||||
.theia-debug-console-variable.number {
|
||||
color: var(--theia-variable-number-variable-color);
|
||||
}
|
||||
.theia-debug-hover-title.boolean,
|
||||
.theia-debug-console-variable.boolean {
|
||||
color: var(--theia-variable-boolean-variable-color);
|
||||
}
|
||||
.theia-debug-hover-title.string,
|
||||
.theia-debug-console-variable.string {
|
||||
color: var(--theia-variable-string-variable-color);
|
||||
/* Naive way of hiding the debug widget when the debug functionality is disabled https://github.com/arduino/arduino-ide/issues/14 */
|
||||
.theia-debug-container .debug-toolbar.hidden,
|
||||
.theia-debug-container .theia-session-container.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* To unset the default debug hover dimension. */
|
||||
.theia-debug-hover {
|
||||
min-width: unset;
|
||||
min-height: unset;
|
||||
width: unset;
|
||||
height: unset;
|
||||
}
|
||||
.theia-debug-container .status-message {
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-size: 12px;
|
||||
|
||||
/* To adjust the left padding in the hover title. */
|
||||
.theia-debug-hover-title {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/* Use the default Theia dimensions only iff the expression is complex (`!!expression.hasChildren~) */
|
||||
.theia-debug-hover.complex-value {
|
||||
min-width: 324px;
|
||||
min-height: 324px;
|
||||
width: 324px;
|
||||
height: 324px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
@@ -34,6 +34,37 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ide-updater-dialog--footer {
|
||||
display: inline-block;
|
||||
margin-top: -16px;
|
||||
padding: 12px 0 24px 0;
|
||||
border-top: 1px solid var(--theia-editorWidget-border);
|
||||
}
|
||||
.ide-updater-dialog--footer-content {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.ide-updater-dialog--footer-link {
|
||||
display: inline-block;
|
||||
color: var(--theia-textLink-foreground);
|
||||
font-weight: 500;
|
||||
line-height: 13px;
|
||||
}
|
||||
.ide-updater-dialog--footer-link:hover {
|
||||
color: var(--theia-textLink-foreground);
|
||||
cursor: pointer;
|
||||
}
|
||||
.ide-updater-dialog--footer-link-icon {
|
||||
display: inline-block;
|
||||
-webkit-mask: url(../icons/link-open-icon.svg) center no-repeat;
|
||||
background-color: var(--theia-textLink-foreground);
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
cursor: pointer;
|
||||
transform: translateY(2px);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog {
|
||||
color: var(--theia-editor-foreground);
|
||||
background-color: var(--theia-editor-background);
|
||||
@@ -109,6 +140,7 @@
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
padding-bottom: 20px !important;
|
||||
}
|
||||
|
||||
#ide-updater-dialog-container .skip-version-button {
|
||||
|
@@ -10,6 +10,7 @@
|
||||
@import "./settings-dialog.css";
|
||||
@import "./firmware-uploader-dialog.css";
|
||||
@import "./ide-updater-dialog.css";
|
||||
@import "./version-welcome-dialog.css";
|
||||
@import "./certificate-uploader-dialog.css";
|
||||
@import "./user-fields-dialog.css";
|
||||
@import "./debug.css";
|
||||
|
@@ -0,0 +1,7 @@
|
||||
#version-welcome-dialog-container > .dialogBlock {
|
||||
width: 546px;
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
@@ -13,6 +13,8 @@ import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { ApplicationConnectionStatusContribution } from './connection-status-service';
|
||||
import { ToolbarAwareTabBar } from './tab-bars';
|
||||
import { find } from '@theia/core/shared/@phosphor/algorithm';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
|
||||
@injectable()
|
||||
export class ApplicationShell extends TheiaApplicationShell {
|
||||
@@ -48,6 +50,38 @@ export class ApplicationShell extends TheiaApplicationShell {
|
||||
return super.addWidget(widget, { ...options, ref });
|
||||
}
|
||||
|
||||
override doRevealWidget(id: string): Widget | undefined {
|
||||
let widget = find(this.mainPanel.widgets(), (w) => w.id === id);
|
||||
if (!widget) {
|
||||
widget = find(this.bottomPanel.widgets(), (w) => w.id === id);
|
||||
if (widget) {
|
||||
this.expandBottomPanel();
|
||||
}
|
||||
}
|
||||
if (widget) {
|
||||
const tabBar = this.getTabBarFor(widget);
|
||||
if (tabBar) {
|
||||
tabBar.currentTitle = widget.title;
|
||||
}
|
||||
}
|
||||
if (!widget) {
|
||||
widget = this.leftPanelHandler.expand(id);
|
||||
}
|
||||
if (!widget) {
|
||||
widget = this.rightPanelHandler.expand(id);
|
||||
}
|
||||
if (widget) {
|
||||
// Prevent focusing the output widget when is updated
|
||||
// See https://github.com/arduino/arduino-ide/issues/2679
|
||||
if (!(widget instanceof OutputWidget)) {
|
||||
this.windowService.focus();
|
||||
}
|
||||
return widget;
|
||||
} else {
|
||||
return this.secondaryWindowHandler.revealWidget(id);
|
||||
}
|
||||
}
|
||||
|
||||
override handleEvent(): boolean {
|
||||
// NOOP, dragging has been disabled
|
||||
return false;
|
||||
|
@@ -2,7 +2,7 @@ import {
|
||||
CommonCommands,
|
||||
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
||||
} from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import type { OnWillStopAction } from '@theia/core/lib/browser/frontend-application';
|
||||
import type { OnWillStopAction } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import type { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import type { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import type { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
||||
ConnectionStatus,
|
||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
||||
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import type { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/index';
|
||||
import type { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { WebSocketConnectionSource } from '@theia/core/lib/browser/messaging/ws-connection-source';
|
||||
import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
@@ -74,8 +74,8 @@ export class DaemonPort implements FrontendApplicationContribution {
|
||||
@inject(NotificationCenter)
|
||||
private readonly notificationCenter: NotificationCenter;
|
||||
|
||||
private readonly onPortDidChangeEmitter = new Emitter<string | undefined>();
|
||||
private _port: string | undefined;
|
||||
private readonly onPortDidChangeEmitter = new Emitter<number | undefined>();
|
||||
private _port: number | undefined;
|
||||
|
||||
onStart(): void {
|
||||
this.daemon.tryGetPort().then(
|
||||
@@ -91,15 +91,15 @@ export class DaemonPort implements FrontendApplicationContribution {
|
||||
this.onPortDidChangeEmitter.dispose();
|
||||
}
|
||||
|
||||
get port(): string | undefined {
|
||||
get port(): number | undefined {
|
||||
return this._port;
|
||||
}
|
||||
|
||||
get onDidChangePort(): Event<string | undefined> {
|
||||
get onDidChangePort(): Event<number | undefined> {
|
||||
return this.onPortDidChangeEmitter.event;
|
||||
}
|
||||
|
||||
private setPort(port: string | undefined): void {
|
||||
private setPort(port: number | undefined): void {
|
||||
const oldPort = this._port;
|
||||
this._port = port;
|
||||
if (this._port !== oldPort) {
|
||||
@@ -114,8 +114,8 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
|
||||
private readonly daemonPort: DaemonPort;
|
||||
@inject(IsOnline)
|
||||
private readonly isOnline: IsOnline;
|
||||
@inject(WebSocketConnectionProvider)
|
||||
private readonly connectionProvider: WebSocketConnectionProvider;
|
||||
@inject(WebSocketConnectionSource)
|
||||
private readonly connectionSource: WebSocketConnectionSource;
|
||||
|
||||
@postConstruct()
|
||||
protected override init(): void {
|
||||
@@ -128,7 +128,7 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
|
||||
}
|
||||
|
||||
protected override async performPingRequest(): Promise<void> {
|
||||
if (!this.connectionProvider['socket'].connected) {
|
||||
if (!this.connectionSource['socket'].connected) {
|
||||
this.updateStatus(false);
|
||||
return;
|
||||
}
|
||||
@@ -171,8 +171,8 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon
|
||||
private readonly notificationManager: NotificationManager;
|
||||
@inject(CreateFeatures)
|
||||
private readonly createFeatures: CreateFeatures;
|
||||
@inject(WebSocketConnectionProvider)
|
||||
private readonly connectionProvider: WebSocketConnectionProvider;
|
||||
@inject(WebSocketConnectionSource)
|
||||
private readonly connectionSource: WebSocketConnectionSource;
|
||||
|
||||
private readonly offlineStatusDidChangeEmitter = new Emitter<
|
||||
OfflineConnectionStatus | undefined
|
||||
@@ -202,7 +202,7 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon
|
||||
const params = <OfflineMessageParams>{
|
||||
port: this.daemonPort.port,
|
||||
online: this.isOnline.online,
|
||||
backendConnected: this.connectionProvider['socket'].connected, // https://github.com/arduino/arduino-ide/issues/2081
|
||||
backendConnected: this.connectionSource['socket'].connected, // https://github.com/arduino/arduino-ide/issues/2081
|
||||
};
|
||||
this._offlineStatus = offlineConnectionStatusType(params);
|
||||
const { text, tooltip } = offlineMessage(params);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { SidebarBottomMenuWidget as TheiaSidebarBottomMenuWidget } from '@theia/core/lib/browser/shell/sidebar-bottom-menu-widget';
|
||||
import type { SidebarMenu } from '@theia/core/lib/browser/shell/sidebar-menu-widget';
|
||||
import type { SidebarMenuItem } from '@theia/core/lib/browser/shell/sidebar-menu-widget';
|
||||
import type { MenuPath } from '@theia/core/lib/common/menu';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import {
|
||||
@@ -46,46 +46,45 @@ export class SidebarBottomMenuWidget extends TheiaSidebarBottomMenuWidget {
|
||||
this.contextMenuRenderer.render(options);
|
||||
}
|
||||
|
||||
protected override render(): React.ReactNode {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.menus.map((menu) => this.renderMenu(menu))}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private renderMenu(menu: SidebarMenu): React.ReactNode {
|
||||
override renderItem(item: SidebarMenuItem): React.ReactNode {
|
||||
// Removes the _Settings_ (cog) icon from the left sidebar
|
||||
if (menu.id === 'settings-menu') {
|
||||
if (item.menu.id === 'settings-menu') {
|
||||
return undefined;
|
||||
}
|
||||
const arduinoAccount = menu.id === accountMenu.id;
|
||||
const picture =
|
||||
const arduinoAccount = item.menu.id === accountMenu.id;
|
||||
const arduinoAccountPicture =
|
||||
arduinoAccount &&
|
||||
this.connectionStatue.offlineStatus !== 'internet' &&
|
||||
this.createFeatures.session?.account.picture;
|
||||
const className = typeof picture === 'string' ? undefined : menu.iconClass;
|
||||
|
||||
return (
|
||||
<i
|
||||
key={menu.id}
|
||||
className={className}
|
||||
title={menu.title}
|
||||
onClick={(e) => this.onClick(e, menu.menuPath)}
|
||||
<div
|
||||
key={item.menu.id}
|
||||
className="theia-sidebar-menu-item"
|
||||
title={item.menu.title}
|
||||
onClick={(e) => this.onClick(e, item.menu.menuPath)}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onMouseEnter={(e) => this.onMouseEnter(e, item.menu.title)}
|
||||
onMouseOut={this.onMouseOut}
|
||||
>
|
||||
{picture && (
|
||||
<div className="account-icon">
|
||||
{arduinoAccountPicture ? (
|
||||
<i>
|
||||
<img
|
||||
src={picture}
|
||||
className="arduino-account-picture"
|
||||
src={arduinoAccountPicture}
|
||||
alt={nls.localize(
|
||||
'arduino/cloud/profilePicture',
|
||||
'Profile picture'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</i>
|
||||
) : (
|
||||
<i className={item.menu.iconClass} />
|
||||
)}
|
||||
</i>
|
||||
{item.badge && (
|
||||
<div className="theia-badge-decorator-sidebar">{item.badge}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { SelectOption } from '@theia/core/lib/browser/widgets/select-component';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
@@ -51,6 +52,24 @@ class DebugConfigurationSelect extends TheiaDebugConfigurationSelect {
|
||||
);
|
||||
}
|
||||
|
||||
protected override renderOptions(): SelectOption[] {
|
||||
const options = super.renderOptions();
|
||||
const addConfiguration = options[options.length - 1];
|
||||
const separator = options[options.length - 2];
|
||||
// Remove "Add configuration..." and the preceding separator options.
|
||||
// They're expected to be the last two items.
|
||||
if (
|
||||
addConfiguration.value ===
|
||||
TheiaDebugConfigurationSelect.ADD_CONFIGURATION &&
|
||||
separator.separator
|
||||
) {
|
||||
options.splice(options.length - 2, 2);
|
||||
return options;
|
||||
}
|
||||
// Something is unexpected with the select options.
|
||||
return options;
|
||||
}
|
||||
|
||||
override componentWillUnmount(): void {
|
||||
this.toDisposeOnUnmount.dispose();
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection';
|
||||
import { DefaultDebugSessionFactory as TheiaDefaultDebugSessionFactory } from '@theia/debug/lib/browser/debug-session-contribution';
|
||||
import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||
import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
|
||||
import {
|
||||
DebugAdapterPath,
|
||||
@@ -12,6 +13,7 @@ import { DebugSession } from './debug-session';
|
||||
@injectable()
|
||||
export class DefaultDebugSessionFactory extends TheiaDefaultDebugSessionFactory {
|
||||
override get(
|
||||
manager: DebugSessionManager,
|
||||
sessionId: string,
|
||||
options: DebugConfigurationSessionOptions,
|
||||
parentSession?: DebugSession
|
||||
@@ -20,12 +22,12 @@ export class DefaultDebugSessionFactory extends TheiaDefaultDebugSessionFactory
|
||||
sessionId,
|
||||
() =>
|
||||
new Promise<DebugChannel>((resolve) =>
|
||||
this.connectionProvider.openChannel(
|
||||
this.connectionProvider.listen(
|
||||
`${DebugAdapterPath}/${sessionId}`,
|
||||
(wsChannel) => {
|
||||
(_, wsChannel) => {
|
||||
resolve(new ForwardingDebugChannel(wsChannel));
|
||||
},
|
||||
{ reconnecting: false }
|
||||
false
|
||||
)
|
||||
),
|
||||
this.getTraceOutputChannel()
|
||||
@@ -35,6 +37,9 @@ export class DefaultDebugSessionFactory extends TheiaDefaultDebugSessionFactory
|
||||
sessionId,
|
||||
options,
|
||||
parentSession,
|
||||
this.testService,
|
||||
options.testRun,
|
||||
manager,
|
||||
connection,
|
||||
this.terminalService,
|
||||
this.editorManager,
|
||||
|
@@ -0,0 +1,56 @@
|
||||
import { codicon } from '@theia/core/lib/browser/widgets/widget';
|
||||
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { DebugWidget as TheiaDebugWidget } from '@theia/debug/lib/browser/view/debug-widget';
|
||||
import { DebugDisabledStatusMessageSource } from '../../contributions/debug';
|
||||
import {
|
||||
removeWidgetIfPresent,
|
||||
unshiftWidgetIfNotPresent,
|
||||
} from '../dialogs/widgets';
|
||||
|
||||
@injectable()
|
||||
export class DebugWidget extends TheiaDebugWidget {
|
||||
@inject(DebugDisabledStatusMessageSource)
|
||||
private readonly debugStatusMessageSource: DebugDisabledStatusMessageSource;
|
||||
|
||||
private readonly statusMessageWidget = new Widget();
|
||||
private readonly messageNode = document.createElement('div');
|
||||
|
||||
@postConstruct()
|
||||
protected override init(): void {
|
||||
super.init();
|
||||
this.messageNode.classList.add('status-message', 'noselect');
|
||||
this.statusMessageWidget.node.appendChild(this.messageNode);
|
||||
this.updateState();
|
||||
this.toDisposeOnDetach.pushAll([
|
||||
this.debugStatusMessageSource.onDidChangeMessage((message) =>
|
||||
this.updateState(message)
|
||||
),
|
||||
this.statusMessageWidget,
|
||||
]);
|
||||
}
|
||||
|
||||
private updateState(message = this.debugStatusMessageSource.message): void {
|
||||
requestAnimationFrame(() => {
|
||||
this.messageNode.textContent = message ?? '';
|
||||
const enabled = !message;
|
||||
updateVisibility(enabled, this.toolbar, this.sessionWidget);
|
||||
if (enabled) {
|
||||
removeWidgetIfPresent(this.layout, this.statusMessageWidget);
|
||||
} else {
|
||||
unshiftWidgetIfNotPresent(this.layout, this.statusMessageWidget);
|
||||
}
|
||||
this.title.iconClass = enabled ? codicon('debug-alt') : 'fa fa-ban'; // TODO: find a better icon?
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateVisibility(visible: boolean, ...widgets: Widget[]): void {
|
||||
widgets.forEach((widget) =>
|
||||
visible ? widget.removeClass('hidden') : widget.addClass('hidden')
|
||||
);
|
||||
}
|
51
arduino-ide-extension/src/browser/theia/dialogs/widgets.ts
Normal file
51
arduino-ide-extension/src/browser/theia/dialogs/widgets.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
Layout,
|
||||
PanelLayout,
|
||||
Widget,
|
||||
} from '@theia/core/shared/@phosphor/widgets';
|
||||
|
||||
/**
|
||||
*
|
||||
* Removes the widget from the layout if the `layout` is a `PanelLayout` and the widget is present in the layout.
|
||||
* Otherwise, it's NOOP
|
||||
* @param layout the layout to remove the widget from. Must be a `PanelLayout`.
|
||||
* @param toRemove the widget to remove from the layout
|
||||
*/
|
||||
export function removeWidgetIfPresent(
|
||||
layout: Layout | null,
|
||||
toRemove: Widget
|
||||
): void {
|
||||
if (layout instanceof PanelLayout) {
|
||||
const index = layout.widgets.indexOf(toRemove);
|
||||
if (index < 0) {
|
||||
// Unlike the default `PanelLayout#removeWidget` behavior, (https://github.com/phosphorjs/phosphor/blob/9f5e11025b62d2c4a6fb59e2681ae1ed323dcde4/packages/widgets/src/panellayout.ts#L154-L156)
|
||||
// do not try to remove widget if it's not present (the index is negative).
|
||||
// Otherwise, required widgets could be removed based on the default ArrayExt behavior (https://github.com/phosphorjs/phosphor/blob/9f5e11025b62d2c4a6fb59e2681ae1ed323dcde4/packages/algorithm/src/array.ts#L1075-L1077)
|
||||
// See https://github.com/arduino/arduino-ide/issues/2354 for more details.
|
||||
return;
|
||||
}
|
||||
layout.removeWidget(toRemove);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Inserts the widget to the `0` index of the layout if the `layout` is a `PanelLayout` and the widget is not yet part of the layout.
|
||||
* Otherwise, it's NOOP
|
||||
* @param layout the layout to add the widget to. Must be a `PanelLayout`.
|
||||
* @param toAdd the widget to add to the layout
|
||||
*/
|
||||
export function unshiftWidgetIfNotPresent(
|
||||
layout: Layout | null,
|
||||
toAdd: Widget
|
||||
): void {
|
||||
if (layout instanceof PanelLayout) {
|
||||
const index = layout.widgets.indexOf(toAdd);
|
||||
if (index >= 0) {
|
||||
// Do not try to add the widget to the layout if it's already present.
|
||||
// This is the counterpart logic of the `removeWidgetIfPresent` function.
|
||||
return;
|
||||
}
|
||||
layout.insertWidget(0, toAdd);
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
import { injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import { EditorCommandContribution as TheiaEditorCommandContribution } from '@theia/editor/lib/browser/editor-command';
|
||||
|
||||
@injectable()
|
||||
export class EditorCommandContribution extends TheiaEditorCommandContribution {
|
||||
@postConstruct()
|
||||
protected override init(): void {
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/8722.
|
||||
this.editorPreferences.onPreferenceChanged(
|
||||
({ preferenceName, newValue, oldValue }) => {
|
||||
if (preferenceName === 'files.autoSave') {
|
||||
const autoSaveWasOnBeforeChange = !oldValue || oldValue !== 'off';
|
||||
const autoSaveIsOnAfterChange = !newValue || newValue !== 'off';
|
||||
if (!autoSaveWasOnBeforeChange && autoSaveIsOnAfterChange) {
|
||||
this.shell.saveAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,19 +1,18 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { TextEditor } from '@theia/editor/lib/browser';
|
||||
import { EditorContribution as TheiaEditorContribution } from '@theia/editor/lib/browser/editor-contribution';
|
||||
|
||||
@injectable()
|
||||
export class EditorContribution extends TheiaEditorContribution {
|
||||
protected override updateLanguageStatus(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
|
||||
editor: TextEditor | undefined
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
..._: Parameters<TheiaEditorContribution['updateLanguageStatus']>
|
||||
): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
protected override updateEncodingStatus(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
|
||||
editor: TextEditor | undefined
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
..._: Parameters<TheiaEditorContribution['updateEncodingStatus']>
|
||||
): void {
|
||||
// https://github.com/arduino/arduino-ide/issues/1393
|
||||
// NOOP
|
||||
|
@@ -36,7 +36,7 @@ export class FileResourceResolver extends TheiaFileResourceResolver {
|
||||
);
|
||||
}
|
||||
return new WriteQueuedFileResource(uri, this.fileService, {
|
||||
isReadonly: stat?.isReadonly ?? false,
|
||||
readOnly: stat?.isReadonly ?? false,
|
||||
shouldOverwrite: () => this.shouldOverwrite(uri),
|
||||
shouldOpenAsText: (error) => this.shouldOpenAsText(uri, error),
|
||||
});
|
||||
|
@@ -2,6 +2,7 @@ import React from '@theia/core/shared/react';
|
||||
import { NotificationComponent as TheiaNotificationComponent } from '@theia/messages/lib/browser/notification-component';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { codicon } from '@theia/core/lib/browser';
|
||||
import { sanitize } from 'dompurify';
|
||||
|
||||
export class NotificationComponent extends TheiaNotificationComponent {
|
||||
override render(): React.ReactNode {
|
||||
@@ -20,7 +21,7 @@ export class NotificationComponent extends TheiaNotificationComponent {
|
||||
/>
|
||||
<div className="theia-notification-message">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: message }}
|
||||
dangerouslySetInnerHTML={{ __html: sanitize(message) }}
|
||||
onClick={this.onMessageClick}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -117,7 +117,7 @@ export function maybeUpdateReadOnlyState(
|
||||
const model = editor.document;
|
||||
const oldReadOnly = model.readOnly;
|
||||
const resource = model['resource'];
|
||||
const newReadOnly = Boolean(resource.isReadonly) || isReadOnly(resource.uri);
|
||||
const newReadOnly = Boolean(resource.readOnly) || isReadOnly(resource.uri);
|
||||
if (oldReadOnly !== newReadOnly) {
|
||||
editor.getControl().updateOptions({ readOnly: newReadOnly });
|
||||
if (newReadOnly) {
|
||||
|
@@ -20,7 +20,7 @@ export class MonacoTextModelService extends TheiaMonacoTextModelService {
|
||||
.getContributions()
|
||||
.find(({ scheme }) => resource.uri.scheme === scheme);
|
||||
const readOnly =
|
||||
Boolean(resource.isReadonly) ||
|
||||
Boolean(resource.readOnly) ||
|
||||
this.sketchesServiceClient.isReadOnly(resource.uri);
|
||||
return factory
|
||||
? factory.createModel(resource)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||
import {
|
||||
Disposable,
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import type { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { injectable, interfaces } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
PluginContributions,
|
||||
HostedPluginSupport as TheiaHostedPluginSupport,
|
||||
} from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import { HostedPluginSupport } from '../../hosted/hosted-plugin-support';
|
||||
import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import type { PluginContributions } from '@theia/plugin-ext/lib/hosted/common/hosted-plugin';
|
||||
import type { HostedPluginSupport } from '../../hosted/hosted-plugin-support';
|
||||
|
||||
@injectable()
|
||||
export class HostedPluginSupportImpl
|
||||
|
@@ -0,0 +1,9 @@
|
||||
import { TestViewContribution as TheiaTestViewContribution } from '@theia/test/lib/browser/view/test-view-contribution';
|
||||
import { injectable } from 'inversify';
|
||||
|
||||
@injectable()
|
||||
export class TestViewContribution extends TheiaTestViewContribution {
|
||||
override async initializeLayout(): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ import {
|
||||
TabBarToolbarRegistry,
|
||||
TabBarToolbarItem,
|
||||
ReactTabBarToolbarItem,
|
||||
RenderedToolbarItem,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { ReactWidget } from '@theia/core/lib/browser';
|
||||
@@ -14,7 +15,7 @@ export const ARDUINO_TOOLBAR_ITEM_CLASS = 'arduino-tool-item';
|
||||
export namespace ArduinoToolbarComponent {
|
||||
export interface Props {
|
||||
side: 'left' | 'right';
|
||||
items: (TabBarToolbarItem | ReactTabBarToolbarItem)[];
|
||||
items: TabBarToolbarItem[];
|
||||
commands: CommandRegistry;
|
||||
labelParser: LabelParser;
|
||||
commandIsEnabled: (id: string) => boolean;
|
||||
@@ -34,7 +35,7 @@ export class ArduinoToolbarComponent extends React.Component<
|
||||
this.state = { tooltip: '' };
|
||||
}
|
||||
|
||||
protected renderItem = (item: TabBarToolbarItem) => {
|
||||
protected renderItem = (item: RenderedToolbarItem) => {
|
||||
let innerText = '';
|
||||
let className = `arduino-tool-icon ${item.id}-icon`;
|
||||
if (item.text) {
|
||||
@@ -46,7 +47,8 @@ export class ArduinoToolbarComponent extends React.Component<
|
||||
}
|
||||
}
|
||||
}
|
||||
const command = this.props.commands.getCommand(item.command);
|
||||
const command =
|
||||
item.command && this.props.commands.getCommand(item.command);
|
||||
const cls = `${ARDUINO_TOOLBAR_ITEM_CLASS} ${
|
||||
TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM
|
||||
} ${command && this.props.commandIsEnabled(command.id) ? 'enabled' : ''} ${
|
||||
@@ -80,7 +82,9 @@ export class ArduinoToolbarComponent extends React.Component<
|
||||
const items = [
|
||||
<React.Fragment key={this.props.side + '-arduino-toolbar-tooltip'}>
|
||||
{[...this.props.items].map((item) =>
|
||||
TabBarToolbarItem.is(item) ? this.renderItem(item) : item.render()
|
||||
ReactTabBarToolbarItem.is(item)
|
||||
? item.render()
|
||||
: this.renderItem(item)
|
||||
)}
|
||||
</React.Fragment>,
|
||||
];
|
||||
@@ -94,10 +98,7 @@ export class ArduinoToolbarComponent extends React.Component<
|
||||
}
|
||||
|
||||
export class ArduinoToolbar extends ReactWidget {
|
||||
protected items = new Map<
|
||||
string,
|
||||
TabBarToolbarItem | ReactTabBarToolbarItem
|
||||
>();
|
||||
protected items = new Map<string, TabBarToolbarItem>();
|
||||
|
||||
constructor(
|
||||
protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry,
|
||||
@@ -112,9 +113,7 @@ export class ArduinoToolbar extends ReactWidget {
|
||||
this.tabBarToolbarRegistry.onDidChange(() => this.updateToolbar());
|
||||
}
|
||||
|
||||
protected updateItems(
|
||||
items: Array<TabBarToolbarItem | ReactTabBarToolbarItem>
|
||||
): void {
|
||||
protected updateItems(items: Array<TabBarToolbarItem>): void {
|
||||
this.items.clear();
|
||||
const revItems = items
|
||||
.sort(TabBarToolbarItem.PRIORITY_COMPARATOR)
|
||||
@@ -163,7 +162,7 @@ export class ArduinoToolbar extends ReactWidget {
|
||||
|
||||
protected executeCommand = (e: React.MouseEvent<HTMLElement>) => {
|
||||
const item = this.items.get(e.currentTarget.id);
|
||||
if (TabBarToolbarItem.is(item)) {
|
||||
if (item && item.command) {
|
||||
this.commands.executeCommand(item.command, this, e.target);
|
||||
}
|
||||
};
|
||||
|
@@ -2,7 +2,7 @@ import React from '@theia/core/shared/react';
|
||||
import type { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { Installable } from '../../../common/protocol/installable';
|
||||
import type { ListItemRenderer } from './list-item-renderer';
|
||||
import { UserAbortError } from './list-widget';
|
||||
import { UserAbortError } from '../../../common/protocol/progressible';
|
||||
|
||||
export class ComponentListItem<
|
||||
T extends ArduinoComponent
|
||||
|
@@ -5,7 +5,10 @@ import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
||||
import { Searchable } from '../../../common/protocol/searchable';
|
||||
import { ExecuteWithProgress } from '../../../common/protocol/progressible';
|
||||
import {
|
||||
ExecuteWithProgress,
|
||||
UserAbortError,
|
||||
} from '../../../common/protocol/progressible';
|
||||
import {
|
||||
Installable,
|
||||
libraryInstallFailed,
|
||||
@@ -13,7 +16,7 @@ import {
|
||||
} from '../../../common/protocol/installable';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { SearchBar } from './search-bar';
|
||||
import { ListWidget, UserAbortError } from './list-widget';
|
||||
import { ListWidget } from './list-widget';
|
||||
import { ComponentList } from './component-list';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
import {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import {
|
||||
OpenerOptions,
|
||||
OpenHandler,
|
||||
|
@@ -192,10 +192,3 @@ export namespace ListWidget {
|
||||
readonly defaultSearchOptions: S;
|
||||
}
|
||||
}
|
||||
|
||||
export class UserAbortError extends Error {
|
||||
constructor(message = 'User abort') {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, UserAbortError.prototype);
|
||||
}
|
||||
}
|
||||
|
@@ -127,6 +127,9 @@ export class SketchbookTreeModel extends FileTreeModel {
|
||||
if (preferenceName === 'arduino.sketchbook.showAllFiles') {
|
||||
this.updateRoot();
|
||||
}
|
||||
if (preferenceName === 'arduino.cloud.sharedSpaceID') {
|
||||
this.updateRoot();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service';
|
||||
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application-contribution';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
import { ArduinoPreferences } from '../../arduino-preferences';
|
||||
import { SketchbookWidget } from './sketchbook-widget';
|
||||
|
@@ -39,3 +39,5 @@ export const noSketchOpened = nls.localize(
|
||||
'arduino/common/noSketchOpened',
|
||||
'No sketch opened'
|
||||
);
|
||||
|
||||
export const userAbort = nls.localize('arduino/common/userAbort', 'User abort');
|
||||
|
@@ -5,14 +5,14 @@ export interface ArduinoDaemon {
|
||||
* Returns with a promise that resolves with the port
|
||||
* of the CLI daemon when it's up and running.
|
||||
*/
|
||||
getPort(): Promise<string>;
|
||||
getPort(): Promise<number>;
|
||||
/**
|
||||
* Unlike `getPort` this method returns with a promise
|
||||
* that resolves to `undefined` when the daemon is not running.
|
||||
* Otherwise resolves to the CLI daemon port.
|
||||
*/
|
||||
tryGetPort(): Promise<string | undefined>;
|
||||
start(): Promise<string>;
|
||||
tryGetPort(): Promise<number | undefined>;
|
||||
start(): Promise<number>;
|
||||
stop(): Promise<void>;
|
||||
restart(): Promise<string>;
|
||||
restart(): Promise<number>;
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { FQBN } from 'fqbn';
|
||||
import type { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import type URI from '@theia/core/lib/common/uri';
|
||||
import {
|
||||
@@ -66,7 +67,10 @@ export interface BoardsService
|
||||
skipPostInstall?: boolean;
|
||||
}): Promise<void>;
|
||||
getDetectedPorts(): Promise<DetectedPorts>;
|
||||
getBoardDetails(options: { fqbn: string }): Promise<BoardDetails | undefined>;
|
||||
getBoardDetails(options: {
|
||||
fqbn: string;
|
||||
forceRefresh?: boolean;
|
||||
}): Promise<BoardDetails | undefined>;
|
||||
getBoardPackage(options: {
|
||||
id: string /* TODO: change to PlatformIdentifier type? */;
|
||||
}): Promise<BoardsPackage | undefined>;
|
||||
@@ -75,6 +79,9 @@ export interface BoardsService
|
||||
}): Promise<BoardsPackage | undefined>;
|
||||
searchBoards({ query }: { query?: string }): Promise<BoardWithPackage[]>;
|
||||
getInstalledBoards(): Promise<BoardWithPackage[]>;
|
||||
/**
|
||||
* Returns with all installed platforms including the manually installed ones.
|
||||
*/
|
||||
getInstalledPlatforms(): Promise<BoardsPackage[]>;
|
||||
getBoardUserFields(options: {
|
||||
fqbn: string;
|
||||
@@ -95,7 +102,7 @@ export interface CheckDebugEnabledParams {
|
||||
* The FQBN might contain custom board config options. For example, `arduino:esp32:nano_nora:USBMode=hwcdc,option2=value2`.
|
||||
*/
|
||||
readonly fqbn: string;
|
||||
readonly programmer: string;
|
||||
readonly programmer?: string;
|
||||
}
|
||||
|
||||
export interface BoardSearch extends Searchable.Options {
|
||||
@@ -364,40 +371,6 @@ export interface ConfigOption {
|
||||
readonly values: ConfigValue[];
|
||||
}
|
||||
export namespace ConfigOption {
|
||||
/**
|
||||
* Appends the configuration options to the `fqbn` argument.
|
||||
* Throws an error if the `fqbn` does not have the `segment(':'segment)*` format.
|
||||
* The provided output format is always segment(':'segment)*(':'option'='value(','option'='value)*)?
|
||||
*/
|
||||
export function decorate(
|
||||
fqbn: string,
|
||||
configOptions: ConfigOption[]
|
||||
): string {
|
||||
if (!configOptions.length) {
|
||||
return fqbn;
|
||||
}
|
||||
|
||||
const toValue = (values: ConfigValue[]) => {
|
||||
const selectedValue = values.find(({ selected }) => selected);
|
||||
if (!selectedValue) {
|
||||
console.warn(
|
||||
`None of the config values was selected. Values were: ${JSON.stringify(
|
||||
values
|
||||
)}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return selectedValue.value;
|
||||
};
|
||||
const options = configOptions
|
||||
.map(({ option, values }) => [option, toValue(values)])
|
||||
.filter(([, value]) => !!value)
|
||||
.map(([option, value]) => `${option}=${value}`)
|
||||
.join(',');
|
||||
|
||||
return `${fqbn}:${options}`;
|
||||
}
|
||||
|
||||
export class ConfigOptionError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
@@ -571,28 +544,13 @@ export namespace Board {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error if the `fqbn` argument is not sanitized. A sanitized FQBN has the `VENDOR:ARCHITECTURE:BOARD_ID` construct.
|
||||
*/
|
||||
export function assertSanitizedFqbn(fqbn: string): void {
|
||||
if (fqbn.split(':').length !== 3) {
|
||||
throw new Error(
|
||||
`Expected a sanitized FQBN with three segments in the following format: 'VENDOR:ARCHITECTURE:BOARD_ID'. Got ${fqbn} instead.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to
|
||||
* `VENDOR:ARCHITECTURE:BOARD_ID` format.
|
||||
* See the details of the `{build.fqbn}` entry in the [specs](https://arduino.github.io/arduino-cli/latest/platform-specification/#global-predefined-properties).
|
||||
*/
|
||||
export function sanitizeFqbn(fqbn: string | undefined): string | undefined {
|
||||
if (!fqbn) {
|
||||
return undefined;
|
||||
}
|
||||
const [vendor, arch, id] = fqbn.split(':');
|
||||
return `${vendor}:${arch}:${id}`;
|
||||
export function sanitizeFqbn(fqbn: string): string {
|
||||
return new FQBN(fqbn).sanitize().toString();
|
||||
}
|
||||
|
||||
export type PlatformIdentifier = Readonly<{ vendorId: string; arch: string }>;
|
||||
@@ -749,8 +707,8 @@ export function boardIdentifierEquals(
|
||||
return false; // TODO: This a strict now. Maybe compare name in the future.
|
||||
}
|
||||
if (left.fqbn && right.fqbn) {
|
||||
const leftFqbn = options.looseFqbn ? sanitizeFqbn(left.fqbn) : left.fqbn;
|
||||
const rightFqbn = options.looseFqbn ? sanitizeFqbn(right.fqbn) : right.fqbn;
|
||||
const leftFqbn = new FQBN(left.fqbn).toString(options.looseFqbn);
|
||||
const rightFqbn = new FQBN(right.fqbn).toString(options.looseFqbn);
|
||||
return leftFqbn === rightFqbn;
|
||||
}
|
||||
// No more Genuino hack.
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { ApplicationError } from '@theia/core/lib/common/application-error';
|
||||
import type { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import type {
|
||||
Location,
|
||||
@@ -7,7 +8,7 @@ import type {
|
||||
} from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import type { CompileSummary as ApiCompileSummary } from 'vscode-arduino-api';
|
||||
import type { BoardUserField, Installable } from '../../common/protocol/';
|
||||
import { isPortIdentifier, PortIdentifier, Programmer } from './boards-service';
|
||||
import { PortIdentifier, Programmer, isPortIdentifier } from './boards-service';
|
||||
import type { IndexUpdateSummary } from './notification-service';
|
||||
import type { Sketch } from './sketches-service';
|
||||
|
||||
@@ -70,6 +71,7 @@ export namespace CoreError {
|
||||
Upload: 4002,
|
||||
UploadUsingProgrammer: 4003,
|
||||
BurnBootloader: 4004,
|
||||
UploadRequiresProgrammer: 4005,
|
||||
};
|
||||
export const VerifyFailed = declareCoreError(Codes.Verify);
|
||||
export const UploadFailed = declareCoreError(Codes.Upload);
|
||||
@@ -77,6 +79,10 @@ export namespace CoreError {
|
||||
Codes.UploadUsingProgrammer
|
||||
);
|
||||
export const BurnBootloaderFailed = declareCoreError(Codes.BurnBootloader);
|
||||
export const UploadRequiresProgrammer = declareCoreError(
|
||||
Codes.UploadRequiresProgrammer
|
||||
);
|
||||
|
||||
export function is(
|
||||
error: unknown
|
||||
): error is ApplicationError<number, ErrorLocation[]> {
|
||||
@@ -162,9 +168,18 @@ export function isUploadResponse(arg: unknown): arg is UploadResponse {
|
||||
export const CoreServicePath = '/services/core-service';
|
||||
export const CoreService = Symbol('CoreService');
|
||||
export interface CoreService {
|
||||
compile(options: CoreService.Options.Compile): Promise<void>;
|
||||
upload(options: CoreService.Options.Upload): Promise<UploadResponse>;
|
||||
burnBootloader(options: CoreService.Options.Bootloader): Promise<void>;
|
||||
compile(
|
||||
options: CoreService.Options.Compile,
|
||||
cancellationToken?: CancellationToken
|
||||
): Promise<CompileSummary | undefined>;
|
||||
upload(
|
||||
options: CoreService.Options.Upload,
|
||||
cancellationToken?: CancellationToken
|
||||
): Promise<UploadResponse>;
|
||||
burnBootloader(
|
||||
options: CoreService.Options.Bootloader,
|
||||
cancellationToken?: CancellationToken
|
||||
): Promise<void>;
|
||||
/**
|
||||
* Refreshes the underling core gRPC client for the Arduino CLI.
|
||||
*/
|
||||
|
@@ -71,3 +71,4 @@ export interface IDEUpdaterClient {
|
||||
}
|
||||
|
||||
export const SKIP_IDE_VERSION = 'skipIDEVersion';
|
||||
export const LAST_USED_IDE_VERSION = 'lastUsedIDEVersion';
|
||||
|
@@ -51,7 +51,7 @@ export interface NotificationServiceClient {
|
||||
notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void;
|
||||
|
||||
// Daemon
|
||||
notifyDaemonDidStart(port: string): void;
|
||||
notifyDaemonDidStart(port: number): void;
|
||||
notifyDaemonDidStop(): void;
|
||||
|
||||
// CLI config
|
||||
|
@@ -1,22 +1,48 @@
|
||||
import { ApplicationError } from '@theia/core/lib/common/application-error';
|
||||
import type { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
|
||||
import type { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import type { Progress } from '@theia/core/lib/common/message-service-protocol';
|
||||
import { userAbort } from '../nls';
|
||||
import type { ResponseServiceClient } from './response-service';
|
||||
|
||||
export const UserAbortApplicationError = ApplicationError.declare(
|
||||
9999,
|
||||
(message: string, uri: string) => {
|
||||
return {
|
||||
message,
|
||||
data: { uri },
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export class UserAbortError extends Error {
|
||||
constructor() {
|
||||
super(userAbort);
|
||||
Object.setPrototypeOf(this, UserAbortError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ExecuteWithProgress {
|
||||
export async function doWithProgress<T>(options: {
|
||||
run: ({ progressId }: { progressId: string }) => Promise<T>;
|
||||
run: ({
|
||||
progressId,
|
||||
cancellationToken,
|
||||
}: {
|
||||
progressId: string;
|
||||
cancellationToken?: CancellationToken;
|
||||
}) => Promise<T>;
|
||||
messageService: MessageService;
|
||||
responseService: ResponseServiceClient;
|
||||
progressText: string;
|
||||
keepOutput?: boolean;
|
||||
cancelable?: boolean;
|
||||
}): Promise<T> {
|
||||
return withProgress(
|
||||
options.progressText,
|
||||
options.messageService,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async (progress, _token) => {
|
||||
async (progress, token) => {
|
||||
const progressId = progress.id;
|
||||
const toDispose = options.responseService.onProgressDidChange(
|
||||
(progressMessage) => {
|
||||
@@ -30,24 +56,29 @@ export namespace ExecuteWithProgress {
|
||||
if (!options.keepOutput) {
|
||||
options.responseService.clearOutput();
|
||||
}
|
||||
const result = await options.run({ progressId });
|
||||
const result = await options.run({
|
||||
progressId,
|
||||
cancellationToken: token,
|
||||
});
|
||||
return result;
|
||||
} finally {
|
||||
toDispose.dispose();
|
||||
}
|
||||
}
|
||||
},
|
||||
options.cancelable
|
||||
);
|
||||
}
|
||||
|
||||
export async function withProgress<T>(
|
||||
text: string,
|
||||
messageService: MessageService,
|
||||
cb: (progress: Progress, token: CancellationToken) => Promise<T>
|
||||
cb: (progress: Progress, token: CancellationToken) => Promise<T>,
|
||||
cancelable = false
|
||||
): Promise<T> {
|
||||
const cancellationSource = new CancellationTokenSource();
|
||||
const { token } = cancellationSource;
|
||||
const progress = await messageService.showProgress(
|
||||
{ text, options: { cancelable: false } },
|
||||
{ text, options: { cancelable } },
|
||||
() => cancellationSource.cancel()
|
||||
);
|
||||
try {
|
||||
|
@@ -8,6 +8,8 @@ export namespace SketchesError {
|
||||
export const Codes = {
|
||||
NotFound: 5001,
|
||||
InvalidName: 5002,
|
||||
InvalidFolderName: 5003,
|
||||
SketchAlreadyContainsThisFile: 5004,
|
||||
};
|
||||
export const NotFound = ApplicationError.declare(
|
||||
Codes.NotFound,
|
||||
@@ -27,6 +29,30 @@ export namespace SketchesError {
|
||||
};
|
||||
}
|
||||
);
|
||||
export const InvalidFolderName = ApplicationError.declare(
|
||||
Codes.InvalidFolderName,
|
||||
(message: string, invalidFolderName: string) => {
|
||||
return {
|
||||
message,
|
||||
data: { invalidFolderName },
|
||||
};
|
||||
}
|
||||
);
|
||||
// https://github.com/arduino/arduino-ide/issues/827
|
||||
export const SketchAlreadyContainsThisFile = ApplicationError.declare(
|
||||
Codes.SketchAlreadyContainsThisFile,
|
||||
(
|
||||
message: string,
|
||||
sourceSketchName: string,
|
||||
targetSketchName: string,
|
||||
existingSketchFilename: string
|
||||
) => {
|
||||
return {
|
||||
message,
|
||||
data: { sourceSketchName, targetSketchName, existingSketchFilename },
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export const SketchesServicePath = '/services/sketches-service';
|
||||
@@ -115,13 +141,14 @@ export interface SketchesService {
|
||||
/**
|
||||
* This is the JS/TS re-implementation of [`GenBuildPath`](https://github.com/arduino/arduino-cli/blob/c0d4e4407d80aabad81142693513b3306759cfa6/arduino/sketch/sketch.go#L296-L306) of the CLI.
|
||||
* Pass in a sketch and get the build temporary folder filesystem path calculated from the main sketch file location. Can be multiple ones. This method does not check the existence of the sketch.
|
||||
* Since CLI v1.1.0 the default sketch folder is the os user cache dir. See https://github.com/arduino/arduino-cli/pull/2673/commits/d2ffeb06ca6360a211d5aa7ddd11505212ffb1b9
|
||||
*
|
||||
* The case sensitivity of the drive letter on Windows matters when the CLI calculates the MD5 hash of the temporary build folder.
|
||||
* IDE2 does not know and does not want to rely on how the CLI treats the paths: with lowercase or uppercase drive letters.
|
||||
* Hence, IDE2 has to provide multiple build paths on Windows. This hack will be obsolete when the CLI can provide error codes:
|
||||
* https://github.com/arduino/arduino-cli/issues/1762.
|
||||
*/
|
||||
tempBuildPath(sketch: SketchRef): Promise<string[]>;
|
||||
getBuildPath(sketch: SketchRef): Promise<string[]>;
|
||||
}
|
||||
|
||||
export interface SketchRef {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user