mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-10-04 00:58:31 +00:00
Compare commits
49 Commits
2.2.0
...
arduino/vs
Author | SHA1 | Date | |
---|---|---|---|
![]() |
541c65f38d | ||
![]() |
73b6dc4774 | ||
![]() |
42bf1a0e99 | ||
![]() |
5abdc18fcc | ||
![]() |
633346a3b0 | ||
![]() |
0f83a48649 | ||
![]() |
a0bd5d022f | ||
![]() |
101ba650f3 | ||
![]() |
64ce35edbb | ||
![]() |
2dae4c8258 | ||
![]() |
e7754b7c3b | ||
![]() |
3d2511194a | ||
![]() |
59a3c4faf0 | ||
![]() |
22a69f7488 | ||
![]() |
503533d712 | ||
![]() |
8687e2e044 | ||
![]() |
69b73657b6 | ||
![]() |
7e8f723df3 | ||
![]() |
d19778d0fb | ||
![]() |
e5ef564817 | ||
![]() |
ce01351bfe | ||
![]() |
a110c2b1f2 | ||
![]() |
153e34f11b | ||
![]() |
ed1cb6bcf9 | ||
![]() |
a8e63c8c90 | ||
![]() |
0b2410d49a | ||
![]() |
3f4d2745a8 | ||
![]() |
136545491d | ||
![]() |
7e1d441e6a | ||
![]() |
f0706e1849 | ||
![]() |
4708bae9ab | ||
![]() |
57975f8d91 | ||
![]() |
8f4bcc83ec | ||
![]() |
ce02e263ec | ||
![]() |
ed2d8ad13c | ||
![]() |
bb4b1450e3 | ||
![]() |
8a5dee9307 | ||
![]() |
5939b65511 | ||
![]() |
7f660d76a8 | ||
![]() |
9f48296d4d | ||
![]() |
ec28623a97 | ||
![]() |
73ddbefc3e | ||
![]() |
fe53b8e0d0 | ||
![]() |
90b886ea92 | ||
![]() |
97e26a9584 | ||
![]() |
f0704b678c | ||
![]() |
95bd2cf2e7 | ||
![]() |
8d2808893c | ||
![]() |
e5b5b2a4be |
124
.eslintrc.js
124
.eslintrc.js
@@ -1,66 +1,66 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: 'module', // Allows for the use of imports
|
||||
ecmaFeatures: {
|
||||
jsx: true, // Allows for the parsing of JSX
|
||||
},
|
||||
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: 'module', // Allows for the use of imports
|
||||
ecmaFeatures: {
|
||||
jsx: true, // Allows for the parsing of JSX
|
||||
},
|
||||
ignorePatterns: [
|
||||
'node_modules/*',
|
||||
'**/node_modules/*',
|
||||
'.node_modules/*',
|
||||
'.github/*',
|
||||
'.browser_modules/*',
|
||||
'docs/*',
|
||||
'scripts/*',
|
||||
'electron-app/lib/*',
|
||||
'electron-app/src-gen/*',
|
||||
'electron-app/gen-webpack*.js',
|
||||
'!electron-app/webpack.config.js',
|
||||
'plugins/*',
|
||||
'arduino-ide-extension/src/node/cli-protocol',
|
||||
},
|
||||
ignorePatterns: [
|
||||
'node_modules/*',
|
||||
'**/node_modules/*',
|
||||
'.github/*',
|
||||
'.browser_modules/*',
|
||||
'docs/*',
|
||||
'scripts/*',
|
||||
'electron-app/lib/*',
|
||||
'electron-app/src-gen/*',
|
||||
'electron-app/gen-webpack*.js',
|
||||
'!electron-app/webpack.config.js',
|
||||
'electron-app/plugins/*',
|
||||
'arduino-ide-extension/src/node/cli-protocol',
|
||||
'**/lib/*',
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
|
||||
},
|
||||
},
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||
'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react
|
||||
'plugin:react-hooks/recommended', // Uses recommended rules from react hooks
|
||||
'plugin:prettier/recommended',
|
||||
'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
|
||||
],
|
||||
plugins: ['prettier', 'unused-imports'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-namespace': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-empty-interface': 'warn',
|
||||
'no-unused-vars': 'off',
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
|
||||
},
|
||||
},
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||
'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react
|
||||
'plugin:react-hooks/recommended', // Uses recommended rules from react hooks
|
||||
'plugin:prettier/recommended',
|
||||
'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
|
||||
],
|
||||
plugins: ['prettier', 'unused-imports'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-namespace': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-empty-interface': 'warn',
|
||||
'no-unused-vars': 'off',
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'react/display-name': 'warn',
|
||||
eqeqeq: ['error', 'smart'],
|
||||
'guard-for-in': 'off',
|
||||
'id-blacklist': 'off',
|
||||
'id-match': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
'no-var': 'error',
|
||||
radix: 'error',
|
||||
'prettier/prettier': 'warn',
|
||||
},
|
||||
'react/display-name': 'warn',
|
||||
eqeqeq: ['error', 'smart'],
|
||||
'guard-for-in': 'off',
|
||||
'id-blacklist': 'off',
|
||||
'id-match': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
'no-var': 'error',
|
||||
radix: 'error',
|
||||
'prettier/prettier': 'warn',
|
||||
},
|
||||
};
|
||||
|
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Bug report
|
||||
description: Report a problem with the code or documentation in this repository.
|
||||
labels:
|
||||
- "type: imperfection"
|
||||
- 'type: imperfection'
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
|
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Feature request
|
||||
description: Suggest an enhancement to this project.
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
- 'type: enhancement'
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
|
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,15 +1,18 @@
|
||||
### Motivation
|
||||
|
||||
<!-- Why this pull request? -->
|
||||
|
||||
### Change description
|
||||
|
||||
<!-- What does your code do? -->
|
||||
|
||||
### Other information
|
||||
|
||||
<!-- Any additional information that could help the review process -->
|
||||
|
||||
### Reviewer checklist
|
||||
|
||||
* [ ] PR addresses a single concern.
|
||||
* [ ] The PR has no duplicates (please search among the [Pull Requests](https://github.com/arduino/arduino-ide/pulls) before creating one)
|
||||
* [ ] PR title and description are properly filled.
|
||||
* [ ] Docs have been added / updated (for bug fixes / features)
|
||||
- [ ] PR addresses a single concern.
|
||||
- [ ] The PR has no duplicates (please search among the [Pull Requests](https://github.com/arduino/arduino-ide/pulls) before creating one)
|
||||
- [ ] PR title and description are properly filled.
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
|
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -12,4 +12,4 @@ updates:
|
||||
schedule:
|
||||
interval: daily
|
||||
labels:
|
||||
- "topic: infrastructure"
|
||||
- 'topic: infrastructure'
|
||||
|
32
.github/label-configuration-files/labels.yml
vendored
32
.github/label-configuration-files/labels.yml
vendored
@@ -1,27 +1,27 @@
|
||||
# Used by the "Sync Labels" workflow
|
||||
# See: https://github.com/Financial-Times/github-label-sync#label-config-file
|
||||
|
||||
- name: "topic: accessibility"
|
||||
color: "00ffff"
|
||||
- name: 'topic: accessibility'
|
||||
color: '00ffff'
|
||||
description: Enabling the use of the software by everyone
|
||||
- name: "topic: CLI"
|
||||
color: "00ffff"
|
||||
- name: 'topic: CLI'
|
||||
color: '00ffff'
|
||||
description: Related to Arduino CLI
|
||||
- name: "topic: cloud"
|
||||
color: "00ffff"
|
||||
- name: 'topic: cloud'
|
||||
color: '00ffff'
|
||||
description: Related to Arduino Cloud and cloud sketches
|
||||
- name: "topic: debugger"
|
||||
color: "00ffff"
|
||||
- name: 'topic: debugger'
|
||||
color: '00ffff'
|
||||
description: Related to the integrated debugger
|
||||
- name: "topic: language server"
|
||||
color: "00ffff"
|
||||
- name: 'topic: language server'
|
||||
color: '00ffff'
|
||||
description: Related to the Arduino Language Server
|
||||
- name: "topic: serial monitor"
|
||||
color: "00ffff"
|
||||
- name: 'topic: serial monitor'
|
||||
color: '00ffff'
|
||||
description: Related to the Serial Monitor
|
||||
- name: "topic: theia"
|
||||
color: "00ffff"
|
||||
- name: 'topic: theia'
|
||||
color: '00ffff'
|
||||
description: Related to the Theia IDE framework
|
||||
- name: "topic: theme"
|
||||
color: "00ffff"
|
||||
- name: 'topic: theme'
|
||||
color: '00ffff'
|
||||
description: Related to GUI theming
|
||||
|
112
.github/workflows/assets/linux.Dockerfile
vendored
Normal file
112
.github/workflows/assets/linux.Dockerfile
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
# The Arduino IDE Linux build workflow job runs in this container.
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM ubuntu:18.04
|
||||
|
||||
# See: https://unofficial-builds.nodejs.org/download/release/
|
||||
ARG node_version="18.17.1"
|
||||
|
||||
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"
|
||||
|
||||
# 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.
|
||||
RUN \
|
||||
git config \
|
||||
--add \
|
||||
--global \
|
||||
"safe.directory" "/__w/arduino-ide/arduino-ide"
|
||||
ENV \
|
||||
GIT_CONFIG_GLOBAL="/root/.gitconfig"
|
||||
|
||||
# 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.
|
||||
RUN \
|
||||
apt-get \
|
||||
--yes \
|
||||
install \
|
||||
"python3.8-minimal=3.8.0-3ubuntu1~18.04.2" && \
|
||||
\
|
||||
ln \
|
||||
--symbolic \
|
||||
--force \
|
||||
"$(which python3.8)" \
|
||||
"/usr/bin/python3"
|
||||
|
||||
# Install Theia's package dependencies
|
||||
# These are pre-installed in the GitHub Actions hosted runner machines.
|
||||
RUN \
|
||||
apt-get \
|
||||
--yes \
|
||||
install \
|
||||
"libsecret-1-dev=0.18.6-1" \
|
||||
"libx11-dev=2:1.6.4-3ubuntu0.4" \
|
||||
"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"
|
||||
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"
|
438
.github/workflows/build.yml
vendored
438
.github/workflows/build.yml
vendored
@@ -12,11 +12,17 @@ on:
|
||||
- '.vscode/**'
|
||||
- 'docs/**'
|
||||
- 'scripts/**'
|
||||
- '!scripts/merge-channel-files.js'
|
||||
- 'static/**'
|
||||
- '*.md'
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
paid-runners:
|
||||
description: Include builds on non-free runners
|
||||
type: boolean
|
||||
default: false
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
@@ -24,16 +30,97 @@ on:
|
||||
- '.vscode/**'
|
||||
- 'docs/**'
|
||||
- 'scripts/**'
|
||||
- '!scripts/merge-channel-files.js'
|
||||
- 'static/**'
|
||||
- '*.md'
|
||||
schedule:
|
||||
- cron: '0 3 * * *' # run every day at 3AM (https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule)
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Push Container Images
|
||||
branches:
|
||||
- main
|
||||
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.19'
|
||||
# See: https://github.com/actions/setup-node/#readme
|
||||
NODE_VERSION: '18.17'
|
||||
JOB_TRANSFER_ARTIFACT: build-artifacts
|
||||
CHANGELOG_ARTIFACTS: changelog
|
||||
STAGED_CHANNEL_FILES_ARTIFACT: staged-channel-files
|
||||
BASE_BUILD_DATA: |
|
||||
- config:
|
||||
# Human identifier for the job.
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
# 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
|
||||
# Name of the secret that contains the certificate password.
|
||||
certificate-password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD
|
||||
# File extension for the certificate.
|
||||
certificate-extension: pfx
|
||||
# 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'
|
||||
artifacts:
|
||||
- path: '*Windows_64bit.exe'
|
||||
name: Windows_X86-64_interactive_installer
|
||||
- path: '*Windows_64bit.msi'
|
||||
name: Windows_X86-64_MSI
|
||||
- path: '*Windows_64bit.zip'
|
||||
name: Windows_X86-64_zip
|
||||
- config:
|
||||
name: Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: |
|
||||
{
|
||||
\"image\": \"ghcr.io/arduino/arduino-ide/linux:main\"
|
||||
}
|
||||
mergeable-channel-file: 'false'
|
||||
artifacts:
|
||||
- path: '*Linux_64bit.zip'
|
||||
name: Linux_X86-64_zip
|
||||
- path: '*Linux_64bit.AppImage'
|
||||
name: Linux_X86-64_app_image
|
||||
- config:
|
||||
name: macOS x86
|
||||
runs-on: macos-latest
|
||||
container: |
|
||||
null
|
||||
# APPLE_SIGNING_CERTIFICATE_P12 secret was produced by following the procedure from:
|
||||
# https://www.kencochrane.com/2020/08/01/build-and-sign-golang-binaries-for-macos-with-github-actions/#exporting-the-developer-certificate
|
||||
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12
|
||||
certificate-password-secret: KEYCHAIN_PASSWORD
|
||||
certificate-extension: p12
|
||||
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
|
||||
container: |
|
||||
null
|
||||
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12
|
||||
certificate-password-secret: KEYCHAIN_PASSWORD
|
||||
certificate-extension: p12
|
||||
mergeable-channel-file: 'true'
|
||||
artifacts:
|
||||
- path: '*macOS_arm64.dmg'
|
||||
name: macOS_arm64_dmg
|
||||
- path: '*macOS_arm64.zip'
|
||||
name: macOS_arm64_zip
|
||||
|
||||
jobs:
|
||||
run-determination:
|
||||
@@ -60,44 +147,174 @@ jobs:
|
||||
|
||||
echo "result=$RESULT" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
name: build (${{ matrix.config.os }})
|
||||
build-type-determination:
|
||||
needs: run-determination
|
||||
if: needs.run-determination.outputs.result == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is-release: ${{ steps.determination.outputs.is-release }}
|
||||
is-nightly: ${{ steps.determination.outputs.is-nightly }}
|
||||
channel-name: ${{ steps.determination.outputs.channel-name }}
|
||||
publish-to-s3: ${{ steps.determination.outputs.publish-to-s3 }}
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Determine the type of build
|
||||
id: determination
|
||||
run: |
|
||||
if [[
|
||||
"${{ startsWith(github.ref, 'refs/tags/') }}" == "true"
|
||||
]]; then
|
||||
is_release="true"
|
||||
is_nightly="false"
|
||||
channel_name="stable"
|
||||
elif [[
|
||||
"${{ github.event_name }}" == "schedule" ||
|
||||
(
|
||||
"${{ github.event_name }}" == "workflow_dispatch" &&
|
||||
"${{ github.ref }}" == "refs/heads/main"
|
||||
)
|
||||
]]; then
|
||||
is_release="false"
|
||||
is_nightly="true"
|
||||
channel_name="nightly"
|
||||
else
|
||||
is_release="false"
|
||||
is_nightly="false"
|
||||
channel_name="nightly"
|
||||
fi
|
||||
|
||||
echo "is-release=$is_release" >> $GITHUB_OUTPUT
|
||||
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
|
||||
|
||||
select-targets:
|
||||
needs: build-type-determination
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
artifact-matrix: ${{ steps.assemble.outputs.artifact-matrix }}
|
||||
build-matrix: ${{ steps.assemble.outputs.build-matrix }}
|
||||
merge-channel-files: ${{ steps.assemble.outputs.merge-channel-files }}
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Assemble target data
|
||||
id: assemble
|
||||
run: |
|
||||
# Only run the builds that incur runner charges on release or select manually triggered runs.
|
||||
if [[
|
||||
"${{ needs.build-type-determination.outputs.is-release }}" == "true" ||
|
||||
"${{ github.event.inputs.paid-runners }}" == "true"
|
||||
]]; then
|
||||
build_matrix="$(
|
||||
(
|
||||
echo "${{ env.BASE_BUILD_DATA }}";
|
||||
echo "${{ env.PAID_RUNNER_BUILD_DATA }}"
|
||||
) | \
|
||||
yq \
|
||||
--output-format json \
|
||||
'[.[].config]'
|
||||
)"
|
||||
|
||||
artifact_matrix="$(
|
||||
(
|
||||
echo "${{ env.BASE_BUILD_DATA }}";
|
||||
echo "${{ env.PAID_RUNNER_BUILD_DATA }}"
|
||||
) | \
|
||||
yq \
|
||||
--output-format json \
|
||||
'[.[].artifacts.[]]'
|
||||
)"
|
||||
|
||||
# The build matrix produces two macOS jobs (x86 and ARM) so the "channel update info files"
|
||||
# generated by each must be merged.
|
||||
merge_channel_files="true"
|
||||
|
||||
else
|
||||
build_matrix="$(
|
||||
echo "${{ env.BASE_BUILD_DATA }}" | \
|
||||
yq \
|
||||
--output-format json \
|
||||
'[.[].config]'
|
||||
)"
|
||||
|
||||
artifact_matrix="$(
|
||||
echo "${{ env.BASE_BUILD_DATA }}" | \
|
||||
yq \
|
||||
--output-format json \
|
||||
'[.[].artifacts.[]]'
|
||||
)"
|
||||
|
||||
merge_channel_files="false"
|
||||
fi
|
||||
|
||||
# Set workflow step outputs.
|
||||
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
delimiter="$RANDOM"
|
||||
echo "build-matrix<<$delimiter" >> $GITHUB_OUTPUT
|
||||
echo "$build_matrix" >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
|
||||
delimiter="$RANDOM"
|
||||
echo "artifact-matrix<<$delimiter" >> $GITHUB_OUTPUT
|
||||
echo "$artifact_matrix" >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "merge-channel-files=$merge_channel_files" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
name: build (${{ matrix.config.name }})
|
||||
needs:
|
||||
- build-type-determination
|
||||
- select-targets
|
||||
env:
|
||||
# Location of artifacts generated by build.
|
||||
BUILD_ARTIFACTS_PATH: electron-app/dist/build-artifacts
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- os: windows-2019
|
||||
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX # Name of the secret that contains the certificate.
|
||||
certificate-password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD # Name of the secret that contains the certificate password.
|
||||
certificate-extension: pfx # File extension for the certificate.
|
||||
- os: ubuntu-20.04
|
||||
- os: macos-latest
|
||||
# APPLE_SIGNING_CERTIFICATE_P12 secret was produced by following the procedure from:
|
||||
# https://www.kencochrane.com/2020/08/01/build-and-sign-golang-binaries-for-macos-with-github-actions/#exporting-the-developer-certificate
|
||||
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12
|
||||
certificate-password-secret: KEYCHAIN_PASSWORD
|
||||
certificate-extension: p12
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
config: ${{ fromJson(needs.select-targets.outputs.build-matrix) }}
|
||||
runs-on: ${{ matrix.config.runs-on }}
|
||||
container: ${{ fromJSON(matrix.config.container) }}
|
||||
defaults:
|
||||
run:
|
||||
# Avoid problems caused by different default shell for container jobs (sh) vs non-container jobs (bash).
|
||||
shell: bash
|
||||
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- 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 16.14
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js
|
||||
if: fromJSON(matrix.config.container) == null
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16.14'
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install Python 3.x
|
||||
uses: actions/setup-python@v4
|
||||
if: fromJSON(matrix.config.container) == null
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
python-version: '3.11.x'
|
||||
|
||||
- name: Install Go
|
||||
if: fromJSON(matrix.config.container) == null
|
||||
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 }}
|
||||
@@ -109,7 +326,6 @@ jobs:
|
||||
version: 3.x
|
||||
|
||||
- name: Package
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
AC_USERNAME: ${{ secrets.AC_USERNAME }}
|
||||
@@ -117,9 +333,14 @@ jobs:
|
||||
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: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }}
|
||||
IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
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 }}
|
||||
run: |
|
||||
# See: https://www.electron.build/code-signing
|
||||
if [ $CAN_SIGN = false ]; then
|
||||
@@ -131,10 +352,6 @@ jobs:
|
||||
export CSC_FOR_PULL_REQUEST=true
|
||||
fi
|
||||
|
||||
if [ "${{ runner.OS }}" = "Windows" ]; then
|
||||
npm config set msvs_version 2017 --global
|
||||
fi
|
||||
|
||||
npx node-gyp install
|
||||
yarn install --immutable
|
||||
|
||||
@@ -147,35 +364,113 @@ jobs:
|
||||
yarn --cwd electron-app build
|
||||
yarn --cwd electron-app package
|
||||
|
||||
# Both macOS jobs generate a "channel update info file" with same path and name. The second job to complete would
|
||||
# overwrite the file generated by the first in the workflow artifact.
|
||||
- name: Stage channel file for merge
|
||||
if: >
|
||||
needs.select-targets.outputs.merge-channel-files == 'true' &&
|
||||
matrix.config.mergeable-channel-file == 'true'
|
||||
run: |
|
||||
staged_channel_files_path="${{ runner.temp }}/staged-channel-files"
|
||||
mkdir "$staged_channel_files_path"
|
||||
mv \
|
||||
"${{ env.BUILD_ARTIFACTS_PATH }}/${{ needs.build-type-determination.outputs.channel-name }}-mac.yml" \
|
||||
"${staged_channel_files_path}/${{ needs.build-type-determination.outputs.channel-name }}-mac-${{ runner.arch }}.yml"
|
||||
|
||||
# Set workflow environment variable for use in other steps.
|
||||
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
|
||||
echo "STAGED_CHANNEL_FILES_PATH=$staged_channel_files_path" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Upload staged-for-merge channel file artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
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: Upload [GitHub Actions]
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
path: electron-app/dist/build-artifacts
|
||||
path: ${{ env.BUILD_ARTIFACTS_PATH }}
|
||||
|
||||
merge-channel-files:
|
||||
needs:
|
||||
- build-type-determination
|
||||
- select-targets
|
||||
- build
|
||||
if: needs.select-targets.outputs.merge-channel-files == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
|
||||
echo "CHANNEL_FILES_PATH=${{ runner.temp }}/channel-files" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download staged-for-merge channel files artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ env.STAGED_CHANNEL_FILES_ARTIFACT }}
|
||||
path: ${{ env.CHANNEL_FILES_PATH }}
|
||||
|
||||
- name: Remove no longer needed artifact
|
||||
uses: geekyeggo/delete-artifact@v2
|
||||
with:
|
||||
name: ${{ env.STAGED_CHANNEL_FILES_ARTIFACT }}
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Merge "channel update info files"
|
||||
run: |
|
||||
node \
|
||||
./scripts/merge-channel-files.js \
|
||||
--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
|
||||
with:
|
||||
if-no-files-found: error
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
path: ${{ env.CHANNEL_FILES_PATH }}
|
||||
|
||||
artifacts:
|
||||
name: ${{ matrix.artifact.name }} artifact
|
||||
needs: build
|
||||
needs:
|
||||
- select-targets
|
||||
- build
|
||||
if: always() && needs.build.result != 'skipped'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
artifact:
|
||||
- path: '*Linux_64bit.zip'
|
||||
name: Linux_X86-64_zip
|
||||
- path: '*Linux_64bit.AppImage'
|
||||
name: Linux_X86-64_app_image
|
||||
- path: '*macOS_64bit.dmg'
|
||||
name: macOS_dmg
|
||||
- path: '*macOS_64bit.zip'
|
||||
name: macOS_zip
|
||||
- path: '*Windows_64bit.exe'
|
||||
name: Windows_X86-64_interactive_installer
|
||||
- path: '*Windows_64bit.msi'
|
||||
name: Windows_X86-64_MSI
|
||||
- path: '*Windows_64bit.zip'
|
||||
name: Windows_X86-64_zip
|
||||
artifact: ${{ fromJson(needs.select-targets.outputs.artifact-matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Download job transfer artifact
|
||||
@@ -191,20 +486,22 @@ jobs:
|
||||
path: ${{ env.JOB_TRANSFER_ARTIFACT }}/${{ matrix.artifact.path }}
|
||||
|
||||
changelog:
|
||||
needs: build
|
||||
needs:
|
||||
- build-type-determination
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
BODY: ${{ steps.changelog.outputs.BODY }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # To fetch all history for all branches and tags.
|
||||
|
||||
- name: Generate Changelog
|
||||
id: changelog
|
||||
env:
|
||||
IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
IS_RELEASE: ${{ needs.build-type-determination.outputs.is-release }}
|
||||
run: |
|
||||
export LATEST_TAG=$(git describe --abbrev=0)
|
||||
export GIT_LOG=$(git log --pretty=" - %s [%h]" $LATEST_TAG..HEAD | sed 's/ *$//g')
|
||||
@@ -230,15 +527,27 @@ jobs:
|
||||
echo "$BODY" > CHANGELOG.txt
|
||||
|
||||
- name: Upload Changelog [GitHub Actions]
|
||||
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
|
||||
if: needs.build-type-determination.outputs.is-nightly == 'true'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
|
||||
path: CHANGELOG.txt
|
||||
|
||||
publish:
|
||||
needs: changelog
|
||||
if: github.repository == 'arduino/arduino-ide' && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'))
|
||||
needs:
|
||||
- build-type-determination
|
||||
- merge-channel-files
|
||||
- changelog
|
||||
if: >
|
||||
always() &&
|
||||
needs.build-type-determination.result == 'success' &&
|
||||
(
|
||||
needs.merge-channel-files.result == 'skipped' ||
|
||||
needs.merge-channel-files.result == 'success'
|
||||
) &&
|
||||
needs.changelog.result == 'success' &&
|
||||
needs.build-type-determination.outputs.publish-to-s3 == 'true' &&
|
||||
needs.build-type-determination.outputs.is-nightly == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download [GitHub Actions]
|
||||
@@ -258,8 +567,19 @@ jobs:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
release:
|
||||
needs: changelog
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
needs:
|
||||
- build-type-determination
|
||||
- merge-channel-files
|
||||
- changelog
|
||||
if: >
|
||||
always() &&
|
||||
needs.build-type-determination.result == 'success' &&
|
||||
(
|
||||
needs.merge-channel-files.result == 'skipped' ||
|
||||
needs.merge-channel-files.result == 'success'
|
||||
) &&
|
||||
needs.changelog.result == 'success' &&
|
||||
needs.build-type-determination.outputs.is-release == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download [GitHub Actions]
|
||||
@@ -283,17 +603,8 @@ jobs:
|
||||
file_glob: true
|
||||
body: ${{ needs.changelog.outputs.BODY }}
|
||||
|
||||
# Temporary measure to prevent release update offers before the manually produced builds are uploaded.
|
||||
# The step must be removed once fully automated builds are regained.
|
||||
- name: Remove "channel update info files" related to manual builds
|
||||
run: |
|
||||
# See: https://github.com/arduino/arduino-ide/issues/2018
|
||||
rm "${{ env.JOB_TRANSFER_ARTIFACT }}/stable-linux.yml"
|
||||
# See: https://github.com/arduino/arduino-ide/issues/408
|
||||
rm "${{ env.JOB_TRANSFER_ARTIFACT }}/stable-mac.yml"
|
||||
|
||||
- name: Publish Release [S3]
|
||||
if: github.repository == 'arduino/arduino-ide'
|
||||
if: needs.build-type-determination.outputs.publish-to-s3 == 'true'
|
||||
uses: docker://plugins/s3
|
||||
env:
|
||||
PLUGIN_SOURCE: '${{ env.JOB_TRANSFER_ARTIFACT }}/*'
|
||||
@@ -307,6 +618,7 @@ jobs:
|
||||
# This job must run after all jobs that use the transfer artifact.
|
||||
needs:
|
||||
- build
|
||||
- merge-channel-files
|
||||
- publish
|
||||
- release
|
||||
- artifacts
|
||||
|
58
.github/workflows/check-containers.yml
vendored
Normal file
58
.github/workflows/check-containers.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Check Containers
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/check-containers.ya?ml"
|
||||
- "**.Dockerfile"
|
||||
- "**/Dockerfile"
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/check-containers.ya?ml"
|
||||
- "**.Dockerfile"
|
||||
- "**/Dockerfile"
|
||||
repository_dispatch:
|
||||
schedule:
|
||||
# Run periodically to catch breakage caused by external changes.
|
||||
- cron: "0 7 * * MON"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
run:
|
||||
name: Run (${{ matrix.image.path }})
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
ports:
|
||||
- 5000:5000
|
||||
|
||||
env:
|
||||
IMAGE_NAME: name/app:latest
|
||||
REGISTRY: localhost:5000
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image:
|
||||
- path: .github/workflows/assets/linux.Dockerfile
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and push to local registry
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.image.path }}
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Run container
|
||||
run: |
|
||||
docker \
|
||||
run \
|
||||
--rm \
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
12
.github/workflows/check-i18n-task.yml
vendored
12
.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.19'
|
||||
|
||||
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
|
||||
on:
|
||||
@@ -56,17 +56,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js 16.14
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js 18.17
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16.14'
|
||||
node-version: '18.17'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
|
8
.github/workflows/compose-full-changelog.yml
vendored
8
.github/workflows/compose-full-changelog.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
env:
|
||||
CHANGELOG_ARTIFACTS: changelog
|
||||
# See: https://github.com/actions/setup-node/#readme
|
||||
NODE_VERSION: 16.x
|
||||
NODE_VERSION: '18.17'
|
||||
|
||||
jobs:
|
||||
create-changelog:
|
||||
@@ -16,10 +16,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Create full changelog
|
||||
id: full-changelog
|
||||
run: |
|
||||
yarn add @octokit/rest --ignore-workspace-root-check
|
||||
yarn add @octokit/rest@19.0.13 --ignore-workspace-root-check
|
||||
mkdir "${{ github.workspace }}/${{ env.CHANGELOG_ARTIFACTS }}"
|
||||
|
||||
# Get the changelog file name to build
|
||||
|
12
.github/workflows/i18n-nightly-push.yml
vendored
12
.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.19'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -14,17 +14,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js 16.14
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js 18.17
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16.14'
|
||||
node-version: '18.17'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
|
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.19'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -14,17 +14,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js 16.14
|
||||
uses: actions/setup-node@v3
|
||||
- name: Install Node.js 18.17
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16.14'
|
||||
node-version: '18.17'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
|
70
.github/workflows/push-container-images.yml
vendored
Normal file
70
.github/workflows/push-container-images.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Push Container Images
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/push-container-images.ya?ml"
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/push-container-images.ya?ml"
|
||||
- "**.Dockerfile"
|
||||
- "**/Dockerfile"
|
||||
repository_dispatch:
|
||||
schedule:
|
||||
# Run periodically to catch breakage caused by external changes.
|
||||
- cron: "0 8 * * MON"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
push:
|
||||
name: Push (${{ matrix.image.name }})
|
||||
# Only run the job when GITHUB_TOKEN has the privileges required for Container registry login.
|
||||
if: >
|
||||
(
|
||||
github.event_name != 'pull_request' &&
|
||||
github.repository == 'arduino/arduino-ide'
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name == 'arduino/arduino-ide'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image:
|
||||
- path: .github/workflows/assets/linux.Dockerfile
|
||||
name: ${{ github.repository }}/linux
|
||||
registry: ghcr.io
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
registry: ${{ matrix.image.registry }}
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
- name: Extract metadata for image
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ matrix.image.registry }}/${{ matrix.image.name }}
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.image.path }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
# Workflow is triggered on relevant events for the sake of a "dry run" validation but image is only pushed to
|
||||
# registry on commit to the main branch.
|
||||
push: ${{ github.ref == 'refs/heads/main' }}
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
14
.github/workflows/sync-labels.yml
vendored
14
.github/workflows/sync-labels.yml
vendored
@@ -5,15 +5,15 @@ name: Sync Labels
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/sync-labels.ya?ml"
|
||||
- ".github/label-configuration-files/*.ya?ml"
|
||||
- '.github/workflows/sync-labels.ya?ml'
|
||||
- '.github/label-configuration-files/*.ya?ml'
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/sync-labels.ya?ml"
|
||||
- ".github/label-configuration-files/*.ya?ml"
|
||||
- '.github/workflows/sync-labels.ya?ml'
|
||||
- '.github/label-configuration-files/*.ya?ml'
|
||||
schedule:
|
||||
# Run daily at 8 AM UTC to sync with changes to shared label configurations.
|
||||
- cron: "0 8 * * *"
|
||||
- cron: '0 8 * * *'
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download JSON schema for labels configuration file
|
||||
id: download-schema
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
echo "flag=--dry-run" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download configuration files artifact
|
||||
uses: actions/download-artifact@v3
|
||||
|
10
.github/workflows/themes-weekly-pull.yml
vendored
10
.github/workflows/themes-weekly-pull.yml
vendored
@@ -8,25 +8,25 @@ on:
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: "1.19"
|
||||
NODE_VERSION: 16.x
|
||||
GO_VERSION: '1.19'
|
||||
NODE_VERSION: '18.17'
|
||||
|
||||
jobs:
|
||||
pull-from-jsonbin:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,7 +1,7 @@
|
||||
node_modules/
|
||||
lib/
|
||||
downloads/
|
||||
resources/
|
||||
arduino-ide-extension/src/node/resources
|
||||
arduino-ide-extension/Examples/
|
||||
src-gen/
|
||||
gen-webpack.config.js
|
||||
|
11
.prettierignore
Normal file
11
.prettierignore
Normal file
@@ -0,0 +1,11 @@
|
||||
lib
|
||||
dist
|
||||
plugins
|
||||
src-gen
|
||||
i18n
|
||||
gen-webpack*
|
||||
.browser_modules
|
||||
arduino-ide-extension/src/node/resources
|
||||
cli-protocol
|
||||
*color-theme.json
|
||||
arduino-icons.json
|
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"printWidth": 80,
|
||||
"endOfLine": "auto"
|
||||
}
|
28
.prettierrc.json
Normal file
28
.prettierrc.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"printWidth": 80,
|
||||
"endOfLine": "auto",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.json",
|
||||
"options": {
|
||||
"tabWidth": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.css",
|
||||
"options": {
|
||||
"tabWidth": 4,
|
||||
"singleQuote": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.html",
|
||||
"options": {
|
||||
"tabWidth": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -7,7 +7,7 @@
|
||||
"name": "App",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||
},
|
||||
"cwd": "${workspaceFolder}/electron-app",
|
||||
"args": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"--no-app-auto-install",
|
||||
"--plugins=local-dir:./plugins",
|
||||
"--hosted-plugin-inspect=9339",
|
||||
"--no-ping-timeout",
|
||||
"--no-ping-timeout"
|
||||
],
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
@@ -42,7 +42,7 @@
|
||||
"name": "App [Dev]",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||
},
|
||||
"cwd": "${workspaceFolder}/electron-app",
|
||||
"args": [
|
||||
@@ -56,7 +56,7 @@
|
||||
"--hosted-plugin-inspect=9339",
|
||||
"--content-trace",
|
||||
"--open-devtools",
|
||||
"--no-ping-timeout",
|
||||
"--no-ping-timeout"
|
||||
],
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
@@ -115,15 +115,12 @@
|
||||
"request": "attach",
|
||||
"name": "Attach by Process ID",
|
||||
"processId": "${command:PickProcess}"
|
||||
},
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Launch Electron Backend & Frontend",
|
||||
"configurations": [
|
||||
"App",
|
||||
"Attach to Electron Frontend"
|
||||
]
|
||||
"configurations": ["App", "Attach to Electron Frontend"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -8,5 +8,5 @@
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
10
.vscode/tasks.json
vendored
10
.vscode/tasks.json
vendored
@@ -4,8 +4,11 @@
|
||||
{
|
||||
"label": "Rebuild App",
|
||||
"type": "shell",
|
||||
"command": "yarn rebuild:browser && yarn rebuild:electron",
|
||||
"command": "yarn rebuild",
|
||||
"group": "build",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/electron-app"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new",
|
||||
@@ -37,10 +40,7 @@
|
||||
{
|
||||
"label": "Watch All",
|
||||
"type": "shell",
|
||||
"dependsOn": [
|
||||
"Watch Extension",
|
||||
"Watch App"
|
||||
]
|
||||
"dependsOn": ["Watch Extension", "Watch App"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
[](https://github.com/arduino/arduino-ide/actions?query=workflow%3A%22Arduino+IDE%22)
|
||||
|
||||
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 at https://github.com/arduino/Arduino.
|
||||
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).
|
||||
|
||||
The Arduino IDE 2.x is a major rewrite, sharing no code with the IDE 1.x. It is based on the [Theia IDE](https://theia-ide.org/) framework and built with [Electron](https://www.electronjs.org/). The backend operations such as compilation and uploading are offloaded to an [arduino-cli](https://github.com/arduino/arduino-cli) instance running in daemon mode. This new IDE was developed with the goal of preserving the same interface and user experience of the previous major version in order to provide a frictionless upgrade.
|
||||
|
||||
|
@@ -55,12 +55,14 @@ The Config Service knows about your system, like for example the default sketch
|
||||
- checking whether a file is in a data or sketch directory
|
||||
|
||||
### `"arduino"` configuration in the `package.json`:
|
||||
- `"cli"`:
|
||||
- `"version"` type `string` | `{ owner: string, repo: string, commitish?: string }`: if the type is a `string` and is a valid semver, it will get the corresponding [released](https://github.com/arduino/arduino-cli/releases) CLI. If the type is `string` and is a [date in `YYYYMMDD`](https://arduino.github.io/arduino-cli/latest/installation/#nightly-builds) format, it will get a nightly CLI. If the type is an object, a CLI, build from the sources in the `owner/repo` will be used. If `commitish` is not defined, the HEAD of the default branch will be used. In any other cases an error is thrown.
|
||||
|
||||
- `"cli"`:
|
||||
- `"version"` type `string` | `{ owner: string, repo: string, commitish?: string }`: if the type is a `string` and is a valid semver, it will get the corresponding [released](https://github.com/arduino/arduino-cli/releases) CLI. If the type is `string` and is a [date in `YYYYMMDD`](https://arduino.github.io/arduino-cli/latest/installation/#nightly-builds) format, it will get a nightly CLI. If the type is an object, a CLI, build from the sources in the `owner/repo` will be used. If `commitish` is not defined, the HEAD of the default branch will be used. In any other cases an error is thrown.
|
||||
|
||||
#### Rebuild gRPC protocol interfaces
|
||||
- Some CLI updates can bring changes to the gRPC interfaces, as the API might change. gRPC interfaces can be updated running the command
|
||||
`yarn --cwd arduino-ide-extension generate-protocol`
|
||||
|
||||
- Some CLI updates can bring changes to the gRPC interfaces, as the API might change. gRPC interfaces can be updated running the command
|
||||
`yarn --cwd arduino-ide-extension generate-protocol`
|
||||
|
||||
### Update **clangd** and **ClangFormat**
|
||||
|
||||
@@ -72,11 +74,13 @@ The [**clangd** C++ language server](https://clangd.llvm.org/) and the [**ClangF
|
||||
1. Submit a pull request in [the `arduino/tooling-project-assets` repository](https://github.com/arduino/tooling-project-assets) to update the version in the `vars.DEFAULT_CLANG_FORMAT_VERSION` field of [`Taskfile.yml`](https://github.com/arduino/tooling-project-assets/blob/main/Taskfile.yml).
|
||||
|
||||
### Customize Icons
|
||||
|
||||
ArduinoIde uses a customized version of FontAwesome.
|
||||
In order to update/replace icons follow the following steps:
|
||||
- import the file `arduino-icons.json` in [Icomoon](https://icomoon.io/app/#/projects)
|
||||
- load it
|
||||
- edit the icons as needed
|
||||
- !! download the **new** `arduino-icons.json` file and put it in this repo
|
||||
- Click on "Generate Font" in Icomoon, then download
|
||||
- place the updated fonts in the `src/style/fonts` directory
|
||||
|
||||
- import the file `arduino-icons.json` in [Icomoon](https://icomoon.io/app/#/projects)
|
||||
- load it
|
||||
- edit the icons as needed
|
||||
- !! download the **new** `arduino-icons.json` file and put it in this repo
|
||||
- Click on "Generate Font" in Icomoon, then download
|
||||
- place the updated fonts in the `src/style/fonts` directory
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "arduino-ide-extension",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.2",
|
||||
"description": "An extension for Theia building the Arduino IDE",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
@@ -24,29 +24,29 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.8.14",
|
||||
"@theia/application-package": "1.39.0",
|
||||
"@theia/core": "1.39.0",
|
||||
"@theia/debug": "1.39.0",
|
||||
"@theia/editor": "1.39.0",
|
||||
"@theia/electron": "1.39.0",
|
||||
"@theia/filesystem": "1.39.0",
|
||||
"@theia/keymaps": "1.39.0",
|
||||
"@theia/markers": "1.39.0",
|
||||
"@theia/messages": "1.39.0",
|
||||
"@theia/monaco": "1.39.0",
|
||||
"@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.39.0",
|
||||
"@theia/outline-view": "1.39.0",
|
||||
"@theia/output": "1.39.0",
|
||||
"@theia/plugin-ext": "1.39.0",
|
||||
"@theia/preferences": "1.39.0",
|
||||
"@theia/scm": "1.39.0",
|
||||
"@theia/search-in-workspace": "1.39.0",
|
||||
"@theia/terminal": "1.39.0",
|
||||
"@theia/typehierarchy": "1.39.0",
|
||||
"@theia/workspace": "1.39.0",
|
||||
"@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",
|
||||
"@tippyjs/react": "^4.2.5",
|
||||
"@types/auth0-js": "^9.14.0",
|
||||
"@types/auth0-js": "^9.21.3",
|
||||
"@types/btoa": "^1.2.3",
|
||||
"@types/dateformat": "^3.0.1",
|
||||
"@types/google-protobuf": "^3.7.2",
|
||||
@@ -60,7 +60,7 @@
|
||||
"@types/temp": "^0.8.34",
|
||||
"arduino-serial-plotter-webapp": "0.2.0",
|
||||
"async-mutex": "^0.3.0",
|
||||
"auth0-js": "^9.14.0",
|
||||
"auth0-js": "^9.23.2",
|
||||
"btoa": "^1.2.1",
|
||||
"classnames": "^2.3.1",
|
||||
"cpy": "^10.0.0",
|
||||
@@ -69,6 +69,7 @@
|
||||
"deepmerge": "^4.2.2",
|
||||
"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",
|
||||
@@ -109,7 +110,7 @@
|
||||
"devDependencies": {
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"@types/chai": "^4.2.7",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@xhmikosr/downloader": "^13.0.1",
|
||||
"chai": "^4.2.0",
|
||||
@@ -118,18 +119,15 @@
|
||||
"decompress-tarbz2": "^4.1.1",
|
||||
"decompress-targz": "^4.1.1",
|
||||
"decompress-unzip": "^4.0.1",
|
||||
"grpc_tools_node_protoc_ts": "^4.1.0",
|
||||
"mocha": "^7.0.0",
|
||||
"grpc_tools_node_protoc_ts": "^5.3.3",
|
||||
"mocha": "^10.2.0",
|
||||
"mockdate": "^3.0.5",
|
||||
"moment": "^2.24.0",
|
||||
"ncp": "^2.0.0",
|
||||
"rimraf": "^2.6.1",
|
||||
"shelljs": "^0.8.3",
|
||||
"uuid": "^3.2.1",
|
||||
"yargs": "^11.1.0"
|
||||
"rimraf": "^2.6.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"grpc-tools": "^1.9.0",
|
||||
"grpc-tools": "^1.12.4",
|
||||
"protoc": "^1.0.4"
|
||||
},
|
||||
"mocha": {
|
||||
@@ -172,13 +170,13 @@
|
||||
],
|
||||
"arduino": {
|
||||
"arduino-cli": {
|
||||
"version": "0.34.0"
|
||||
"version": "0.35.0-rc.7"
|
||||
},
|
||||
"arduino-fwuploader": {
|
||||
"version": "2.4.0"
|
||||
"version": "2.4.1"
|
||||
},
|
||||
"arduino-language-server": {
|
||||
"version": "0.7.4"
|
||||
"version": "0.7.5"
|
||||
},
|
||||
"clangd": {
|
||||
"version": "14.0.0"
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
(async () => {
|
||||
const path = require('path');
|
||||
const shell = require('shelljs');
|
||||
const semver = require('semver');
|
||||
const moment = require('moment');
|
||||
const downloader = require('./downloader');
|
||||
@@ -29,8 +28,8 @@
|
||||
})();
|
||||
|
||||
if (!version) {
|
||||
shell.echo(`Could not retrieve CLI version info from the 'package.json'.`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not retrieve CLI version info from the 'package.json'.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { platform, arch } = process;
|
||||
@@ -71,24 +70,24 @@
|
||||
}
|
||||
})();
|
||||
if (!suffix) {
|
||||
shell.echo(`The CLI is not available for ${platform} ${arch}.`);
|
||||
shell.exit(1);
|
||||
console.log(`The CLI is not available for ${platform} ${arch}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (semver.valid(version)) {
|
||||
const url = `https://downloads.arduino.cc/arduino-cli/arduino-cli_${version}_${suffix}`;
|
||||
shell.echo(
|
||||
console.log(
|
||||
`📦 Identified released version of the CLI. Downloading version ${version} from '${url}'`
|
||||
);
|
||||
await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli');
|
||||
} else if (moment(version, 'YYYYMMDD', true).isValid()) {
|
||||
const url = `https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-${version}_${suffix}`;
|
||||
shell.echo(
|
||||
console.log(
|
||||
`🌙 Identified nightly version of the CLI. Downloading version ${version} from '${url}'`
|
||||
);
|
||||
await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli');
|
||||
} else {
|
||||
shell.echo(`🔥 Could not interpret 'version': ${version}`);
|
||||
shell.exit(1);
|
||||
console.log(`🔥 Could not interpret 'version': ${version}`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
taskBuildFromGit(version, destinationPath, 'CLI');
|
||||
|
@@ -5,10 +5,14 @@ const version = '1.10.0';
|
||||
|
||||
(async () => {
|
||||
const os = require('node:os');
|
||||
const { existsSync, promises: fs } = require('node:fs');
|
||||
const {
|
||||
existsSync,
|
||||
promises: fs,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
cpSync,
|
||||
} = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const shell = require('shelljs');
|
||||
const { v4 } = require('uuid');
|
||||
const { exec } = require('./utils');
|
||||
|
||||
const destination = path.join(
|
||||
@@ -20,31 +24,38 @@ const version = '1.10.0';
|
||||
'Examples'
|
||||
);
|
||||
if (existsSync(destination)) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`Skipping Git checkout of the examples because the repository already exists: ${destination}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`);
|
||||
if (shell.mkdir('-p', repository).code !== 0) {
|
||||
shell.exit(1);
|
||||
}
|
||||
const repository = await fs.mkdtemp(
|
||||
path.join(os.tmpdir(), 'arduino-examples-')
|
||||
);
|
||||
|
||||
exec(
|
||||
'git',
|
||||
['clone', 'https://github.com/arduino/arduino-examples.git', repository],
|
||||
shell
|
||||
{ logStdout: true }
|
||||
);
|
||||
|
||||
exec(
|
||||
'git',
|
||||
['-C', repository, 'checkout', `tags/${version}`, '-b', version],
|
||||
shell
|
||||
{ logStdout: true }
|
||||
);
|
||||
|
||||
shell.mkdir('-p', destination);
|
||||
shell.cp('-fR', path.join(repository, 'examples', '*'), destination);
|
||||
mkdirSync(destination, { recursive: true });
|
||||
const examplesPath = path.join(repository, 'examples');
|
||||
const exampleResources = readdirSync(examplesPath);
|
||||
for (const exampleResource of exampleResources) {
|
||||
cpSync(
|
||||
path.join(examplesPath, exampleResource),
|
||||
path.join(destination, exampleResource),
|
||||
{ recursive: true }
|
||||
);
|
||||
}
|
||||
|
||||
const isSketch = async (pathLike) => {
|
||||
try {
|
||||
@@ -104,5 +115,5 @@ const version = '1.10.0';
|
||||
JSON.stringify(examples, null, 2),
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
shell.echo(`Generated output to ${path.join(destination, 'examples.json')}`);
|
||||
console.log(`Generated output to ${path.join(destination, 'examples.json')}`);
|
||||
})();
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
(async () => {
|
||||
const path = require('node:path');
|
||||
const shell = require('shelljs');
|
||||
const semver = require('semver');
|
||||
const downloader = require('./downloader');
|
||||
const { taskBuildFromGit } = require('./utils');
|
||||
@@ -28,10 +27,10 @@
|
||||
})();
|
||||
|
||||
if (!version) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`Could not retrieve Firmware Uploader version info from the 'package.json'.`
|
||||
);
|
||||
shell.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { platform, arch } = process;
|
||||
@@ -71,14 +70,14 @@
|
||||
}
|
||||
})();
|
||||
if (!suffix) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`The Firmware Uploader is not available for ${platform} ${arch}.`
|
||||
);
|
||||
shell.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
if (semver.valid(version)) {
|
||||
const url = `https://downloads.arduino.cc/arduino-fwuploader/arduino-fwuploader_${version}_${suffix}`;
|
||||
shell.echo(
|
||||
console.log(
|
||||
`📦 Identified released version of the Firmware Uploader. Downloading version ${version} from '${url}'`
|
||||
);
|
||||
await downloader.downloadUnzipFile(
|
||||
@@ -87,8 +86,8 @@
|
||||
'arduino-fwuploader'
|
||||
);
|
||||
} else {
|
||||
shell.echo(`🔥 Could not interpret 'version': ${version}`);
|
||||
shell.exit(1);
|
||||
console.log(`🔥 Could not interpret 'version': ${version}`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
taskBuildFromGit(version, destinationPath, 'Firmware Uploader');
|
||||
|
@@ -5,7 +5,6 @@
|
||||
|
||||
(() => {
|
||||
const path = require('path');
|
||||
const shell = require('shelljs');
|
||||
const downloader = require('./downloader');
|
||||
const { goBuildFromGit } = require('./utils');
|
||||
|
||||
@@ -25,20 +24,20 @@
|
||||
})();
|
||||
|
||||
if (!DEFAULT_LS_VERSION) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`Could not retrieve Arduino Language Server version info from the 'package.json'.`
|
||||
);
|
||||
shell.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!DEFAULT_CLANGD_VERSION) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`Could not retrieve clangd version info from the 'package.json'.`
|
||||
);
|
||||
shell.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const yargs = require('yargs')
|
||||
const yargs = require('@theia/core/shared/yargs')
|
||||
.option('ls-version', {
|
||||
alias: 'lv',
|
||||
default: DEFAULT_LS_VERSION,
|
||||
@@ -114,10 +113,10 @@
|
||||
throw new Error(`Unsupported platform/arch: ${platformArch}.`);
|
||||
}
|
||||
if (!lsSuffix || !clangdSuffix) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`The arduino-language-server is not available for ${platform} ${arch}.`
|
||||
);
|
||||
shell.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (typeof lsVersion === 'string') {
|
||||
|
@@ -1,20 +1,19 @@
|
||||
// @ts-check
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const shell = require('shelljs');
|
||||
const decompress = require('decompress');
|
||||
const unzip = require('decompress-unzip');
|
||||
const untargz = require('decompress-targz');
|
||||
const untarbz2 = require('decompress-tarbz2');
|
||||
|
||||
process.on('unhandledRejection', (reason, _) => {
|
||||
shell.echo(String(reason));
|
||||
shell.exit(1);
|
||||
throw reason;
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.log(String(reason));
|
||||
process.exit(1);
|
||||
});
|
||||
process.on('uncaughtException', (error) => {
|
||||
shell.echo(String(error));
|
||||
shell.exit(1);
|
||||
throw error;
|
||||
console.log(String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -30,55 +29,42 @@ exports.downloadUnzipFile = async (
|
||||
force = false
|
||||
) => {
|
||||
if (fs.existsSync(targetFile) && !force) {
|
||||
shell.echo(`Skipping download because file already exists: ${targetFile}`);
|
||||
console.log(`Skipping download because file already exists: ${targetFile}`);
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(path.dirname(targetFile))) {
|
||||
if (shell.mkdir('-p', path.dirname(targetFile)).code !== 0) {
|
||||
shell.echo('Could not create new directory.');
|
||||
shell.exit(1);
|
||||
}
|
||||
}
|
||||
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
|
||||
|
||||
const downloads = path.join(__dirname, '..', 'downloads');
|
||||
if (shell.rm('-rf', targetFile, downloads).code !== 0) {
|
||||
shell.exit(1);
|
||||
}
|
||||
fs.rmSync(targetFile, { recursive: true, force: true });
|
||||
fs.rmSync(downloads, { recursive: true, force: true });
|
||||
|
||||
shell.echo(`>>> Downloading from '${url}'...`);
|
||||
const { default: download } = await import('@xhmikosr/downloader');
|
||||
console.log(`>>> Downloading from '${url}'...`);
|
||||
const data = await download(url);
|
||||
shell.echo(`<<< Download succeeded.`);
|
||||
console.log(`<<< Download succeeded.`);
|
||||
|
||||
shell.echo('>>> Decompressing...');
|
||||
console.log('>>> Decompressing...');
|
||||
const files = await decompress(data, downloads, {
|
||||
plugins: [unzip(), untargz(), untarbz2()],
|
||||
});
|
||||
if (files.length === 0) {
|
||||
shell.echo('Error ocurred while decompressing the archive.');
|
||||
shell.exit(1);
|
||||
console.log('Error ocurred while decompressing the archive.');
|
||||
process.exit(1);
|
||||
}
|
||||
const fileIndex = files.findIndex((f) => f.path.startsWith(filePrefix));
|
||||
if (fileIndex === -1) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`The downloaded artifact does not contain any file with prefix ${filePrefix}.`
|
||||
);
|
||||
shell.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
shell.echo('<<< Decompressing succeeded.');
|
||||
console.log('<<< Decompressing succeeded.');
|
||||
|
||||
if (
|
||||
shell.mv('-f', path.join(downloads, files[fileIndex].path), targetFile)
|
||||
.code !== 0
|
||||
) {
|
||||
shell.echo(`Could not move file to target path: ${targetFile}`);
|
||||
shell.exit(1);
|
||||
}
|
||||
fs.renameSync(path.join(downloads, files[fileIndex].path), targetFile);
|
||||
if (!fs.existsSync(targetFile)) {
|
||||
shell.echo(`Could not find file: ${targetFile}`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not find file: ${targetFile}`);
|
||||
process.exit(1);
|
||||
}
|
||||
shell.echo(`Done: ${targetFile}`);
|
||||
console.log(`Done: ${targetFile}`);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -86,7 +72,7 @@ exports.downloadUnzipFile = async (
|
||||
* @param targetDir {string} Directory into which to decompress the archive
|
||||
* @param targetFile {string} Path to the main file expected after decompressing
|
||||
* @param force {boolean} Whether to download even if the target file exists
|
||||
* @param decompressOptions {import('decompress').DecompressOptions}
|
||||
* @param decompressOptions {import('decompress').DecompressOptions|undefined} [decompressOptions]
|
||||
*/
|
||||
exports.downloadUnzipAll = async (
|
||||
url,
|
||||
@@ -96,22 +82,16 @@ exports.downloadUnzipAll = async (
|
||||
decompressOptions = undefined
|
||||
) => {
|
||||
if (fs.existsSync(targetFile) && !force) {
|
||||
shell.echo(`Skipping download because file already exists: ${targetFile}`);
|
||||
console.log(`Skipping download because file already exists: ${targetFile}`);
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
if (shell.mkdir('-p', targetDir).code !== 0) {
|
||||
shell.echo('Could not create new directory.');
|
||||
shell.exit(1);
|
||||
}
|
||||
}
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
|
||||
shell.echo(`>>> Downloading from '${url}'...`);
|
||||
const { default: download } = await import('@xhmikosr/downloader');
|
||||
console.log(`>>> Downloading from '${url}'...`);
|
||||
const data = await download(url);
|
||||
shell.echo(`<<< Download succeeded.`);
|
||||
console.log(`<<< Download succeeded.`);
|
||||
|
||||
shell.echo('>>> Decompressing...');
|
||||
console.log('>>> Decompressing...');
|
||||
let options = {
|
||||
plugins: [unzip(), untargz(), untarbz2()],
|
||||
};
|
||||
@@ -120,14 +100,27 @@ exports.downloadUnzipAll = async (
|
||||
}
|
||||
const files = await decompress(data, targetDir, options);
|
||||
if (files.length === 0) {
|
||||
shell.echo('Error ocurred while decompressing the archive.');
|
||||
shell.exit(1);
|
||||
console.log('Error ocurred while decompressing the archive.');
|
||||
process.exit(1);
|
||||
}
|
||||
shell.echo('<<< Decompressing succeeded.');
|
||||
console.log('<<< Decompressing succeeded.');
|
||||
|
||||
if (!fs.existsSync(targetFile)) {
|
||||
shell.echo(`Could not find file: ${targetFile}`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not find file: ${targetFile}`);
|
||||
process.exit(1);
|
||||
}
|
||||
shell.echo(`Done: ${targetFile}`);
|
||||
console.log(`Done: ${targetFile}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns {Promise<import('node:buffer').Buffer>}
|
||||
*/
|
||||
async function download(url) {
|
||||
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);
|
||||
return data;
|
||||
}
|
||||
|
@@ -3,22 +3,19 @@
|
||||
(async () => {
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { mkdirSync, promises: fs } = require('node:fs');
|
||||
const { exec } = require('./utils');
|
||||
const glob = require('glob');
|
||||
const { v4 } = require('uuid');
|
||||
const shell = require('shelljs');
|
||||
const { SemVer, gte, valid: validSemVer } = require('semver');
|
||||
const protoc = path.dirname(require('protoc/protoc'));
|
||||
|
||||
const repository = path.join(os.tmpdir(), `${v4()}-arduino-cli`);
|
||||
if (shell.mkdir('-p', repository).code !== 0) {
|
||||
shell.exit(1);
|
||||
}
|
||||
const repository = await fs.mkdtemp(path.join(os.tmpdir(), 'arduino-cli-'));
|
||||
|
||||
const { owner, repo, commitish } = (() => {
|
||||
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
||||
if (!pkg) {
|
||||
shell.echo(`Could not parse the 'package.json'.`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not parse the 'package.json'.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const defaultVersion = {
|
||||
@@ -48,21 +45,21 @@
|
||||
// We assume an object with `owner`, `repo`, commitish?` properties.
|
||||
const { owner, repo, commitish } = version;
|
||||
if (!owner) {
|
||||
shell.echo(`Could not retrieve 'owner' from ${JSON.stringify(version)}`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not retrieve 'owner' from ${JSON.stringify(version)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (!repo) {
|
||||
shell.echo(`Could not retrieve 'repo' from ${JSON.stringify(version)}`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not retrieve 'repo' from ${JSON.stringify(version)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return { owner, repo, commitish };
|
||||
})();
|
||||
|
||||
const url = `https://github.com/${owner}/${repo}.git`;
|
||||
shell.echo(`>>> Cloning repository from '${url}'...`);
|
||||
exec('git', ['clone', url, repository], shell);
|
||||
shell.echo(`<<< Repository cloned.`);
|
||||
console.log(`>>> Cloning repository from '${url}'...`);
|
||||
exec('git', ['clone', url, repository], { logStdout: true });
|
||||
console.log(`<<< Repository cloned.`);
|
||||
|
||||
const { platform } = process;
|
||||
const resourcesFolder = path.join(
|
||||
@@ -76,10 +73,12 @@
|
||||
resourcesFolder,
|
||||
`arduino-cli${platform === 'win32' ? '.exe' : ''}`
|
||||
);
|
||||
const versionJson = exec(cli, ['version', '--format', 'json'], shell).trim();
|
||||
const versionJson = exec(cli, ['version', '--format', 'json'], {
|
||||
logStdout: true,
|
||||
}).trim();
|
||||
if (!versionJson) {
|
||||
shell.echo(`Could not retrieve the CLI version from ${cli}.`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not retrieve the CLI version from ${cli}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
// As of today (28.01.2021), the `VersionString` can be one of the followings:
|
||||
// - `nightly-YYYYMMDD` stands for the nightly build, we use the , the `commitish` from the `package.json` to check out the code.
|
||||
@@ -96,52 +95,57 @@
|
||||
}
|
||||
*/
|
||||
const versionObject = JSON.parse(versionJson);
|
||||
const version = versionObject.VersionString;
|
||||
if (
|
||||
version &&
|
||||
!version.startsWith('nightly-') &&
|
||||
version !== '0.0.0-git' &&
|
||||
version !== 'git-snapshot'
|
||||
) {
|
||||
shell.echo(`>>> Checking out tagged version: '${version}'...`);
|
||||
exec('git', ['-C', repository, 'fetch', '--all', '--tags'], shell);
|
||||
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}`;
|
||||
}
|
||||
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],
|
||||
shell
|
||||
{ logStdout: true }
|
||||
);
|
||||
shell.echo(`<<< Checked out tagged version: '${version}'.`);
|
||||
console.log(`<<< Checked out tagged version: '${version}'.`);
|
||||
} else if (commitish) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`>>> Checking out commitish from 'package.json': '${commitish}'...`
|
||||
);
|
||||
exec('git', ['-C', repository, 'checkout', commitish], shell);
|
||||
shell.echo(
|
||||
exec('git', ['-C', repository, 'checkout', commitish], { logStdout: true });
|
||||
console.log(
|
||||
`<<< Checked out commitish from 'package.json': '${commitish}'.`
|
||||
);
|
||||
} else if (versionObject.Commit) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`>>> Checking out commitish from the CLI: '${versionObject.Commit}'...`
|
||||
);
|
||||
exec('git', ['-C', repository, 'checkout', versionObject.Commit], shell);
|
||||
shell.echo(
|
||||
exec('git', ['-C', repository, 'checkout', versionObject.Commit], {
|
||||
logStdout: true,
|
||||
});
|
||||
console.log(
|
||||
`<<< Checked out commitish from the CLI: '${versionObject.Commit}'.`
|
||||
);
|
||||
} else {
|
||||
shell.echo(`WARN: no 'git checkout'. Generating from the HEAD revision.`);
|
||||
console.log(`WARN: no 'git checkout'. Generating from the HEAD revision.`);
|
||||
}
|
||||
|
||||
shell.echo('>>> Generating TS/JS API from:');
|
||||
exec('git', ['-C', repository, 'rev-parse', '--abbrev-ref', 'HEAD'], shell);
|
||||
console.log('>>> Generating TS/JS API from:');
|
||||
exec('git', ['-C', repository, 'rev-parse', '--abbrev-ref', 'HEAD'], {
|
||||
logStdout: true,
|
||||
});
|
||||
|
||||
const rpc = path.join(repository, 'rpc');
|
||||
const out = path.join(__dirname, '..', 'src', 'node', 'cli-protocol');
|
||||
shell.mkdir('-p', out);
|
||||
mkdirSync(out, { recursive: true });
|
||||
|
||||
const protos = await new Promise((resolve) =>
|
||||
glob('**/*.proto', { cwd: rpc }, (error, matches) => {
|
||||
if (error) {
|
||||
shell.echo(error.stack ?? error.message);
|
||||
console.log(error.stack ?? error.message);
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
@@ -149,12 +153,11 @@
|
||||
})
|
||||
);
|
||||
if (!protos || protos.length === 0) {
|
||||
shell.echo(`Could not find any .proto files under ${rpc}.`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not find any .proto files under ${rpc}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Generate JS code from the `.proto` files.
|
||||
|
||||
exec(
|
||||
'grpc_tools_node_protoc',
|
||||
[
|
||||
@@ -164,7 +167,7 @@
|
||||
rpc,
|
||||
...protos,
|
||||
],
|
||||
shell
|
||||
{ logStdout: true }
|
||||
);
|
||||
|
||||
// Generate the `.d.ts` files for JS.
|
||||
@@ -183,8 +186,8 @@
|
||||
rpc,
|
||||
...protos,
|
||||
],
|
||||
shell
|
||||
{ logStdout: true }
|
||||
);
|
||||
|
||||
shell.echo('<<< Generation was successful.');
|
||||
console.log('<<< Generation was successful.');
|
||||
})();
|
||||
|
@@ -3,24 +3,21 @@
|
||||
const exec = (
|
||||
/** @type {string} */ command,
|
||||
/** @type {readonly string[]} */ args,
|
||||
/** @type {import('shelljs')|undefined}*/ shell = undefined,
|
||||
/** @type {import('node:child_process').ExecFileSyncOptionsWithStringEncoding|undefined} */ options = undefined
|
||||
/** @type {Partial<import('node:child_process').ExecFileSyncOptionsWithStringEncoding> & { logStdout?: boolean }|undefined} */ options = undefined
|
||||
) => {
|
||||
try {
|
||||
const stdout = require('node:child_process').execFileSync(
|
||||
command,
|
||||
args,
|
||||
options ? options : { encoding: 'utf8' }
|
||||
);
|
||||
if (shell) {
|
||||
shell.echo(stdout.trim());
|
||||
const stdout = require('node:child_process').execFileSync(command, args, {
|
||||
encoding: 'utf8',
|
||||
...(options ?? {}),
|
||||
});
|
||||
if (options?.logStdout) {
|
||||
console.log(stdout.trim());
|
||||
}
|
||||
return stdout;
|
||||
} catch (err) {
|
||||
if (shell) {
|
||||
shell.echo(err instanceof Error ? err.message : String(err));
|
||||
shell.exit(1);
|
||||
}
|
||||
console.log(
|
||||
`Failed to execute ${command} with args: ${JSON.stringify(args)}`
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
@@ -59,32 +56,31 @@ function buildFromGit(command, version, destinationPath, taskName) {
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const temp = require('temp');
|
||||
const shell = require('shelljs');
|
||||
|
||||
// We assume an object with `owner`, `repo`, commitish?` properties.
|
||||
if (typeof version !== 'object') {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`Expected a \`{ owner, repo, commitish }\` object. Got <${version}> instead.`
|
||||
);
|
||||
}
|
||||
const { owner, repo, commitish } = version;
|
||||
if (!owner) {
|
||||
shell.echo(`Could not retrieve 'owner' from ${JSON.stringify(version)}`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not retrieve 'owner' from ${JSON.stringify(version)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (!repo) {
|
||||
shell.echo(`Could not retrieve 'repo' from ${JSON.stringify(version)}`);
|
||||
shell.exit(1);
|
||||
console.log(`Could not retrieve 'repo' from ${JSON.stringify(version)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const url = `https://github.com/${owner}/${repo}.git`;
|
||||
shell.echo(
|
||||
console.log(
|
||||
`Building ${taskName} from ${url}. Commitish: ${
|
||||
commitish ? commitish : 'HEAD'
|
||||
}`
|
||||
);
|
||||
|
||||
if (fs.existsSync(destinationPath)) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`Skipping the ${taskName} build because it already exists: ${destinationPath}`
|
||||
);
|
||||
return;
|
||||
@@ -97,48 +93,51 @@ function buildFromGit(command, version, destinationPath, taskName) {
|
||||
'node',
|
||||
'resources'
|
||||
);
|
||||
if (shell.mkdir('-p', resourcesFolder).code !== 0) {
|
||||
shell.echo('Could not create resources folder.');
|
||||
shell.exit(1);
|
||||
}
|
||||
fs.mkdirSync(resourcesFolder, { recursive: true });
|
||||
|
||||
const tempRepoPath = temp.mkdirSync();
|
||||
shell.echo(`>>> Cloning ${taskName} source to ${tempRepoPath}...`);
|
||||
exec('git', ['clone', url, tempRepoPath], shell);
|
||||
shell.echo(`<<< Cloned ${taskName} repo.`);
|
||||
console.log(`>>> Cloning ${taskName} source to ${tempRepoPath}...`);
|
||||
exec('git', ['clone', url, tempRepoPath], { logStdout: true });
|
||||
console.log(`<<< Cloned ${taskName} repo.`);
|
||||
|
||||
if (commitish) {
|
||||
shell.echo(`>>> Checking out ${commitish}...`);
|
||||
exec('git', ['-C', tempRepoPath, 'checkout', commitish], shell);
|
||||
shell.echo(`<<< Checked out ${commitish}.`);
|
||||
console.log(`>>> Checking out ${commitish}...`);
|
||||
exec('git', ['-C', tempRepoPath, 'checkout', commitish], {
|
||||
logStdout: true,
|
||||
});
|
||||
console.log(`<<< Checked out ${commitish}.`);
|
||||
}
|
||||
|
||||
exec('git', ['-C', tempRepoPath, 'rev-parse', '--short', 'HEAD'], shell);
|
||||
exec('git', ['-C', tempRepoPath, 'rev-parse', '--short', 'HEAD'], {
|
||||
logStdout: true,
|
||||
});
|
||||
|
||||
shell.echo(`>>> Building the ${taskName}...`);
|
||||
exec(command, ['build'], shell, { cwd: tempRepoPath, encoding: 'utf8' });
|
||||
shell.echo(`<<< Done ${taskName} build.`);
|
||||
console.log(`>>> Building the ${taskName}...`);
|
||||
exec(command, ['build'], {
|
||||
cwd: tempRepoPath,
|
||||
encoding: 'utf8',
|
||||
logStdout: true,
|
||||
});
|
||||
console.log(`<<< Done ${taskName} build.`);
|
||||
|
||||
const binName = path.basename(destinationPath);
|
||||
if (!fs.existsSync(path.join(tempRepoPath, binName))) {
|
||||
shell.echo(
|
||||
console.log(
|
||||
`Could not find the ${taskName} at ${path.join(tempRepoPath, binName)}.`
|
||||
);
|
||||
shell.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const binPath = path.join(tempRepoPath, binName);
|
||||
shell.echo(
|
||||
console.log(
|
||||
`>>> Copying ${taskName} from ${binPath} to ${destinationPath}...`
|
||||
);
|
||||
if (shell.cp(binPath, destinationPath).code !== 0) {
|
||||
shell.exit(1);
|
||||
}
|
||||
shell.echo(`<<< Copied the ${taskName}.`);
|
||||
fs.copyFileSync(binPath, destinationPath);
|
||||
console.log(`<<< Copied the ${taskName}.`);
|
||||
|
||||
shell.echo(`<<< Verifying ${taskName}...`);
|
||||
console.log(`<<< Verifying ${taskName}...`);
|
||||
if (!fs.existsSync(destinationPath)) {
|
||||
shell.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
shell.echo(`>>> Verified ${taskName}.`);
|
||||
console.log(`>>> Verified ${taskName}.`);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import '../../src/browser/style/index.css';
|
||||
import { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { Container, ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
|
||||
import { CommandContribution } from '@theia/core/lib/common/command';
|
||||
import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||
@@ -271,8 +271,8 @@ import { MonitorModel } from './monitor-model';
|
||||
import { MonitorManagerProxyClientImpl } from './monitor-manager-proxy-client-impl';
|
||||
import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||
import { EditorManager } from './theia/editor/editor-manager';
|
||||
import { HostedPluginEvents } from './hosted-plugin-events';
|
||||
import { HostedPluginSupport } from './theia/plugin-ext/hosted-plugin';
|
||||
import { HostedPluginEvents } from './hosted/hosted-plugin-events';
|
||||
import { HostedPluginSupportImpl } from './theia/plugin-ext/hosted-plugin';
|
||||
import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import { Formatter, FormatterPath } from '../common/protocol/formatter';
|
||||
import { Format } from './contributions/format';
|
||||
@@ -358,6 +358,20 @@ import { MonacoEditorMenuContribution as TheiaMonacoEditorMenuContribution } fro
|
||||
import { UpdateArduinoState } from './contributions/update-arduino-state';
|
||||
import { TerminalFrontendContribution } from './theia/terminal/terminal-frontend-contribution';
|
||||
import { TerminalFrontendContribution as TheiaTerminalFrontendContribution } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
||||
import { SelectionService } from '@theia/core/lib/common/selection-service';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { CorePreferences } from '@theia/core/lib/browser/core-preferences';
|
||||
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 { 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';
|
||||
|
||||
// Hack to fix copy/cut/paste issue after electron version update in Theia.
|
||||
// https://github.com/eclipse-theia/theia/issues/12487
|
||||
@@ -451,6 +465,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
// To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board.
|
||||
bind(BoardsDataStore).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(BoardsDataStore);
|
||||
bind(CommandContribution).toService(BoardsDataStore);
|
||||
bind(StartupTaskProvider).toService(BoardsDataStore); // to inherit the boards config options, programmer, etc in a new window
|
||||
|
||||
// Logger for the Arduino daemon
|
||||
bind(ILogger)
|
||||
.toDynamicValue((ctx) => {
|
||||
@@ -750,6 +767,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
Contribution.configure(bind, CreateCloudCopy);
|
||||
Contribution.configure(bind, UpdateArduinoState);
|
||||
Contribution.configure(bind, BoardsDataMenuUpdater);
|
||||
Contribution.configure(bind, AutoSelectProgrammer);
|
||||
Contribution.configure(bind, InoHighlight);
|
||||
|
||||
bindContributionProvider(bind, StartupTaskProvider);
|
||||
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
|
||||
@@ -796,10 +815,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
);
|
||||
const iconThemeService =
|
||||
context.container.get<IconThemeService>(IconThemeService);
|
||||
const selectionService =
|
||||
context.container.get<SelectionService>(SelectionService);
|
||||
const commandService =
|
||||
context.container.get<CommandService>(CommandService);
|
||||
const corePreferences =
|
||||
context.container.get<CorePreferences>(CorePreferences);
|
||||
return new TabBarRenderer(
|
||||
contextMenuRenderer,
|
||||
decoratorService,
|
||||
iconThemeService
|
||||
iconThemeService,
|
||||
selectionService,
|
||||
commandService,
|
||||
corePreferences
|
||||
);
|
||||
});
|
||||
|
||||
@@ -842,6 +870,28 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
// To be able to use a `launch.json` from outside of the workspace.
|
||||
bind(DebugConfigurationManager).toSelf().inSingletonScope();
|
||||
rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager);
|
||||
// To update the currently selected debug config <select> option when starting a debug session.
|
||||
bind(DebugSessionManager).toSelf().inSingletonScope();
|
||||
rebind(TheiaDebugSessionManager).toService(DebugSessionManager);
|
||||
// Customized debug widget with its customized config <select> to update it programmatically.
|
||||
bind(WidgetFactory)
|
||||
.toDynamicValue(({ container }) => ({
|
||||
id: DebugWidget.ID,
|
||||
createWidget: () => {
|
||||
const child = new Container({ defaultScope: 'Singleton' });
|
||||
child.parent = container;
|
||||
child.bind(DebugViewModel).toSelf();
|
||||
child.bind(DebugToolBar).toSelf();
|
||||
child.bind(DebugSessionWidget).toSelf();
|
||||
child.bind(DebugConfigurationWidget).toSelf(); // with the patched select
|
||||
child // use the customized one in the Theia DI
|
||||
.bind(TheiaDebugConfigurationWidget)
|
||||
.toService(DebugConfigurationWidget);
|
||||
child.bind(DebugWidget).toSelf();
|
||||
return child.get(DebugWidget);
|
||||
},
|
||||
}))
|
||||
.inSingletonScope();
|
||||
|
||||
// To avoid duplicate tabs use deepEqual instead of string equal: https://github.com/eclipse-theia/theia/issues/11309
|
||||
bind(WidgetManager).toSelf().inSingletonScope();
|
||||
@@ -970,8 +1020,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
})
|
||||
.inSingletonScope();
|
||||
|
||||
bind(HostedPluginSupport).toSelf().inSingletonScope();
|
||||
rebind(TheiaHostedPluginSupport).toService(HostedPluginSupport);
|
||||
bind(HostedPluginSupportImpl).toSelf().inSingletonScope();
|
||||
bind(HostedPluginSupport).toService(HostedPluginSupportImpl);
|
||||
rebind(TheiaHostedPluginSupport).toService(HostedPluginSupportImpl);
|
||||
bind(HostedPluginEvents).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(HostedPluginEvents);
|
||||
|
||||
|
@@ -58,7 +58,9 @@ type StrictPreferenceSchemaProperties<T extends object> = {
|
||||
[p in keyof T]: PreferenceSchemaProperty;
|
||||
};
|
||||
type ArduinoPreferenceSchemaProperties =
|
||||
StrictPreferenceSchemaProperties<ArduinoConfiguration> & { 'arduino.window.zoomLevel': PreferenceSchemaProperty };
|
||||
StrictPreferenceSchemaProperties<ArduinoConfiguration> & {
|
||||
'arduino.window.zoomLevel': PreferenceSchemaProperty;
|
||||
};
|
||||
|
||||
const properties: ArduinoPreferenceSchemaProperties = {
|
||||
'arduino.language.log': {
|
||||
|
@@ -48,16 +48,17 @@ namespace BoardsConfigComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Item<T> extends React.Component<{
|
||||
class Item<T> extends React.Component<{
|
||||
item: T;
|
||||
label: string;
|
||||
selected: boolean;
|
||||
onClick: (item: T) => void;
|
||||
missing?: boolean;
|
||||
details?: string;
|
||||
title?: string | ((item: T) => string);
|
||||
}> {
|
||||
override render(): React.ReactNode {
|
||||
const { selected, label, missing, details } = this.props;
|
||||
const { selected, label, missing, details, item } = this.props;
|
||||
const classNames = ['item'];
|
||||
if (selected) {
|
||||
classNames.push('selected');
|
||||
@@ -65,11 +66,15 @@ export abstract class Item<T> extends React.Component<{
|
||||
if (missing === true) {
|
||||
classNames.push('missing');
|
||||
}
|
||||
let title = this.props.title ?? `${label}${!details ? '' : details}`;
|
||||
if (typeof title === 'function') {
|
||||
title = title(item);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onClick={this.onClick}
|
||||
className={classNames.join(' ')}
|
||||
title={`${label}${!details ? '' : details}`}
|
||||
title={title}
|
||||
>
|
||||
<div className="label">{label}</div>
|
||||
{!details ? '' : <div className="details">{details}</div>}
|
||||
@@ -234,9 +239,20 @@ export class BoardsConfigComponent extends React.Component<
|
||||
distinctBoards.set(key, board);
|
||||
}
|
||||
}
|
||||
const title = (board: Board.Detailed): string => {
|
||||
const { details, manuallyInstalled } = board;
|
||||
let label = board.name;
|
||||
if (details) {
|
||||
label += details;
|
||||
}
|
||||
if (manuallyInstalled) {
|
||||
label += nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)');
|
||||
}
|
||||
return label;
|
||||
};
|
||||
|
||||
const boardsList = Array.from(distinctBoards.values()).map((board) => (
|
||||
<Item<BoardWithPackage>
|
||||
<Item<Board.Detailed>
|
||||
key={toKey(board)}
|
||||
item={board}
|
||||
label={board.name}
|
||||
@@ -244,6 +260,7 @@ export class BoardsConfigComponent extends React.Component<
|
||||
selected={board.selected}
|
||||
onClick={this.selectBoard}
|
||||
missing={board.missing}
|
||||
title={title}
|
||||
/>
|
||||
));
|
||||
|
||||
|
@@ -1,21 +1,41 @@
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { StorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import type {
|
||||
Command,
|
||||
CommandContribution,
|
||||
CommandRegistry,
|
||||
} from '@theia/core/lib/common/command';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { deepClone } from '@theia/core/lib/common/objects';
|
||||
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 {
|
||||
BoardDetails,
|
||||
BoardsService,
|
||||
ConfigOption,
|
||||
ConfigValue,
|
||||
Programmer,
|
||||
isBoardIdentifierChangeEvent,
|
||||
isProgrammer,
|
||||
} from '../../common/protocol';
|
||||
import { notEmpty } from '../../common/utils';
|
||||
import type {
|
||||
StartupTask,
|
||||
StartupTaskProvider,
|
||||
} from '../../electron-common/startup-task';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { BoardsServiceProvider } from './boards-service-provider';
|
||||
|
||||
@injectable()
|
||||
export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
export class BoardsDataStore
|
||||
implements
|
||||
FrontendApplicationContribution,
|
||||
StartupTaskProvider,
|
||||
CommandContribution
|
||||
{
|
||||
@inject(ILogger)
|
||||
@named('store')
|
||||
private readonly logger: ILogger;
|
||||
@@ -23,46 +43,122 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
private readonly boardsService: BoardsService;
|
||||
@inject(NotificationCenter)
|
||||
private readonly notificationCenter: NotificationCenter;
|
||||
@inject(LocalStorageService)
|
||||
private readonly storageService: LocalStorageService;
|
||||
// When `@theia/workspace` is part of the application, the workspace-scoped storage service is the default implementation, and the `StorageService` symbol must be used for the injection.
|
||||
// https://github.com/eclipse-theia/theia/blob/ba3722b04ff91eb6a4af6a571c9e263c77cdd8b5/packages/workspace/src/browser/workspace-frontend-module.ts#L97-L98
|
||||
// In other words, store the data (such as the board configs) per sketch, not per IDE2 installation. https://github.com/arduino/arduino-ide/issues/2240
|
||||
@inject(StorageService)
|
||||
private readonly storageService: StorageService;
|
||||
@inject(BoardsServiceProvider)
|
||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
@inject(FrontendApplicationStateService)
|
||||
private readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
private readonly onChangedEmitter = new Emitter<string[]>();
|
||||
private readonly toDispose = new DisposableCollection(this.onChangedEmitter);
|
||||
private readonly onDidChangeEmitter =
|
||||
new Emitter<BoardsDataStoreChangeEvent>();
|
||||
private readonly toDispose = new DisposableCollection(
|
||||
this.onDidChangeEmitter
|
||||
);
|
||||
private _selectedBoardData: BoardsDataStoreChange | undefined;
|
||||
|
||||
onStart(): void {
|
||||
this.toDispose.push(
|
||||
this.toDispose.pushAll([
|
||||
this.boardsServiceProvider.onBoardsConfigDidChange((event) => {
|
||||
if (isBoardIdentifierChangeEvent(event)) {
|
||||
this.updateSelectedBoardData(event.selectedBoard?.fqbn);
|
||||
}
|
||||
}),
|
||||
this.notificationCenter.onPlatformDidInstall(async ({ item }) => {
|
||||
const dataDidChangePerFqbn: string[] = [];
|
||||
for (const fqbn of item.boards
|
||||
const boardsWithFqbn = item.boards
|
||||
.map(({ fqbn }) => fqbn)
|
||||
.filter(notEmpty)
|
||||
.filter((fqbn) => !!fqbn)) {
|
||||
.filter(notEmpty);
|
||||
const changes: BoardsDataStoreChange[] = [];
|
||||
for (const fqbn of boardsWithFqbn) {
|
||||
const key = this.getStorageKey(fqbn);
|
||||
let data = await this.storageService.getData<ConfigOption[]>(key);
|
||||
if (!data || !data.length) {
|
||||
const details = await this.getBoardDetailsSafe(fqbn);
|
||||
if (details) {
|
||||
data = details.configOptions;
|
||||
if (data.length) {
|
||||
await this.storageService.setData(key, data);
|
||||
dataDidChangePerFqbn.push(fqbn);
|
||||
}
|
||||
}
|
||||
const storedData =
|
||||
await this.storageService.getData<BoardsDataStore.Data>(key);
|
||||
if (!storedData) {
|
||||
// if no previously value is available for the board, do not update the cache
|
||||
continue;
|
||||
}
|
||||
const details = await this.loadBoardDetails(fqbn);
|
||||
if (details) {
|
||||
const data = createDataStoreEntry(details);
|
||||
await this.storageService.setData(key, data);
|
||||
changes.push({ fqbn, data });
|
||||
}
|
||||
}
|
||||
if (dataDidChangePerFqbn.length) {
|
||||
this.fireChanged(...dataDidChangePerFqbn);
|
||||
if (changes.length) {
|
||||
this.fireChanged(...changes);
|
||||
}
|
||||
})
|
||||
}),
|
||||
this.onDidChange((event) => {
|
||||
const selectedFqbn =
|
||||
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn;
|
||||
if (event.changes.find((change) => change.fqbn === selectedFqbn)) {
|
||||
this.updateSelectedBoardData(selectedFqbn);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
Promise.all([
|
||||
this.boardsServiceProvider.ready,
|
||||
this.appStateService.reachedState('ready'),
|
||||
]).then(() =>
|
||||
this.updateSelectedBoardData(
|
||||
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private async getSelectedBoardData(
|
||||
fqbn: string | undefined
|
||||
): Promise<BoardsDataStoreChange | undefined> {
|
||||
if (!fqbn) {
|
||||
return undefined;
|
||||
} else {
|
||||
const data = await this.getData(fqbn);
|
||||
if (data === BoardsDataStore.Data.EMPTY) {
|
||||
return undefined;
|
||||
}
|
||||
return { fqbn, data };
|
||||
}
|
||||
}
|
||||
|
||||
private async updateSelectedBoardData(
|
||||
fqbn: string | undefined
|
||||
): Promise<void> {
|
||||
this._selectedBoardData = await this.getSelectedBoardData(fqbn);
|
||||
}
|
||||
|
||||
onStop(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
get onChanged(): Event<string[]> {
|
||||
return this.onChangedEmitter.event;
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(USE_INHERITED_DATA, {
|
||||
execute: async (arg: unknown) => {
|
||||
if (isBoardsDataStoreChange(arg)) {
|
||||
await this.setData(arg);
|
||||
this.fireChanged(arg);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
tasks(): StartupTask[] {
|
||||
if (!this._selectedBoardData) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
command: USE_INHERITED_DATA.id,
|
||||
args: [this._selectedBoardData],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
get onDidChange(): Event<BoardsDataStoreChangeEvent> {
|
||||
return this.onDidChangeEmitter.event;
|
||||
}
|
||||
|
||||
async appendConfigToFqbn(
|
||||
@@ -81,22 +177,19 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
}
|
||||
|
||||
const key = this.getStorageKey(fqbn);
|
||||
let data = await this.storageService.getData<
|
||||
const storedData = await this.storageService.getData<
|
||||
BoardsDataStore.Data | undefined
|
||||
>(key, undefined);
|
||||
if (BoardsDataStore.Data.is(data)) {
|
||||
return data;
|
||||
if (BoardsDataStore.Data.is(storedData)) {
|
||||
return storedData;
|
||||
}
|
||||
|
||||
const boardDetails = await this.getBoardDetailsSafe(fqbn);
|
||||
const boardDetails = await this.loadBoardDetails(fqbn);
|
||||
if (!boardDetails) {
|
||||
return BoardsDataStore.Data.EMPTY;
|
||||
}
|
||||
|
||||
data = {
|
||||
configOptions: boardDetails.configOptions,
|
||||
programmers: boardDetails.programmers,
|
||||
};
|
||||
const data = createDataStoreEntry(boardDetails);
|
||||
await this.storageService.setData(key, data);
|
||||
return data;
|
||||
}
|
||||
@@ -108,17 +201,15 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
fqbn: string;
|
||||
selectedProgrammer: Programmer;
|
||||
}): Promise<boolean> {
|
||||
const data = deepClone(await this.getData(fqbn));
|
||||
const { programmers } = data;
|
||||
const storedData = deepClone(await this.getData(fqbn));
|
||||
const { programmers } = storedData;
|
||||
if (!programmers.find((p) => Programmer.equals(selectedProgrammer, p))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.setData({
|
||||
fqbn,
|
||||
data: { ...data, selectedProgrammer },
|
||||
});
|
||||
this.fireChanged(fqbn);
|
||||
const data = { ...storedData, selectedProgrammer };
|
||||
await this.setData({ fqbn, data });
|
||||
this.fireChanged({ fqbn, data });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -139,28 +230,24 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
}
|
||||
let updated = false;
|
||||
for (const value of configOption.values) {
|
||||
if (value.value === selectedValue) {
|
||||
(value as any).selected = true;
|
||||
const mutable: Mutable<ConfigValue> = value;
|
||||
if (mutable.value === selectedValue) {
|
||||
mutable.selected = true;
|
||||
updated = true;
|
||||
} else {
|
||||
(value as any).selected = false;
|
||||
mutable.selected = false;
|
||||
}
|
||||
}
|
||||
if (!updated) {
|
||||
return false;
|
||||
}
|
||||
await this.setData({ fqbn, data });
|
||||
this.fireChanged(fqbn);
|
||||
this.fireChanged({ fqbn, data });
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async setData({
|
||||
fqbn,
|
||||
data,
|
||||
}: {
|
||||
fqbn: string;
|
||||
data: BoardsDataStore.Data;
|
||||
}): Promise<void> {
|
||||
protected async setData(change: BoardsDataStoreChange): Promise<void> {
|
||||
const { fqbn, data } = change;
|
||||
const key = this.getStorageKey(fqbn);
|
||||
return this.storageService.setData(key, data);
|
||||
}
|
||||
@@ -169,11 +256,9 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
return `.arduinoIDE-configOptions-${fqbn}`;
|
||||
}
|
||||
|
||||
protected async getBoardDetailsSafe(
|
||||
fqbn: string
|
||||
): Promise<BoardDetails | undefined> {
|
||||
async loadBoardDetails(fqbn: string): Promise<BoardDetails | undefined> {
|
||||
try {
|
||||
const details = this.boardsService.getBoardDetails({ fqbn });
|
||||
const details = await this.boardsService.getBoardDetails({ fqbn });
|
||||
return details;
|
||||
} catch (err) {
|
||||
if (
|
||||
@@ -194,8 +279,8 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
}
|
||||
}
|
||||
|
||||
protected fireChanged(...fqbn: string[]): void {
|
||||
this.onChangedEmitter.fire(fqbn);
|
||||
protected fireChanged(...changes: BoardsDataStoreChange[]): void {
|
||||
this.onDidChangeEmitter.fire({ changes });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,20 +289,86 @@ export namespace BoardsDataStore {
|
||||
readonly configOptions: ConfigOption[];
|
||||
readonly programmers: Programmer[];
|
||||
readonly selectedProgrammer?: Programmer;
|
||||
readonly defaultProgrammerId?: string;
|
||||
}
|
||||
export namespace Data {
|
||||
export const EMPTY: Data = {
|
||||
export const EMPTY: Data = deepFreeze({
|
||||
configOptions: [],
|
||||
programmers: [],
|
||||
};
|
||||
export function is(arg: any): arg is Data {
|
||||
});
|
||||
|
||||
export function is(arg: unknown): arg is Data {
|
||||
return (
|
||||
!!arg &&
|
||||
'configOptions' in arg &&
|
||||
Array.isArray(arg['configOptions']) &&
|
||||
'programmers' in arg &&
|
||||
Array.isArray(arg['programmers'])
|
||||
typeof arg === 'object' &&
|
||||
arg !== null &&
|
||||
Array.isArray((<Data>arg).configOptions) &&
|
||||
Array.isArray((<Data>arg).programmers) &&
|
||||
((<Data>arg).selectedProgrammer === undefined ||
|
||||
isProgrammer((<Data>arg).selectedProgrammer)) &&
|
||||
((<Data>arg).defaultProgrammerId === undefined ||
|
||||
typeof (<Data>arg).defaultProgrammerId === 'string')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isEmptyData(data: BoardsDataStore.Data): boolean {
|
||||
return (
|
||||
Boolean(!data.configOptions.length) &&
|
||||
Boolean(!data.programmers.length) &&
|
||||
Boolean(!data.selectedProgrammer) &&
|
||||
Boolean(!data.defaultProgrammerId)
|
||||
);
|
||||
}
|
||||
|
||||
export function findDefaultProgrammer(
|
||||
programmers: readonly Programmer[],
|
||||
defaultProgrammerId: string | undefined | BoardsDataStore.Data
|
||||
): Programmer | undefined {
|
||||
if (!defaultProgrammerId) {
|
||||
return undefined;
|
||||
}
|
||||
const id =
|
||||
typeof defaultProgrammerId === 'string'
|
||||
? defaultProgrammerId
|
||||
: defaultProgrammerId.defaultProgrammerId;
|
||||
return programmers.find((p) => p.id === id);
|
||||
}
|
||||
function createDataStoreEntry(details: BoardDetails): BoardsDataStore.Data {
|
||||
const configOptions = details.configOptions.slice();
|
||||
const programmers = details.programmers.slice();
|
||||
const { defaultProgrammerId } = details;
|
||||
const selectedProgrammer = findDefaultProgrammer(
|
||||
programmers,
|
||||
defaultProgrammerId
|
||||
);
|
||||
const data = {
|
||||
configOptions,
|
||||
programmers,
|
||||
...(selectedProgrammer ? { selectedProgrammer } : {}),
|
||||
...(defaultProgrammerId ? { defaultProgrammerId } : {}),
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
export interface BoardsDataStoreChange {
|
||||
readonly fqbn: string;
|
||||
readonly data: BoardsDataStore.Data;
|
||||
}
|
||||
|
||||
function isBoardsDataStoreChange(arg: unknown): arg is BoardsDataStoreChange {
|
||||
return (
|
||||
typeof arg === 'object' &&
|
||||
arg !== null &&
|
||||
typeof (<BoardsDataStoreChange>arg).fqbn === 'string' &&
|
||||
BoardsDataStore.Data.is((<BoardsDataStoreChange>arg).data)
|
||||
);
|
||||
}
|
||||
|
||||
export interface BoardsDataStoreChangeEvent {
|
||||
readonly changes: readonly BoardsDataStoreChange[];
|
||||
}
|
||||
|
||||
const USE_INHERITED_DATA: Command = {
|
||||
id: 'arduino-use-inherited-boards-data',
|
||||
};
|
||||
|
@@ -0,0 +1,123 @@
|
||||
import type { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
BoardDetails,
|
||||
Programmer,
|
||||
isBoardIdentifierChangeEvent,
|
||||
} from '../../common/protocol';
|
||||
import {
|
||||
BoardsDataStore,
|
||||
findDefaultProgrammer,
|
||||
isEmptyData,
|
||||
} from '../boards/boards-data-store';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { Contribution } from './contribution';
|
||||
|
||||
/**
|
||||
* Before CLI 0.35.0-rc.3, there was no `programmer#default` property in the `board details` response.
|
||||
* This method does the programmer migration in the data store. If there is a programmer selected, it's a noop.
|
||||
* If no programmer is selected, it forcefully reloads the details from the CLI and updates it in the local storage.
|
||||
*/
|
||||
@injectable()
|
||||
export class AutoSelectProgrammer extends Contribution {
|
||||
@inject(BoardsServiceProvider)
|
||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
@inject(BoardsDataStore)
|
||||
private readonly boardsDataStore: BoardsDataStore;
|
||||
|
||||
override onStart(): void {
|
||||
this.boardsServiceProvider.onBoardsConfigDidChange((event) => {
|
||||
if (isBoardIdentifierChangeEvent(event)) {
|
||||
this.ensureProgrammerIsSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this.boardsServiceProvider.ready.then(() =>
|
||||
this.ensureProgrammerIsSelected()
|
||||
);
|
||||
}
|
||||
|
||||
private async ensureProgrammerIsSelected(): Promise<boolean> {
|
||||
return ensureProgrammerIsSelected({
|
||||
fqbn: this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn,
|
||||
getData: (fqbn) => this.boardsDataStore.getData(fqbn),
|
||||
loadBoardDetails: (fqbn) => this.boardsDataStore.loadBoardDetails(fqbn),
|
||||
selectProgrammer: (arg) => this.boardsDataStore.selectProgrammer(arg),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface EnsureProgrammerIsSelectedParams {
|
||||
fqbn: string | undefined;
|
||||
getData: (fqbn: string | undefined) => MaybePromise<BoardsDataStore.Data>;
|
||||
loadBoardDetails: (fqbn: string) => MaybePromise<BoardDetails | undefined>;
|
||||
selectProgrammer(options: {
|
||||
fqbn: string;
|
||||
selectedProgrammer: Programmer;
|
||||
}): MaybePromise<boolean>;
|
||||
}
|
||||
|
||||
export async function ensureProgrammerIsSelected(
|
||||
params: EnsureProgrammerIsSelectedParams
|
||||
): Promise<boolean> {
|
||||
const { fqbn, getData, loadBoardDetails, selectProgrammer } = params;
|
||||
if (!fqbn) {
|
||||
return false;
|
||||
}
|
||||
console.debug(`Ensuring a programmer is selected for ${fqbn}...`);
|
||||
const data = await getData(fqbn);
|
||||
if (isEmptyData(data)) {
|
||||
// For example, the platform is not installed.
|
||||
console.debug(`Skipping. No boards data is available for ${fqbn}.`);
|
||||
return false;
|
||||
}
|
||||
if (data.selectedProgrammer) {
|
||||
console.debug(
|
||||
`A programmer is already selected for ${fqbn}: '${data.selectedProgrammer.id}'.`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
let programmer = findDefaultProgrammer(data.programmers, data);
|
||||
if (programmer) {
|
||||
// select the programmer if the default info is available
|
||||
const result = await selectProgrammer({
|
||||
fqbn,
|
||||
selectedProgrammer: programmer,
|
||||
});
|
||||
if (result) {
|
||||
console.debug(`Selected '${programmer.id}' programmer for ${fqbn}.`);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
console.debug(`Reloading board details for ${fqbn}...`);
|
||||
const reloadedData = await loadBoardDetails(fqbn);
|
||||
if (!reloadedData) {
|
||||
console.debug(`Skipping. No board details found for ${fqbn}.`);
|
||||
return false;
|
||||
}
|
||||
if (!reloadedData.programmers.length) {
|
||||
console.debug(`Skipping. ${fqbn} does not have programmers.`);
|
||||
return false;
|
||||
}
|
||||
programmer = findDefaultProgrammer(reloadedData.programmers, reloadedData);
|
||||
if (!programmer) {
|
||||
console.debug(
|
||||
`Skipping. Could not find a default programmer for ${fqbn}. Programmers were: `
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const result = await selectProgrammer({
|
||||
fqbn,
|
||||
selectedProgrammer: programmer,
|
||||
});
|
||||
if (result) {
|
||||
console.debug(`Selected '${programmer.id}' programmer for ${fqbn}.`);
|
||||
} else {
|
||||
console.debug(
|
||||
`Could not select '${programmer.id}' programmer for ${fqbn}.`
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
@@ -35,7 +35,7 @@ export class BoardsDataMenuUpdater extends Contribution {
|
||||
private readonly toDisposeOnBoardChange = new DisposableCollection();
|
||||
|
||||
override onStart(): void {
|
||||
this.boardsDataStore.onChanged(() =>
|
||||
this.boardsDataStore.onDidChange(() =>
|
||||
this.updateMenuActions(
|
||||
this.boardsServiceProvider.boardsConfig.selectedBoard
|
||||
)
|
||||
|
@@ -1,106 +1,155 @@
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { noBoardSelected } from '../../common/nls';
|
||||
import {
|
||||
Board,
|
||||
BoardDetails,
|
||||
BoardIdentifier,
|
||||
BoardsService,
|
||||
CheckDebugEnabledParams,
|
||||
ExecutableService,
|
||||
SketchRef,
|
||||
isBoardIdentifierChangeEvent,
|
||||
Sketch,
|
||||
isCompileSummary,
|
||||
} from '../../common/protocol';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { HostedPluginSupport } from '../hosted/hosted-plugin-support';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import {
|
||||
URI,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
SketchContribution,
|
||||
TabBarToolbarRegistry,
|
||||
URI,
|
||||
} from './contribution';
|
||||
import { MaybePromise, MenuModelRegistry, nls } from '@theia/core/lib/common';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
const COMPILE_FOR_DEBUG_KEY = 'arduino-compile-for-debug';
|
||||
|
||||
interface StartDebugParams {
|
||||
/**
|
||||
* Absolute filesystem path to the Arduino CLI executable.
|
||||
*/
|
||||
readonly cliPath: string;
|
||||
/**
|
||||
* The the board to debug.
|
||||
*/
|
||||
readonly board: Readonly<{ fqbn: string; name?: string }>;
|
||||
/**
|
||||
* Absolute filesystem path of the sketch to debug.
|
||||
*/
|
||||
readonly sketchPath: string;
|
||||
/**
|
||||
* Location where the `launch.json` will be created on the fly before starting every debug session.
|
||||
* If not defined, it falls back to `sketchPath/.vscode/launch.json`.
|
||||
*/
|
||||
readonly launchConfigsDirPath?: string;
|
||||
/**
|
||||
* Absolute path to the `arduino-cli.yaml` file. If not specified, it falls back to `~/.arduinoIDE/arduino-cli.yaml`.
|
||||
*/
|
||||
readonly cliConfigPath?: string;
|
||||
/**
|
||||
* Programmer for the debugging.
|
||||
*/
|
||||
readonly programmer?: string;
|
||||
/**
|
||||
* Custom progress title to use when getting the debug information from the CLI.
|
||||
*/
|
||||
readonly title?: string;
|
||||
}
|
||||
type StartDebugResult = boolean;
|
||||
|
||||
@injectable()
|
||||
export class Debug extends SketchContribution {
|
||||
@inject(HostedPluginSupport)
|
||||
private readonly hostedPluginSupport: HostedPluginSupport;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
private readonly notificationCenter: NotificationCenter;
|
||||
|
||||
@inject(ExecutableService)
|
||||
private readonly executableService: ExecutableService;
|
||||
|
||||
@inject(BoardsService)
|
||||
private readonly boardService: BoardsService;
|
||||
|
||||
@inject(BoardsServiceProvider)
|
||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
@inject(BoardsDataStore)
|
||||
private readonly boardsDataStore: BoardsDataStore;
|
||||
|
||||
/**
|
||||
* If `undefined`, debugging is enabled. Otherwise, the reason why it's disabled.
|
||||
* If `undefined`, debugging is enabled. Otherwise, the human-readable reason why it's disabled.
|
||||
*/
|
||||
private _disabledMessages?: string = nls.localize(
|
||||
'arduino/common/noBoardSelected',
|
||||
'No board selected'
|
||||
); // Initial pessimism.
|
||||
private disabledMessageDidChangeEmitter = new Emitter<string | undefined>();
|
||||
private onDisabledMessageDidChange =
|
||||
this.disabledMessageDidChangeEmitter.event;
|
||||
private _message?: string = noBoardSelected; // Initial pessimism.
|
||||
private didChangeMessageEmitter = new Emitter<string | undefined>();
|
||||
private onDidChangeMessage = this.didChangeMessageEmitter.event;
|
||||
|
||||
private get disabledMessage(): string | undefined {
|
||||
return this._disabledMessages;
|
||||
private get message(): string | undefined {
|
||||
return this._message;
|
||||
}
|
||||
private set disabledMessage(message: string | undefined) {
|
||||
this._disabledMessages = message;
|
||||
this.disabledMessageDidChangeEmitter.fire(this._disabledMessages);
|
||||
private set message(message: string | undefined) {
|
||||
this._message = message;
|
||||
this.didChangeMessageEmitter.fire(this._message);
|
||||
}
|
||||
|
||||
private readonly debugToolbarItem = {
|
||||
id: Debug.Commands.START_DEBUGGING.id,
|
||||
command: Debug.Commands.START_DEBUGGING.id,
|
||||
tooltip: `${
|
||||
this.disabledMessage
|
||||
this.message
|
||||
? nls.localize(
|
||||
'arduino/debug/debugWithMessage',
|
||||
'Debug - {0}',
|
||||
this.disabledMessage
|
||||
this.message
|
||||
)
|
||||
: Debug.Commands.START_DEBUGGING.label
|
||||
}`,
|
||||
priority: 3,
|
||||
onDidChange: this.onDisabledMessageDidChange as Event<void>,
|
||||
onDidChange: this.onDidChangeMessage as Event<void>,
|
||||
};
|
||||
|
||||
override onStart(): void {
|
||||
this.onDisabledMessageDidChange(
|
||||
this.onDidChangeMessage(
|
||||
() =>
|
||||
(this.debugToolbarItem.tooltip = `${
|
||||
this.disabledMessage
|
||||
this.message
|
||||
? nls.localize(
|
||||
'arduino/debug/debugWithMessage',
|
||||
'Debug - {0}',
|
||||
this.disabledMessage
|
||||
this.message
|
||||
)
|
||||
: Debug.Commands.START_DEBUGGING.label
|
||||
}`)
|
||||
);
|
||||
this.boardsServiceProvider.onBoardsConfigDidChange((event) => {
|
||||
if (isBoardIdentifierChangeEvent(event)) {
|
||||
this.refreshState(event.selectedBoard);
|
||||
this.updateMessage();
|
||||
}
|
||||
});
|
||||
this.notificationCenter.onPlatformDidInstall(() => this.updateMessage());
|
||||
this.notificationCenter.onPlatformDidUninstall(() => this.updateMessage());
|
||||
this.boardsDataStore.onDidChange((event) => {
|
||||
const selectedFqbn =
|
||||
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn;
|
||||
if (event.changes.find((change) => change.fqbn === selectedFqbn)) {
|
||||
this.updateMessage();
|
||||
}
|
||||
});
|
||||
this.commandService.onDidExecuteCommand((event) => {
|
||||
const { commandId, args } = event;
|
||||
if (
|
||||
commandId === 'arduino.languageserver.notifyBuildDidComplete' &&
|
||||
isCompileSummary(args[0])
|
||||
) {
|
||||
this.updateMessage();
|
||||
}
|
||||
});
|
||||
this.notificationCenter.onPlatformDidInstall(() => this.refreshState());
|
||||
this.notificationCenter.onPlatformDidUninstall(() => this.refreshState());
|
||||
}
|
||||
|
||||
override onReady(): MaybePromise<void> {
|
||||
this.refreshState();
|
||||
override onReady(): void {
|
||||
this.boardsServiceProvider.ready.then(() => this.updateMessage());
|
||||
}
|
||||
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
@@ -108,7 +157,7 @@ export class Debug extends SketchContribution {
|
||||
execute: () => this.startDebug(),
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isEnabled: () => !this.disabledMessage,
|
||||
isEnabled: () => !this.message,
|
||||
});
|
||||
registry.registerCommand(Debug.Commands.TOGGLE_OPTIMIZE_FOR_DEBUG, {
|
||||
execute: () => this.toggleCompileForDebug(),
|
||||
@@ -131,94 +180,56 @@ export class Debug extends SketchContribution {
|
||||
});
|
||||
}
|
||||
|
||||
private async refreshState(
|
||||
board: Board | undefined = this.boardsServiceProvider.boardsConfig
|
||||
private async updateMessage(): Promise<void> {
|
||||
try {
|
||||
await this.isDebugEnabled();
|
||||
this.message = undefined;
|
||||
} catch (err) {
|
||||
let message = String(err);
|
||||
if (err instanceof Error) {
|
||||
message = err.message;
|
||||
}
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
private async isDebugEnabled(
|
||||
board: BoardIdentifier | undefined = this.boardsServiceProvider.boardsConfig
|
||||
.selectedBoard
|
||||
): Promise<void> {
|
||||
if (!board) {
|
||||
this.disabledMessage = nls.localize(
|
||||
'arduino/common/noBoardSelected',
|
||||
'No board selected'
|
||||
);
|
||||
return;
|
||||
}
|
||||
const fqbn = board.fqbn;
|
||||
if (!fqbn) {
|
||||
this.disabledMessage = nls.localize(
|
||||
'arduino/debug/noPlatformInstalledFor',
|
||||
"Platform is not installed for '{0}'",
|
||||
board.name
|
||||
);
|
||||
return;
|
||||
}
|
||||
const details = await this.boardService.getBoardDetails({ fqbn });
|
||||
if (!details) {
|
||||
this.disabledMessage = nls.localize(
|
||||
'arduino/debug/noPlatformInstalledFor',
|
||||
"Platform is not installed for '{0}'",
|
||||
board.name
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { debuggingSupported } = details;
|
||||
if (!debuggingSupported) {
|
||||
this.disabledMessage = nls.localize(
|
||||
'arduino/debug/debuggingNotSupported',
|
||||
"Debugging is not supported by '{0}'",
|
||||
board.name
|
||||
);
|
||||
} else {
|
||||
this.disabledMessage = undefined;
|
||||
}
|
||||
): Promise<string> {
|
||||
const debugFqbn = await isDebugEnabled(
|
||||
board,
|
||||
(fqbn) => this.boardService.getBoardDetails({ fqbn }),
|
||||
(fqbn) => this.boardsDataStore.getData(fqbn),
|
||||
(fqbn) => this.boardsDataStore.appendConfigToFqbn(fqbn),
|
||||
(params) => this.boardService.checkDebugEnabled(params)
|
||||
);
|
||||
return debugFqbn;
|
||||
}
|
||||
|
||||
private async startDebug(
|
||||
board: BoardIdentifier | undefined = this.boardsServiceProvider.boardsConfig
|
||||
.selectedBoard
|
||||
): Promise<void> {
|
||||
if (!board) {
|
||||
return;
|
||||
.selectedBoard,
|
||||
sketch:
|
||||
| CurrentSketch
|
||||
| undefined = this.sketchServiceClient.tryGetCurrentSketch()
|
||||
): Promise<StartDebugResult> {
|
||||
if (!CurrentSketch.isValid(sketch)) {
|
||||
return false;
|
||||
}
|
||||
const { name, fqbn } = board;
|
||||
if (!fqbn) {
|
||||
return;
|
||||
const params = await this.createStartDebugParams(board);
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
await this.hostedPluginSupport.didStart;
|
||||
const [sketch, executables] = await Promise.all([
|
||||
this.sketchServiceClient.currentSketch(),
|
||||
this.executableService.list(),
|
||||
]);
|
||||
if (!CurrentSketch.isValid(sketch)) {
|
||||
return;
|
||||
}
|
||||
const ideTempFolderUri = await this.sketchesService.getIdeTempFolderUri(
|
||||
sketch
|
||||
);
|
||||
const [cliPath, sketchPath, configPath] = await Promise.all([
|
||||
this.fileService.fsPath(new URI(executables.cliUri)),
|
||||
this.fileService.fsPath(new URI(sketch.uri)),
|
||||
this.fileService.fsPath(new URI(ideTempFolderUri)),
|
||||
]);
|
||||
const config = {
|
||||
cliPath,
|
||||
board: {
|
||||
fqbn,
|
||||
name,
|
||||
},
|
||||
sketchPath,
|
||||
configPath,
|
||||
};
|
||||
try {
|
||||
await this.commandService.executeCommand('arduino.debug.start', config);
|
||||
const result = await this.debug(params);
|
||||
return Boolean(result);
|
||||
} catch (err) {
|
||||
if (await this.isSketchNotVerifiedError(err, sketch)) {
|
||||
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
|
||||
const answer = await this.messageService.error(
|
||||
nls.localize(
|
||||
'arduino/debug/sketchIsNotCompiled',
|
||||
"Sketch '{0}' must be verified before starting a debug session. Please verify the sketch and start debugging again. Do you want to verify the sketch now?",
|
||||
sketch.name
|
||||
),
|
||||
sketchIsNotCompiled(sketch.name),
|
||||
yes
|
||||
);
|
||||
if (answer === yes) {
|
||||
@@ -230,6 +241,16 @@ export class Debug extends SketchContribution {
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async debug(
|
||||
params: StartDebugParams
|
||||
): Promise<StartDebugResult | undefined> {
|
||||
return this.commandService.executeCommand<StartDebugResult>(
|
||||
'arduino.debug.start',
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
get compileForDebug(): boolean {
|
||||
@@ -237,7 +258,7 @@ export class Debug extends SketchContribution {
|
||||
return value === 'true';
|
||||
}
|
||||
|
||||
async toggleCompileForDebug(): Promise<void> {
|
||||
private toggleCompileForDebug(): void {
|
||||
const oldState = this.compileForDebug;
|
||||
const newState = !oldState;
|
||||
window.localStorage.setItem(COMPILE_FOR_DEBUG_KEY, String(newState));
|
||||
@@ -246,7 +267,7 @@ export class Debug extends SketchContribution {
|
||||
|
||||
private async isSketchNotVerifiedError(
|
||||
err: unknown,
|
||||
sketch: Sketch
|
||||
sketch: SketchRef
|
||||
): Promise<boolean> {
|
||||
if (err instanceof Error) {
|
||||
try {
|
||||
@@ -260,6 +281,48 @@ export class Debug extends SketchContribution {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async createStartDebugParams(
|
||||
board: BoardIdentifier | undefined
|
||||
): Promise<StartDebugParams | undefined> {
|
||||
if (!board || !board.fqbn) {
|
||||
return undefined;
|
||||
}
|
||||
let debugFqbn: string | undefined = undefined;
|
||||
try {
|
||||
debugFqbn = await this.isDebugEnabled(board);
|
||||
} catch {}
|
||||
if (!debugFqbn) {
|
||||
return undefined;
|
||||
}
|
||||
const [sketch, executables, boardsData] = await Promise.all([
|
||||
this.sketchServiceClient.currentSketch(),
|
||||
this.executableService.list(),
|
||||
this.boardsDataStore.getData(board.fqbn),
|
||||
]);
|
||||
if (!CurrentSketch.isValid(sketch)) {
|
||||
return undefined;
|
||||
}
|
||||
const ideTempFolderUri = await this.sketchesService.getIdeTempFolderUri(
|
||||
sketch
|
||||
);
|
||||
const [cliPath, sketchPath, launchConfigsDirPath] = await Promise.all([
|
||||
this.fileService.fsPath(new URI(executables.cliUri)),
|
||||
this.fileService.fsPath(new URI(sketch.uri)),
|
||||
this.fileService.fsPath(new URI(ideTempFolderUri)),
|
||||
]);
|
||||
return {
|
||||
board: { fqbn: debugFqbn, name: board.name },
|
||||
cliPath,
|
||||
sketchPath,
|
||||
launchConfigsDirPath,
|
||||
programmer: boardsData.selectedProgrammer?.id,
|
||||
title: nls.localize(
|
||||
'arduino/debug/getDebugInfo',
|
||||
'Getting debug info...'
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
export namespace Debug {
|
||||
export namespace Commands {
|
||||
@@ -284,3 +347,89 @@ export namespace Debug {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-API)
|
||||
*/
|
||||
export async function isDebugEnabled(
|
||||
board: BoardIdentifier | undefined,
|
||||
getDetails: (fqbn: string) => MaybePromise<BoardDetails | undefined>,
|
||||
getData: (fqbn: string) => MaybePromise<BoardsDataStore.Data>,
|
||||
appendConfigToFqbn: (fqbn: string) => MaybePromise<string | undefined>,
|
||||
checkDebugEnabled: (params: CheckDebugEnabledParams) => MaybePromise<string>
|
||||
): Promise<string> {
|
||||
if (!board) {
|
||||
throw new Error(noBoardSelected);
|
||||
}
|
||||
const { fqbn } = board;
|
||||
if (!fqbn) {
|
||||
throw new Error(noPlatformInstalledFor(board.name));
|
||||
}
|
||||
const [details, data, fqbnWithConfig] = await Promise.all([
|
||||
getDetails(fqbn),
|
||||
getData(fqbn),
|
||||
appendConfigToFqbn(fqbn),
|
||||
]);
|
||||
if (!details) {
|
||||
throw new Error(noPlatformInstalledFor(board.name));
|
||||
}
|
||||
if (!fqbnWithConfig) {
|
||||
throw new Error(
|
||||
`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,
|
||||
};
|
||||
try {
|
||||
const debugFqbn = await checkDebugEnabled(params);
|
||||
return debugFqbn;
|
||||
} catch (err) {
|
||||
throw new Error(debuggingNotSupported(board.name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-API)
|
||||
*/
|
||||
export function sketchIsNotCompiled(sketchName: string): string {
|
||||
return nls.localize(
|
||||
'arduino/debug/sketchIsNotCompiled',
|
||||
"Sketch '{0}' must be verified before starting a debug session. Please verify the sketch and start debugging again. Do you want to verify the sketch now?",
|
||||
sketchName
|
||||
);
|
||||
}
|
||||
/**
|
||||
* (non-API)
|
||||
*/
|
||||
export function noPlatformInstalledFor(boardName: string): string {
|
||||
return nls.localize(
|
||||
'arduino/debug/noPlatformInstalledFor',
|
||||
"Platform is not installed for '{0}'",
|
||||
boardName
|
||||
);
|
||||
}
|
||||
/**
|
||||
* (non-API)
|
||||
*/
|
||||
export function debuggingNotSupported(boardName: string): string {
|
||||
return nls.localize(
|
||||
'arduino/debug/debuggingNotSupported',
|
||||
"Debugging is not supported by '{0}'",
|
||||
boardName
|
||||
);
|
||||
}
|
||||
/**
|
||||
* (non-API)
|
||||
*/
|
||||
export function noProgrammerSelectedFor(boardName: string): string {
|
||||
return nls.localize(
|
||||
'arduino/debug/noProgrammerSelectedFor',
|
||||
"No programmer selected for '{0}'",
|
||||
boardName
|
||||
);
|
||||
}
|
||||
|
@@ -300,8 +300,8 @@ export class LibraryExamples extends Examples {
|
||||
this.notificationCenter.onLibraryDidUninstall(() => this.update());
|
||||
}
|
||||
|
||||
override async onReady(): Promise<void> {
|
||||
this.update(); // no `await`
|
||||
override onReady(): void {
|
||||
this.boardsServiceProvider.ready.then(() => this.update());
|
||||
}
|
||||
|
||||
protected override handleBoardChanged(board: Board | undefined): void {
|
||||
|
@@ -1,4 +1,3 @@
|
||||
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';
|
||||
@@ -15,13 +14,14 @@ export class Format
|
||||
@inject(Formatter)
|
||||
private readonly formatter: Formatter;
|
||||
|
||||
override onStart(): MaybePromise<void> {
|
||||
override onStart(): void {
|
||||
monaco.languages.registerDocumentRangeFormattingEditProvider(
|
||||
InoSelector,
|
||||
this
|
||||
);
|
||||
monaco.languages.registerDocumentFormattingEditProvider(InoSelector, this);
|
||||
}
|
||||
|
||||
async provideDocumentRangeFormattingEdits(
|
||||
model: monaco.editor.ITextModel,
|
||||
range: monaco.Range,
|
||||
|
@@ -2,7 +2,6 @@ import PQueue from 'p-queue';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { MenuModelRegistry, MenuPath } from '@theia/core/lib/common/menu';
|
||||
import {
|
||||
Disposable,
|
||||
@@ -22,28 +21,25 @@ import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
@injectable()
|
||||
export class IncludeLibrary extends SketchContribution {
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
private readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
private readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
@inject(EditorManager)
|
||||
protected override readonly editorManager: EditorManager;
|
||||
private readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
private readonly notificationCenter: NotificationCenter;
|
||||
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
|
||||
@inject(LibraryService)
|
||||
protected readonly libraryService: LibraryService;
|
||||
private readonly libraryService: LibraryService;
|
||||
|
||||
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
private readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
|
||||
private readonly toDispose = new DisposableCollection();
|
||||
|
||||
override onStart(): void {
|
||||
this.boardsServiceProvider.onBoardsConfigDidChange(() =>
|
||||
@@ -56,8 +52,8 @@ export class IncludeLibrary extends SketchContribution {
|
||||
this.notificationCenter.onDidReinitialize(() => this.updateMenuActions());
|
||||
}
|
||||
|
||||
override async onReady(): Promise<void> {
|
||||
this.updateMenuActions();
|
||||
override onReady(): void {
|
||||
this.boardsServiceProvider.ready.then(() => this.updateMenuActions());
|
||||
}
|
||||
|
||||
override registerMenus(registry: MenuModelRegistry): void {
|
||||
@@ -93,7 +89,7 @@ export class IncludeLibrary extends SketchContribution {
|
||||
});
|
||||
}
|
||||
|
||||
protected async updateMenuActions(): Promise<void> {
|
||||
private async updateMenuActions(): Promise<void> {
|
||||
return this.queue.add(async () => {
|
||||
this.toDispose.dispose();
|
||||
this.mainMenuManager.update();
|
||||
@@ -139,7 +135,7 @@ export class IncludeLibrary extends SketchContribution {
|
||||
});
|
||||
}
|
||||
|
||||
protected registerLibrary(
|
||||
private registerLibrary(
|
||||
libraryOrPlaceholder: LibraryPackage | string,
|
||||
menuPath: MenuPath
|
||||
): Disposable {
|
||||
@@ -172,7 +168,7 @@ export class IncludeLibrary extends SketchContribution {
|
||||
);
|
||||
}
|
||||
|
||||
protected async includeLibrary(library: LibraryPackage): Promise<void> {
|
||||
private async includeLibrary(library: LibraryPackage): Promise<void> {
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (!CurrentSketch.isValid(sketch)) {
|
||||
return;
|
||||
|
558
arduino-ide-extension/src/browser/contributions/ino-highlight.ts
Normal file
558
arduino-ide-extension/src/browser/contributions/ino-highlight.ts
Normal file
@@ -0,0 +1,558 @@
|
||||
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;
|
@@ -15,31 +15,88 @@ import {
|
||||
} from '../../common/protocol';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { HostedPluginEvents } from '../hosted-plugin-events';
|
||||
import { HostedPluginEvents } from '../hosted/hosted-plugin-events';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { SketchContribution, URI } from './contribution';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
|
||||
interface DaemonAddress {
|
||||
/**
|
||||
* The host where the Arduino CLI daemon is available.
|
||||
*/
|
||||
readonly hostname: string;
|
||||
/**
|
||||
* The port where the Arduino CLI daemon is listening.
|
||||
*/
|
||||
readonly port: number;
|
||||
/**
|
||||
* The [id](https://arduino.github.io/arduino-cli/latest/rpc/commands/#instance) of the initialized core Arduino client instance.
|
||||
*/
|
||||
readonly instance: number;
|
||||
}
|
||||
|
||||
interface StartLanguageServerParams {
|
||||
/**
|
||||
* Absolute filesystem path to the Arduino Language Server executable.
|
||||
*/
|
||||
readonly lsPath: string;
|
||||
/**
|
||||
* The hostname and the port for the gRPC channel connecting to the Arduino CLI daemon.
|
||||
* The `instance` number is for the initialized core Arduino client.
|
||||
*/
|
||||
readonly daemonAddress: DaemonAddress;
|
||||
/**
|
||||
* Absolute filesystem path to [`clangd`](https://clangd.llvm.org/).
|
||||
*/
|
||||
readonly clangdPath: string;
|
||||
/**
|
||||
* The board is relevant to start a specific "flavor" of the language.
|
||||
*/
|
||||
readonly board: { fqbn: string; name?: string };
|
||||
/**
|
||||
* `true` if the LS should generate the log files into the default location. The default location is the `cwd` of the process.
|
||||
* It's very often the same as the workspace root of the IDE, aka the sketch folder.
|
||||
* When it is a string, it is the absolute filesystem path to the folder to generate the log files.
|
||||
* If `string`, but the path is inaccessible, the log files will be generated into the default location.
|
||||
*/
|
||||
readonly log?: boolean | string;
|
||||
/**
|
||||
* Optional `env` for the language server process.
|
||||
*/
|
||||
readonly env?: NodeJS.ProcessEnv;
|
||||
/**
|
||||
* Additional flags for the Arduino Language server process.
|
||||
*/
|
||||
readonly flags?: readonly string[];
|
||||
/**
|
||||
* Set to `true`, to enable `Diagnostics`.
|
||||
*/
|
||||
readonly realTimeDiagnostics?: boolean;
|
||||
/**
|
||||
* If `true`, the logging is not forwarded to the _Output_ view via the language client.
|
||||
*/
|
||||
readonly silentOutput?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The FQBN the language server runs with or `undefined` if it could not start.
|
||||
*/
|
||||
type StartLanguageServerResult = string | undefined;
|
||||
|
||||
@injectable()
|
||||
export class InoLanguage extends SketchContribution {
|
||||
@inject(HostedPluginEvents)
|
||||
private readonly hostedPluginEvents: HostedPluginEvents;
|
||||
|
||||
@inject(ExecutableService)
|
||||
private readonly executableService: ExecutableService;
|
||||
|
||||
@inject(ArduinoDaemon)
|
||||
private readonly daemon: ArduinoDaemon;
|
||||
|
||||
@inject(BoardsService)
|
||||
private readonly boardsService: BoardsService;
|
||||
|
||||
@inject(BoardsServiceProvider)
|
||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
private readonly notificationCenter: NotificationCenter;
|
||||
|
||||
@inject(BoardsDataStore)
|
||||
private readonly boardDataStore: BoardsDataStore;
|
||||
|
||||
@@ -90,7 +147,7 @@ export class InoLanguage extends SketchContribution {
|
||||
this.notificationCenter.onPlatformDidInstall(() => forceRestart()),
|
||||
this.notificationCenter.onPlatformDidUninstall(() => forceRestart()),
|
||||
this.notificationCenter.onDidReinitialize(() => forceRestart()),
|
||||
this.boardDataStore.onChanged((dataChangePerFqbn) => {
|
||||
this.boardDataStore.onDidChange((event) => {
|
||||
if (this.languageServerFqbn) {
|
||||
const sanitizedFqbn = sanitizeFqbn(this.languageServerFqbn);
|
||||
if (!sanitizeFqbn) {
|
||||
@@ -98,13 +155,13 @@ export class InoLanguage extends SketchContribution {
|
||||
`Failed to sanitize the FQBN of the running language server. FQBN with the board settings was: ${this.languageServerFqbn}`
|
||||
);
|
||||
}
|
||||
const matchingFqbn = dataChangePerFqbn.find(
|
||||
(fqbn) => sanitizedFqbn === fqbn
|
||||
const matchingChange = event.changes.find(
|
||||
(change) => change.fqbn === sanitizedFqbn
|
||||
);
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
if (
|
||||
matchingFqbn &&
|
||||
boardsConfig.selectedBoard?.fqbn === matchingFqbn
|
||||
matchingChange &&
|
||||
boardsConfig.selectedBoard?.fqbn === matchingChange.fqbn
|
||||
) {
|
||||
start(boardsConfig.selectedBoard);
|
||||
}
|
||||
@@ -129,6 +186,10 @@ export class InoLanguage extends SketchContribution {
|
||||
if (!port) {
|
||||
return;
|
||||
}
|
||||
const portNumber = Number.parseInt(port, 10); // TODO: IDE2 APIs should provide a number and not string
|
||||
if (Number.isNaN(portNumber)) {
|
||||
return;
|
||||
}
|
||||
const release = await this.languageServerStartMutex.acquire();
|
||||
const toDisposeOnRelease = new DisposableCollection();
|
||||
try {
|
||||
@@ -197,22 +258,22 @@ export class InoLanguage extends SketchContribution {
|
||||
);
|
||||
toDisposeOnRelease.push(Disposable.create(() => clearTimeout(timer)));
|
||||
}),
|
||||
this.commandService.executeCommand<string>(
|
||||
'arduino.languageserver.start',
|
||||
{
|
||||
lsPath,
|
||||
cliDaemonAddr: `localhost:${port}`,
|
||||
clangdPath,
|
||||
log: currentSketchPath ? currentSketchPath : log,
|
||||
cliDaemonInstance: '1',
|
||||
board: {
|
||||
fqbn: fqbnWithConfig,
|
||||
name: name ? `"${name}"` : undefined,
|
||||
},
|
||||
realTimeDiagnostics,
|
||||
silentOutput: true,
|
||||
}
|
||||
),
|
||||
this.start({
|
||||
lsPath,
|
||||
daemonAddress: {
|
||||
hostname: 'localhost',
|
||||
port: portNumber,
|
||||
instance: 1, // TODO: get it from the backend
|
||||
},
|
||||
clangdPath,
|
||||
log: currentSketchPath ? currentSketchPath : log,
|
||||
board: {
|
||||
fqbn: fqbnWithConfig,
|
||||
name,
|
||||
},
|
||||
realTimeDiagnostics,
|
||||
silentOutput: true,
|
||||
}),
|
||||
]);
|
||||
} catch (e) {
|
||||
console.log(`Failed to start language server. Original FQBN: ${fqbn}`, e);
|
||||
@@ -222,4 +283,13 @@ export class InoLanguage extends SketchContribution {
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
private async start(
|
||||
params: StartLanguageServerParams
|
||||
): Promise<StartLanguageServerResult | undefined> {
|
||||
return this.commandService.executeCommand<StartLanguageServerResult>(
|
||||
'arduino.languageserver.start',
|
||||
params
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -19,16 +19,18 @@ export class SelectedBoard extends Contribution {
|
||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
|
||||
override onStart(): void {
|
||||
this.boardsServiceProvider.onBoardListDidChange(() =>
|
||||
this.update(this.boardsServiceProvider.boardList)
|
||||
this.boardsServiceProvider.onBoardListDidChange((boardList) =>
|
||||
this.update(boardList)
|
||||
);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this.update(this.boardsServiceProvider.boardList);
|
||||
this.boardsServiceProvider.ready.then(() => this.update());
|
||||
}
|
||||
|
||||
private update(boardList: BoardList): void {
|
||||
private update(
|
||||
boardList: BoardList = this.boardsServiceProvider.boardList
|
||||
): void {
|
||||
const { selectedBoard, selectedPort } = boardList.boardsConfig;
|
||||
this.statusBar.setElement('arduino-selected-board', {
|
||||
alignment: StatusBarAlignment.RIGHT,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
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 '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import { HostedPluginSupport } from '../hosted/hosted-plugin-support';
|
||||
import type { ArduinoState } from 'vscode-arduino-api';
|
||||
import {
|
||||
BoardsService,
|
||||
@@ -21,7 +21,10 @@ import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { SketchContribution } from './contribution';
|
||||
|
||||
interface UpdateStateParams<T extends ArduinoState> {
|
||||
/**
|
||||
* (non-API) exported for tests
|
||||
*/
|
||||
export interface UpdateStateParams<T extends ArduinoState = ArduinoState> {
|
||||
readonly key: keyof T;
|
||||
readonly value: T[keyof T];
|
||||
}
|
||||
@@ -65,10 +68,13 @@ export class UpdateArduinoState extends SketchContribution {
|
||||
this.updateCompileSummary(args[0]);
|
||||
}
|
||||
}),
|
||||
this.boardsDataStore.onChanged((fqbn) => {
|
||||
this.boardsDataStore.onDidChange((event) => {
|
||||
const selectedFqbn =
|
||||
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn;
|
||||
if (selectedFqbn && fqbn.includes(selectedFqbn)) {
|
||||
if (
|
||||
selectedFqbn &&
|
||||
event.changes.find((change) => change.fqbn === selectedFqbn)
|
||||
) {
|
||||
this.updateBoardDetails(selectedFqbn);
|
||||
}
|
||||
}),
|
||||
@@ -76,7 +82,9 @@ export class UpdateArduinoState extends SketchContribution {
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this.updateBoardsConfig(this.boardsServiceProvider.boardsConfig); // TODO: verify!
|
||||
this.boardsServiceProvider.ready.then(() => {
|
||||
this.updateBoardsConfig(this.boardsServiceProvider.boardsConfig);
|
||||
});
|
||||
this.updateSketchPath(this.sketchServiceClient.tryGetCurrentSketch());
|
||||
this.updateUserDirPath(this.configService.tryGetSketchDirUri());
|
||||
this.updateDataDirPath(this.configService.tryGetDataDirUri());
|
||||
|
@@ -45,10 +45,7 @@ export namespace UploadFirmware {
|
||||
export namespace Commands {
|
||||
export const OPEN: Command = {
|
||||
id: 'arduino-upload-firmware-open',
|
||||
label: nls.localize(
|
||||
'arduino/firmware/updater',
|
||||
'Firmware Updater'
|
||||
),
|
||||
label: nls.localize('arduino/firmware/updater', 'Firmware Updater'),
|
||||
category: 'Arduino',
|
||||
};
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { BoardUserField, CoreError } from '../../common/protocol';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { MenuModelRegistry, Contribution } from './contribution';
|
||||
import { Contribution, MenuModelRegistry } from './contribution';
|
||||
import { UploadSketch } from './upload-sketch';
|
||||
|
||||
@injectable()
|
||||
@@ -21,12 +21,11 @@ export class UserFields extends Contribution {
|
||||
|
||||
protected override init(): void {
|
||||
super.init();
|
||||
this.boardsServiceProvider.onBoardsConfigDidChange(async () => {
|
||||
const userFields =
|
||||
await this.boardsServiceProvider.selectedBoardUserFields();
|
||||
this.boardRequiresUserFields = userFields.length > 0;
|
||||
this.menuManager.update();
|
||||
});
|
||||
this.boardsServiceProvider.onBoardsConfigDidChange(() => this.refresh());
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this.boardsServiceProvider.ready.then(() => this.refresh());
|
||||
}
|
||||
|
||||
override registerMenus(registry: MenuModelRegistry): void {
|
||||
@@ -37,6 +36,13 @@ export class UserFields extends Contribution {
|
||||
});
|
||||
}
|
||||
|
||||
private async refresh(): Promise<void> {
|
||||
const userFields =
|
||||
await this.boardsServiceProvider.selectedBoardUserFields();
|
||||
this.boardRequiresUserFields = userFields.length > 0;
|
||||
this.menuManager.update();
|
||||
}
|
||||
|
||||
private selectedFqbnAddress(): string | undefined {
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
const fqbn = boardsConfig.selectedBoard?.fqbn;
|
||||
|
@@ -179,7 +179,8 @@ export class CreateApi {
|
||||
);
|
||||
})
|
||||
.catch((reason) => {
|
||||
if (reason?.status === 404) return [] as Create.Resource[];
|
||||
if (reason?.status === 404)
|
||||
return [] as Create.Resource[]; // TODO: must not swallow 404
|
||||
else throw reason;
|
||||
});
|
||||
}
|
||||
@@ -486,18 +487,12 @@ export class CreateApi {
|
||||
await this.run(url, init, ResponseResultProvider.NOOP);
|
||||
}
|
||||
|
||||
private fetchCounter = 0;
|
||||
private async run<T>(
|
||||
requestInfo: URL,
|
||||
init: RequestInit | undefined,
|
||||
resultProvider: ResponseResultProvider = ResponseResultProvider.JSON
|
||||
): Promise<T> {
|
||||
const fetchCount = `[${++this.fetchCounter}]`;
|
||||
const fetchStart = performance.now();
|
||||
const method = init?.method ? `${init.method}: ` : '';
|
||||
const url = requestInfo.toString();
|
||||
const response = await fetch(requestInfo.toString(), init);
|
||||
const fetchEnd = performance.now();
|
||||
if (!response.ok) {
|
||||
let details: string | undefined = undefined;
|
||||
try {
|
||||
@@ -508,18 +503,7 @@ export class CreateApi {
|
||||
const { statusText, status } = response;
|
||||
throw new CreateError(statusText, status, details);
|
||||
}
|
||||
const parseStart = performance.now();
|
||||
const result = await resultProvider(response);
|
||||
const parseEnd = performance.now();
|
||||
console.debug(
|
||||
`HTTP ${fetchCount} ${method}${url} [fetch: ${(
|
||||
fetchEnd - fetchStart
|
||||
).toFixed(2)} ms, parse: ${(parseEnd - parseStart).toFixed(
|
||||
2
|
||||
)} ms] body: ${
|
||||
typeof result === 'string' ? result : JSON.stringify(result)
|
||||
}`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@@ -82,6 +82,13 @@ export function isNotFound(err: unknown): err is NotFoundError {
|
||||
return isErrorWithStatusOf(err, 404);
|
||||
}
|
||||
|
||||
export type UnprocessableContentError = CreateError & { status: 422 };
|
||||
export function isUnprocessableContent(
|
||||
err: unknown
|
||||
): err is UnprocessableContentError {
|
||||
return isErrorWithStatusOf(err, 422);
|
||||
}
|
||||
|
||||
function isErrorWithStatusOf(
|
||||
err: unknown,
|
||||
status: number
|
||||
|
@@ -89,7 +89,8 @@
|
||||
"scope": [
|
||||
"storage",
|
||||
"support",
|
||||
"string.quoted.single.c"
|
||||
"string.quoted.single.c",
|
||||
"macro"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#0ca1a6"
|
||||
@@ -101,7 +102,8 @@
|
||||
"meta.function.c",
|
||||
"entity.name.function",
|
||||
"meta.function-call.c",
|
||||
"variable.other"
|
||||
"variable.other",
|
||||
"label"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#F39C12"
|
||||
@@ -146,7 +148,8 @@
|
||||
"name": "meta keywords",
|
||||
"scope": [
|
||||
"keyword.control",
|
||||
"meta.preprocessor.c"
|
||||
"meta.preprocessor.c",
|
||||
"event"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#C586C0"
|
||||
|
@@ -89,7 +89,8 @@
|
||||
"scope": [
|
||||
"storage",
|
||||
"support",
|
||||
"string.quoted.single.c"
|
||||
"string.quoted.single.c",
|
||||
"macro"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#00979D"
|
||||
@@ -101,7 +102,8 @@
|
||||
"meta.function.c",
|
||||
"entity.name.function",
|
||||
"meta.function-call.c",
|
||||
"variable.other"
|
||||
"variable.other",
|
||||
"label"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#D35400"
|
||||
@@ -146,7 +148,8 @@
|
||||
"name": "meta keywords",
|
||||
"scope": [
|
||||
"keyword.control",
|
||||
"meta.preprocessor.c"
|
||||
"meta.preprocessor.c",
|
||||
"event"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#728E00"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { DisposableCollection, Emitter, Event } from '@theia/core';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { HostedPluginSupport } from './theia/plugin-ext/hosted-plugin';
|
||||
import { HostedPluginSupport } from './hosted-plugin-support';
|
||||
|
||||
/**
|
||||
* Frontend contribution to watch VS Code extension start/stop events from Theia.
|
@@ -0,0 +1,14 @@
|
||||
import type { Event } from '@theia/core/lib/common/event';
|
||||
|
||||
/*
|
||||
This implementation hides the default HostedPluginSupport implementation from Theia to be able to test it.
|
||||
Otherwise, the default implementation fails at require time due to the `import.meta` in the Theia plugin worker code.
|
||||
https://github.com/eclipse-theia/theia/blob/964f69ca3b3a5fb87ffa0177fb300b74ba0ca39f/packages/plugin-ext/src/hosted/browser/plugin-worker.ts#L30-L32
|
||||
*/
|
||||
|
||||
export const HostedPluginSupport = Symbol('HostedPluginSupport');
|
||||
export interface HostedPluginSupport {
|
||||
readonly didStart: Promise<void>;
|
||||
readonly onDidLoad: Event<void>;
|
||||
readonly onDidCloseConnection: Event<void>;
|
||||
}
|
@@ -67,6 +67,7 @@ export class SketchesServiceClientImpl
|
||||
);
|
||||
|
||||
private _currentSketch: CurrentSketch | undefined;
|
||||
private _currentIdeTempFolderUri: URI | undefined;
|
||||
private currentSketchLoaded = new Deferred<CurrentSketch>();
|
||||
|
||||
onStart(): void {
|
||||
@@ -74,7 +75,10 @@ export class SketchesServiceClientImpl
|
||||
this.watchSketchbookDir(sketchDirUri);
|
||||
const refreshCurrentSketch = async () => {
|
||||
const currentSketch = await this.loadCurrentSketch();
|
||||
this.useCurrentSketch(currentSketch);
|
||||
const ideTempFolderUri = await this.getIdeTempFolderUriForSketch(
|
||||
currentSketch
|
||||
);
|
||||
this.useCurrentSketch(currentSketch, ideTempFolderUri);
|
||||
};
|
||||
this.toDispose.push(
|
||||
this.configService.onDidChangeSketchDirUri((sketchDirUri) => {
|
||||
@@ -141,7 +145,10 @@ export class SketchesServiceClientImpl
|
||||
}
|
||||
|
||||
if (!Sketch.sameAs(this._currentSketch, reloadedSketch)) {
|
||||
this.useCurrentSketch(reloadedSketch, true);
|
||||
const ideTempFolderUri = await this.getIdeTempFolderUriForSketch(
|
||||
reloadedSketch
|
||||
);
|
||||
this.useCurrentSketch(reloadedSketch, ideTempFolderUri, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -179,11 +186,23 @@ export class SketchesServiceClientImpl
|
||||
]);
|
||||
}
|
||||
|
||||
private async getIdeTempFolderUriForSketch(
|
||||
sketch: CurrentSketch
|
||||
): Promise<URI | undefined> {
|
||||
if (CurrentSketch.isValid(sketch)) {
|
||||
const uri = await this.sketchesService.getIdeTempFolderUri(sketch);
|
||||
return new URI(uri);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private useCurrentSketch(
|
||||
currentSketch: CurrentSketch,
|
||||
ideTempFolderUri: URI | undefined,
|
||||
reassignPromise = false
|
||||
) {
|
||||
this._currentSketch = currentSketch;
|
||||
this._currentIdeTempFolderUri = ideTempFolderUri;
|
||||
if (reassignPromise) {
|
||||
this.currentSketchLoaded = new Deferred();
|
||||
}
|
||||
@@ -273,6 +292,14 @@ export class SketchesServiceClientImpl
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this._currentIdeTempFolderUri &&
|
||||
this._currentIdeTempFolderUri.resolve('launch.json').toString() ===
|
||||
toCheck.toString()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isCloudSketch = toCheck
|
||||
.toString()
|
||||
.includes(`${REMOTE_SKETCHBOOK_FOLDER}/${ARDUINO_CLOUD_FOLDER}`);
|
||||
|
@@ -38,7 +38,9 @@
|
||||
.arduino-select__control.arduino-select__control--menu-is-open {
|
||||
border: 1px solid !important;
|
||||
border-color: var(--theia-focusBorder) !important;
|
||||
border-bottom-color: var(--theia-sideBar-background) !important; /* if the bottom border color has the same color as the background of the control, we make the border "invisible" */
|
||||
border-bottom-color: var(
|
||||
--theia-sideBar-background
|
||||
) !important; /* if the bottom border color has the same color as the background of the control, we make the border "invisible" */
|
||||
}
|
||||
|
||||
.arduino-select__value-container .arduino-select__single-value {
|
||||
|
@@ -1,324 +1,324 @@
|
||||
#select-board-dialog-container > .dialogBlock {
|
||||
width: 640px;
|
||||
height: 500px;
|
||||
width: 640px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
div#select-board-dialog {
|
||||
margin: 5px;
|
||||
height: 100%;
|
||||
margin: 5px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div#select-board-dialog .selectBoardContainer {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.select-board-dialog .head {
|
||||
margin: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.dialogContent.select-board-dialog {
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.dialogContent.select-board-dialog > div.head .title {
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.02em;
|
||||
font-size: 1.2em;
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
margin-bottom: 10px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.02em;
|
||||
font-size: 1.2em;
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
div#select-board-dialog .selectBoardContainer .list .item.selected {
|
||||
background: var(--theia-secondaryButton-hoverBackground);
|
||||
background: var(--theia-secondaryButton-hoverBackground);
|
||||
}
|
||||
|
||||
div#select-board-dialog .selectBoardContainer .list .item.selected i {
|
||||
color: var(--theia-arduino-branding-primary);
|
||||
color: var(--theia-arduino-branding-primary);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .search,
|
||||
#select-board-dialog .selectBoardContainer .search input,
|
||||
#select-board-dialog .selectBoardContainer .list,
|
||||
#select-board-dialog .selectBoardContainer .list {
|
||||
background: var(--theia-editor-background);
|
||||
background: var(--theia-editor-background);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .search input {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 37px;
|
||||
padding: 10px 5px 10px 10px;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
display: flex;
|
||||
color: var(--theia-input-foreground);
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 37px;
|
||||
padding: 10px 5px 10px 10px;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
display: flex;
|
||||
color: var(--theia-input-foreground);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .search input:focus {
|
||||
box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .container .content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .left.container .content {
|
||||
margin: 0 5px 0 0;
|
||||
margin: 0 5px 0 0;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .right.container .content {
|
||||
margin: 0 0 0 5px;
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .container .content .title {
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
padding: 0px 0px 10px 0px;
|
||||
text-transform: uppercase;
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
padding: 0px 0px 10px 0px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .container .content .footer {
|
||||
padding: 10px 5px 10px 0px;
|
||||
padding: 10px 5px 10px 0px;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .container .content .loading {
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
padding: 10px 5px 10px 10px;
|
||||
text-transform: uppercase;
|
||||
/* The max, min-height comes from `.list` 200px + 47px top padding - 2 * 10px top padding */
|
||||
max-height: 227px;
|
||||
min-height: 227px;
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
padding: 10px 5px 10px 10px;
|
||||
text-transform: uppercase;
|
||||
/* The max, min-height comes from `.list` 200px + 47px top padding - 2 * 10px top padding */
|
||||
max-height: 227px;
|
||||
min-height: 227px;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .list .item {
|
||||
padding: 10px 5px 10px 10px;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
flex: 1 0;
|
||||
padding: 10px 5px 10px 10px;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
flex: 1 0;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .list .item .selected-icon {
|
||||
margin-left: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .list .item .details {
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
opacity: var(--theia-mod-disabled-opacity);
|
||||
width: 155px; /* used heuristics for the calculation */
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
opacity: var(--theia-mod-disabled-opacity);
|
||||
width: 155px; /* used heuristics for the calculation */
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .list .item.missing {
|
||||
opacity: var(--theia-mod-disabled-opacity);
|
||||
opacity: var(--theia-mod-disabled-opacity);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .list .item:hover {
|
||||
background: var(--theia-secondaryButton-hoverBackground);
|
||||
background: var(--theia-secondaryButton-hoverBackground);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .list .label {
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .ports.list {
|
||||
margin: 47px 0px 0px 0px; /* 47 is 37 as input height for the `Boards`, plus 10 margin bottom. */
|
||||
margin: 47px 0px 0px 0px; /* 47 is 37 as input height for the `Boards`, plus 10 margin bottom. */
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .search {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item-container {
|
||||
align-items: center;
|
||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||
border-radius: 1px;
|
||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||
border: 1px solid var(--theia-arduino-toolbar-dropdown-border);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
height: var(--arduino-button-height);
|
||||
margin: 0 4px;
|
||||
overflow: hidden;
|
||||
padding: 0 10px;
|
||||
width: 210px;
|
||||
align-items: center;
|
||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||
border-radius: 1px;
|
||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||
border: 1px solid var(--theia-arduino-toolbar-dropdown-border);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
height: var(--arduino-button-height);
|
||||
margin: 0 4px;
|
||||
overflow: hidden;
|
||||
padding: 0 10px;
|
||||
width: 210px;
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item--protocol,
|
||||
.arduino-boards-dropdown-item--protocol {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item--protocol,
|
||||
.arduino-boards-dropdown-item--protocol {
|
||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item-container .arduino-boards-toolbar-item {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item--label {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item--label-connected {
|
||||
font-family: 'Open Sans Bold';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
font-family: "Open Sans Bold";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item-container .caret {
|
||||
width: 10px;
|
||||
margin-right: 5px;
|
||||
width: 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-list {
|
||||
margin: -1px;
|
||||
z-index: 1;
|
||||
border: 1px solid var(--theia-arduino-toolbar-dropdown-border);
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
margin: -1px;
|
||||
z-index: 1;
|
||||
border: 1px solid var(--theia-arduino-toolbar-dropdown-border);
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-list:focus {
|
||||
border: 1px solid var(--theia-arduino-toolbar-dropdown-borderActive);
|
||||
border: 1px solid var(--theia-arduino-toolbar-dropdown-borderActive);
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-list--items-container {
|
||||
overflow: auto;
|
||||
max-height: 404px;
|
||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||
overflow: auto;
|
||||
max-height: 404px;
|
||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-list--items-container::-webkit-scrollbar {
|
||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item {
|
||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||
cursor: default;
|
||||
display: flex;
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||
cursor: default;
|
||||
display: flex;
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item--board-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item--label {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Redefine default codicon size https://github.com/microsoft/vscode/commit/38cd0a377b7abef34fb07fe770fc633e68819ba6 */
|
||||
.arduino-boards-dropdown-item .codicon[class*='codicon-'] {
|
||||
font-size: 14px;
|
||||
.arduino-boards-dropdown-item .codicon[class*="codicon-"] {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item .p-TabBar-toolbar {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
flex-direction: column;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item .p-TabBar-toolbar .item {
|
||||
margin: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item .p-TabBar-toolbar .item .action-label {
|
||||
padding: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item--board-label {
|
||||
font-size: 14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item .arduino-boards-dropdown-item--protocol {
|
||||
margin-right: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item--port-label {
|
||||
font-size: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item:hover {
|
||||
background: var(--theia-arduino-toolbar-dropdown-option-backgroundHover);
|
||||
background: var(--theia-arduino-toolbar-dropdown-option-backgroundHover);
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item--selected,
|
||||
.arduino-boards-dropdown-item--selected:hover {
|
||||
background: var(--theia-arduino-toolbar-dropdown-option-backgroundSelected);
|
||||
border: 1px solid var(--theia-arduino-toolbar-dropdown-option-backgroundSelected);
|
||||
background: var(--theia-arduino-toolbar-dropdown-option-backgroundSelected);
|
||||
border: 1px solid
|
||||
var(--theia-arduino-toolbar-dropdown-option-backgroundSelected);
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item--selected
|
||||
.arduino-boards-dropdown-item--port-label {
|
||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||
.arduino-boards-dropdown-item--port-label {
|
||||
color: var(--theia-arduino-toolbar-dropdown-label);
|
||||
}
|
||||
|
||||
.arduino-boards-dropdown-item--selected .fa {
|
||||
color: var(--theia-arduino-toolbar-dropdown-iconSelected);
|
||||
color: var(--theia-arduino-toolbar-dropdown-iconSelected);
|
||||
}
|
||||
|
||||
.arduino-board-dropdown-footer {
|
||||
color: var(--theia-secondaryButton-foreground);
|
||||
border-top: 1px solid var(--theia-dropdown-border);
|
||||
color: var(--theia-secondaryButton-foreground);
|
||||
border-top: 1px solid var(--theia-dropdown-border);
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 400px) {
|
||||
div.dialogContent.select-board-dialog > div.head {
|
||||
display: none;
|
||||
}
|
||||
div.dialogContent.select-board-dialog > div.head {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .container .content .title {
|
||||
display: none;
|
||||
}
|
||||
#select-board-dialog .selectBoardContainer .container .content .title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#select-board-dialog .no-result {
|
||||
text-transform: uppercase;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
padding: 10px 5px;
|
||||
overflow-wrap: break-word;
|
||||
text-transform: uppercase;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
padding: 10px 5px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
@@ -12,4 +12,4 @@
|
||||
|
||||
.p-MenuBar-item.p-mod-active {
|
||||
color: var(--theia-menubar-selectionForeground);
|
||||
}
|
||||
}
|
||||
|
@@ -1,76 +1,75 @@
|
||||
#certificate-uploader-dialog-container > .dialogBlock {
|
||||
width: 600px;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.certificate-uploader-dialog .theia-select {
|
||||
border: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.certificate-uploader-dialog .arduino-select__control {
|
||||
height: 31px;
|
||||
background: var(--theia-dropdown-background) !important;
|
||||
height: 31px;
|
||||
background: var(--theia-dropdown-background) !important;
|
||||
}
|
||||
|
||||
.certificate-uploader-dialog .dialogRow > button{
|
||||
margin-right: 3px;
|
||||
.certificate-uploader-dialog .dialogRow > button {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.certificate-uploader-dialog .certificate-list {
|
||||
border: 1px solid var(--theia-editorWidget-border);
|
||||
border-radius: 2px;;
|
||||
color: var(--theia-editor-foreground);
|
||||
background-color: var(--theia-editor-background);
|
||||
overflow: auto;
|
||||
height: 120px;
|
||||
flex: 1;
|
||||
border: 1px solid var(--theia-editorWidget-border);
|
||||
border-radius: 2px;
|
||||
color: var(--theia-editor-foreground);
|
||||
background-color: var(--theia-editor-background);
|
||||
overflow: auto;
|
||||
height: 120px;
|
||||
flex: 1;
|
||||
}
|
||||
.certificate-uploader-dialog .certificate-list .certificate-row {
|
||||
display: flex;
|
||||
padding: 6px 10px 5px 10px
|
||||
display: flex;
|
||||
padding: 6px 10px 5px 10px;
|
||||
}
|
||||
.certificate-uploader-dialog .certificate-list .certificate-row:hover {
|
||||
background-color: var(--theia-list-activeSelectionBackground);
|
||||
background-color: var(--theia-list-activeSelectionBackground);
|
||||
}
|
||||
|
||||
.certificate-uploader-dialog .upload-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
.certificate-uploader-dialog .success {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #1DA086;
|
||||
.certificate-uploader-dialog .success {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #1da086;
|
||||
}
|
||||
|
||||
.certificate-uploader-dialog .warn {
|
||||
color: #C11F09;
|
||||
.certificate-uploader-dialog .warn {
|
||||
color: #c11f09;
|
||||
}
|
||||
|
||||
.certificate-uploader-dialog .status-icon {
|
||||
margin-right: 10px;
|
||||
.certificate-uploader-dialog .status-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.certificate-uploader-dialog .add-cert-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.certificate-uploader-dialog .add-cert-btn .caret {
|
||||
margin-left: 6px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.certificate-add {
|
||||
padding: 16px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--theia-editorWidget-border);
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
background-color: var(--theia-editorWidget-background);
|
||||
padding: 16px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--theia-editorWidget-border);
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
background-color: var(--theia-editorWidget-background);
|
||||
}
|
||||
|
||||
.certificate-add input {
|
||||
margin-top: 12px;
|
||||
padding: 0 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
margin-top: 12px;
|
||||
padding: 0 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
@@ -23,8 +23,7 @@
|
||||
-webkit-mask-size: 100%;
|
||||
}
|
||||
|
||||
.p-mod-current
|
||||
.cloud-sketchbook-tree-icon {
|
||||
.p-mod-current .cloud-sketchbook-tree-icon {
|
||||
background-color: var(--theia-foreground);
|
||||
-webkit-mask: url(../icons/arduino-cloud-filled.svg);
|
||||
-webkit-mask-position: center;
|
||||
@@ -33,49 +32,49 @@
|
||||
}
|
||||
|
||||
.sketchbook-trees-container
|
||||
.p-TabBar[data-orientation="horizontal"]
|
||||
> .p-TabBar-content {
|
||||
.p-TabBar[data-orientation="horizontal"]
|
||||
> .p-TabBar-content {
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid var(--theia-tree-indentGuidesStroke);
|
||||
}
|
||||
|
||||
.sketchbook-trees-container
|
||||
.p-Widget.p-TabBar.p-DockPanel-tabBar
|
||||
> ul
|
||||
> li.p-TabBar-tab
|
||||
> div.p-TabBar-tabLabel {
|
||||
.p-Widget.p-TabBar.p-DockPanel-tabBar
|
||||
> ul
|
||||
> li.p-TabBar-tab
|
||||
> div.p-TabBar-tabLabel {
|
||||
display: none;
|
||||
width: 0px;
|
||||
max-width: 0px;
|
||||
}
|
||||
|
||||
.sketchbook-trees-container
|
||||
.p-Widget.p-TabBar.p-DockPanel-tabBar
|
||||
> ul
|
||||
> li.p-TabBar-tab
|
||||
> div.p-TabBar-tabCloseIcon {
|
||||
.p-Widget.p-TabBar.p-DockPanel-tabBar
|
||||
> ul
|
||||
> li.p-TabBar-tab
|
||||
> div.p-TabBar-tabCloseIcon {
|
||||
display: none;
|
||||
width: 0px;
|
||||
max-width: 0px;
|
||||
}
|
||||
|
||||
.sketchbook-trees-container
|
||||
.p-TabBar[data-orientation="horizontal"]
|
||||
.p-TabBar-tab {
|
||||
.p-TabBar[data-orientation="horizontal"]
|
||||
.p-TabBar-tab {
|
||||
min-width: 55px;
|
||||
}
|
||||
|
||||
.sketchbook-trees-container
|
||||
.p-Widget.p-TabBar.p-DockPanel-tabBar
|
||||
> ul
|
||||
> li.p-TabBar-tab {
|
||||
.p-Widget.p-TabBar.p-DockPanel-tabBar
|
||||
> ul
|
||||
> li.p-TabBar-tab {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.sketchbook-trees-container
|
||||
.p-Widget.p-TabBar.p-DockPanel-tabBar
|
||||
> ul
|
||||
> li.p-TabBar-tab.p-mod-current {
|
||||
.p-Widget.p-TabBar.p-DockPanel-tabBar
|
||||
> ul
|
||||
> li.p-TabBar-tab.p-mod-current {
|
||||
border-bottom: 2px solid var(--theia-activityBar-activeBorder);
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.codicon-debug-alt:before {
|
||||
font-family: 'FontAwesome' !important;
|
||||
content: "\e905" !important
|
||||
}
|
||||
.codicon-debug-alt:before {
|
||||
font-family: "FontAwesome" !important;
|
||||
content: "\e905" !important;
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@
|
||||
total = padding + margin = 96px
|
||||
*/
|
||||
max-width: calc(100% - 96px) !important;
|
||||
|
||||
|
||||
min-width: 424px;
|
||||
max-height: 560px;
|
||||
padding: 0 var(--arduino-button-height);
|
||||
@@ -56,14 +56,23 @@
|
||||
}
|
||||
|
||||
.p-Widget.dialogOverlay .dialogControl .spinner,
|
||||
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow .spinner {
|
||||
.p-Widget.dialogOverlay
|
||||
.dialogBlock
|
||||
.dialogContent
|
||||
.dialogSection
|
||||
.dialogRow
|
||||
.spinner {
|
||||
background: var(--theia-icon-loading) center center no-repeat;
|
||||
animation: theia-spin 1.25s linear infinite;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow:first-child {
|
||||
.p-Widget.dialogOverlay
|
||||
.dialogBlock
|
||||
.dialogContent
|
||||
.dialogSection
|
||||
.dialogRow:first-child {
|
||||
margin-top: 0px;
|
||||
height: 32px;
|
||||
}
|
||||
@@ -78,7 +87,7 @@
|
||||
}
|
||||
|
||||
.fa.disabled {
|
||||
opacity: .4;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 560px) {
|
||||
|
@@ -1,7 +1,9 @@
|
||||
/* Show the dirty indicator on unclosable widgets. On hover, it should still show the dot instead of the X. */
|
||||
/* https://github.com/arduino/arduino-pro-ide/issues/380 */
|
||||
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.a-mod-uncloseable.theia-mod-dirty > .p-TabBar-tabCloseIcon:before {
|
||||
content: "\ea71";
|
||||
.p-TabBar.theia-app-centers
|
||||
.p-TabBar-tab.p-mod-closable.a-mod-uncloseable.theia-mod-dirty
|
||||
> .p-TabBar-tabCloseIcon:before {
|
||||
content: "\ea71";
|
||||
}
|
||||
|
||||
.monaco-list-row.show-file-icons.focused {
|
||||
|
@@ -1,31 +1,31 @@
|
||||
#firmware-uploader-dialog-container > .dialogBlock {
|
||||
width: 600px;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.firmware-uploader-dialog .theia-select {
|
||||
border: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.firmware-uploader-dialog .arduino-select__control {
|
||||
height: 31px;
|
||||
background: var(--theia-input-background) !important;
|
||||
height: 31px;
|
||||
background: var(--theia-input-background) !important;
|
||||
}
|
||||
|
||||
.firmware-uploader-dialog .dialogRow > button{
|
||||
margin-right: 3px;
|
||||
.firmware-uploader-dialog .dialogRow > button {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.firmware-uploader-dialog #firmware-select {
|
||||
flex: unset;
|
||||
flex: unset;
|
||||
}
|
||||
|
||||
.firmware-uploader-dialog .success {
|
||||
color: #1DA086;
|
||||
.firmware-uploader-dialog .success {
|
||||
color: #1da086;
|
||||
}
|
||||
|
||||
.firmware-uploader-dialog .warn {
|
||||
color: #C11F09;
|
||||
.firmware-uploader-dialog .warn {
|
||||
color: #c11f09;
|
||||
}
|
||||
|
||||
.firmware-uploader-dialog .status-icon {
|
||||
margin-right: 10px;
|
||||
.firmware-uploader-dialog .status-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
@@ -1,699 +1,698 @@
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('fonts/OpenSans-Regular-webfont.woff') format('woff');
|
||||
font-family: "Open Sans";
|
||||
src: url("fonts/OpenSans-Regular-webfont.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans Bold';
|
||||
src: url('fonts/OpenSans-Bold-webfont.woff') format('woff');
|
||||
font-family: "Open Sans Bold";
|
||||
src: url("fonts/OpenSans-Bold-webfont.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
src:
|
||||
url('fonts/FontAwesome.ttf?h959em') format('truetype'),
|
||||
url('fonts/FontAwesome.woff?h959em') format('woff'),
|
||||
url('fonts/FontAwesome.svg?h959em#FontAwesome') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-family: "FontAwesome";
|
||||
src: url("fonts/FontAwesome.ttf?h959em") format("truetype"),
|
||||
url("fonts/FontAwesome.woff?h959em") format("woff"),
|
||||
url("fonts/FontAwesome.svg?h959em#FontAwesome") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
.fa {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'FontAwesome' !important;
|
||||
speak: never;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: "FontAwesome" !important;
|
||||
speak: never;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.fa-arduino-verify:before {
|
||||
content: "\e90b";
|
||||
content: "\e90b";
|
||||
}
|
||||
.fa-arduino-upload:before {
|
||||
content: "\e90c";
|
||||
content: "\e90c";
|
||||
}
|
||||
.fa-arduino-monitor:before {
|
||||
content: "\e90d";
|
||||
content: "\e90d";
|
||||
}
|
||||
.fa-arduino-sketch-tabs-menu:before {
|
||||
content: "\e90e";
|
||||
content: "\e90e";
|
||||
}
|
||||
.fa-arduino-plotter:before {
|
||||
content: "\e90f";
|
||||
content: "\e90f";
|
||||
}
|
||||
.fa-fa-check:before {
|
||||
content: "\e90a";
|
||||
content: "\e90a";
|
||||
}
|
||||
.fa-arduino-technology-3dimensionscube:before {
|
||||
content: "\e906";
|
||||
content: "\e906";
|
||||
}
|
||||
.fa-arduino-technology-usb:before {
|
||||
content: "\e907";
|
||||
content: "\e907";
|
||||
}
|
||||
.fa-arduino-technology-connection:before {
|
||||
content: "\e908";
|
||||
content: "\e908";
|
||||
}
|
||||
.fa-arduino-technology-bluetooth:before {
|
||||
content: "\e909";
|
||||
content: "\e909";
|
||||
}
|
||||
.fa-arduino-debugger:before {
|
||||
content: "\e905";
|
||||
content: "\e905";
|
||||
}
|
||||
.fa-arduino-search:before {
|
||||
content: "\e901";
|
||||
content: "\e901";
|
||||
}
|
||||
.fa-arduino-boards:before {
|
||||
content: "\e902";
|
||||
content: "\e902";
|
||||
}
|
||||
.fa-arduino-library:before {
|
||||
content: "\e903";
|
||||
content: "\e903";
|
||||
}
|
||||
.fa-arduino-folder:before {
|
||||
content: "\e904";
|
||||
content: "\e904";
|
||||
}
|
||||
.fa-reload:before {
|
||||
content: "\e900";
|
||||
content: "\e900";
|
||||
}
|
||||
.fa-asterisk:before {
|
||||
content: "\f069";
|
||||
content: "\f069";
|
||||
}
|
||||
.fa-plus:before {
|
||||
content: "\f067";
|
||||
content: "\f067";
|
||||
}
|
||||
.fa-question:before {
|
||||
content: "\f128";
|
||||
content: "\f128";
|
||||
}
|
||||
.fa-minus:before {
|
||||
content: "\f068";
|
||||
content: "\f068";
|
||||
}
|
||||
.fa-music:before {
|
||||
content: "\f001";
|
||||
content: "\f001";
|
||||
}
|
||||
.fa-search:before {
|
||||
content: "\f002";
|
||||
content: "\f002";
|
||||
}
|
||||
.fa-envelope-o:before {
|
||||
content: "\f003";
|
||||
content: "\f003";
|
||||
}
|
||||
.fa-heart:before {
|
||||
content: "\f004";
|
||||
content: "\f004";
|
||||
}
|
||||
.fa-star:before {
|
||||
content: "\f005";
|
||||
content: "\f005";
|
||||
}
|
||||
.fa-star-o:before {
|
||||
content: "\f006";
|
||||
content: "\f006";
|
||||
}
|
||||
.fa-user:before {
|
||||
content: "\f007";
|
||||
content: "\f007";
|
||||
}
|
||||
.fa-film:before {
|
||||
content: "\f008";
|
||||
content: "\f008";
|
||||
}
|
||||
.fa-th-large:before {
|
||||
content: "\f009";
|
||||
content: "\f009";
|
||||
}
|
||||
.fa-th:before {
|
||||
content: "\f00a";
|
||||
content: "\f00a";
|
||||
}
|
||||
.fa-th-list:before {
|
||||
content: "\f00b";
|
||||
content: "\f00b";
|
||||
}
|
||||
.fa-close:before {
|
||||
content: "\f00d";
|
||||
content: "\f00d";
|
||||
}
|
||||
.fa-remove:before {
|
||||
content: "\f00d";
|
||||
content: "\f00d";
|
||||
}
|
||||
.fa-times:before {
|
||||
content: "\f00d";
|
||||
content: "\f00d";
|
||||
}
|
||||
.fa-search-plus:before {
|
||||
content: "\f00e";
|
||||
content: "\f00e";
|
||||
}
|
||||
.fa-search-minus:before {
|
||||
content: "\f010";
|
||||
content: "\f010";
|
||||
}
|
||||
.fa-power-off:before {
|
||||
content: "\f011";
|
||||
content: "\f011";
|
||||
}
|
||||
.fa-signal:before {
|
||||
content: "\f012";
|
||||
content: "\f012";
|
||||
}
|
||||
.fa-cog:before {
|
||||
content: "\f013";
|
||||
content: "\f013";
|
||||
}
|
||||
.fa-gear:before {
|
||||
content: "\f013";
|
||||
content: "\f013";
|
||||
}
|
||||
.fa-trash-o:before {
|
||||
content: "\f014";
|
||||
content: "\f014";
|
||||
}
|
||||
.fa-home:before {
|
||||
content: "\f015";
|
||||
content: "\f015";
|
||||
}
|
||||
.fa-file-o:before {
|
||||
content: "\f016";
|
||||
content: "\f016";
|
||||
}
|
||||
.fa-clock-o:before {
|
||||
content: "\f017";
|
||||
content: "\f017";
|
||||
}
|
||||
.fa-download:before {
|
||||
content: "\f019";
|
||||
content: "\f019";
|
||||
}
|
||||
.fa-arrow-circle-o-down:before {
|
||||
content: "\f01a";
|
||||
content: "\f01a";
|
||||
}
|
||||
.fa-arrow-circle-o-up:before {
|
||||
content: "\f01b";
|
||||
content: "\f01b";
|
||||
}
|
||||
.fa-inbox:before {
|
||||
content: "\f01c";
|
||||
content: "\f01c";
|
||||
}
|
||||
.fa-play-circle-o:before {
|
||||
content: "\f01d";
|
||||
content: "\f01d";
|
||||
}
|
||||
.fa-repeat:before {
|
||||
content: "\f01e";
|
||||
content: "\f01e";
|
||||
}
|
||||
.fa-rotate-right:before {
|
||||
content: "\f01e";
|
||||
content: "\f01e";
|
||||
}
|
||||
.fa-refresh:before {
|
||||
content: "\f021";
|
||||
content: "\f021";
|
||||
}
|
||||
.fa-list-alt:before {
|
||||
content: "\f022";
|
||||
content: "\f022";
|
||||
}
|
||||
.fa-lock:before {
|
||||
content: "\f023";
|
||||
content: "\f023";
|
||||
}
|
||||
.fa-volume-off:before {
|
||||
content: "\f026";
|
||||
content: "\f026";
|
||||
}
|
||||
.fa-volume-down:before {
|
||||
content: "\f027";
|
||||
content: "\f027";
|
||||
}
|
||||
.fa-volume-up:before {
|
||||
content: "\f028";
|
||||
content: "\f028";
|
||||
}
|
||||
.fa-qrcode:before {
|
||||
content: "\f029";
|
||||
content: "\f029";
|
||||
}
|
||||
.fa-tag:before {
|
||||
content: "\f02b";
|
||||
content: "\f02b";
|
||||
}
|
||||
.fa-tags:before {
|
||||
content: "\f02c";
|
||||
content: "\f02c";
|
||||
}
|
||||
.fa-book:before {
|
||||
content: "\f02d";
|
||||
content: "\f02d";
|
||||
}
|
||||
.fa-print:before {
|
||||
content: "\f02f";
|
||||
content: "\f02f";
|
||||
}
|
||||
.fa-text-height:before {
|
||||
content: "\f034";
|
||||
content: "\f034";
|
||||
}
|
||||
.fa-text-width:before {
|
||||
content: "\f035";
|
||||
content: "\f035";
|
||||
}
|
||||
.fa-align-left:before {
|
||||
content: "\f036";
|
||||
content: "\f036";
|
||||
}
|
||||
.fa-align-center:before {
|
||||
content: "\f037";
|
||||
content: "\f037";
|
||||
}
|
||||
.fa-align-right:before {
|
||||
content: "\f038";
|
||||
content: "\f038";
|
||||
}
|
||||
.fa-align-justify:before {
|
||||
content: "\f039";
|
||||
content: "\f039";
|
||||
}
|
||||
.fa-list:before {
|
||||
content: "\f03a";
|
||||
content: "\f03a";
|
||||
}
|
||||
.fa-dedent:before {
|
||||
content: "\f03b";
|
||||
content: "\f03b";
|
||||
}
|
||||
.fa-outdent:before {
|
||||
content: "\f03b";
|
||||
content: "\f03b";
|
||||
}
|
||||
.fa-indent:before {
|
||||
content: "\f03c";
|
||||
content: "\f03c";
|
||||
}
|
||||
.fa-pencil:before {
|
||||
content: "\f040";
|
||||
content: "\f040";
|
||||
}
|
||||
.fa-adjust:before {
|
||||
content: "\f042";
|
||||
content: "\f042";
|
||||
}
|
||||
.fa-edit:before {
|
||||
content: "\f044";
|
||||
content: "\f044";
|
||||
}
|
||||
.fa-pencil-square-o:before {
|
||||
content: "\f044";
|
||||
content: "\f044";
|
||||
}
|
||||
.fa-share-square-o:before {
|
||||
content: "\f045";
|
||||
content: "\f045";
|
||||
}
|
||||
.fa-check-square-o:before {
|
||||
content: "\f046";
|
||||
content: "\f046";
|
||||
}
|
||||
.fa-arrows:before {
|
||||
content: "\f047";
|
||||
content: "\f047";
|
||||
}
|
||||
.fa-step-backward:before {
|
||||
content: "\f048";
|
||||
content: "\f048";
|
||||
}
|
||||
.fa-fast-backward:before {
|
||||
content: "\f049";
|
||||
content: "\f049";
|
||||
}
|
||||
.fa-backward:before {
|
||||
content: "\f04a";
|
||||
content: "\f04a";
|
||||
}
|
||||
.fa-play:before {
|
||||
content: "\f04b";
|
||||
content: "\f04b";
|
||||
}
|
||||
.fa-pause:before {
|
||||
content: "\f04c";
|
||||
content: "\f04c";
|
||||
}
|
||||
.fa-stop:before {
|
||||
content: "\f04d";
|
||||
content: "\f04d";
|
||||
}
|
||||
.fa-forward:before {
|
||||
content: "\f04e";
|
||||
content: "\f04e";
|
||||
}
|
||||
.fa-fast-forward:before {
|
||||
content: "\f050";
|
||||
content: "\f050";
|
||||
}
|
||||
.fa-step-forward:before {
|
||||
content: "\f051";
|
||||
content: "\f051";
|
||||
}
|
||||
.fa-eject:before {
|
||||
content: "\f052";
|
||||
content: "\f052";
|
||||
}
|
||||
.fa-chevron-left:before {
|
||||
content: "\f053";
|
||||
content: "\f053";
|
||||
}
|
||||
.fa-chevron-right:before {
|
||||
content: "\f054";
|
||||
content: "\f054";
|
||||
}
|
||||
.fa-plus-circle:before {
|
||||
content: "\f055";
|
||||
content: "\f055";
|
||||
}
|
||||
.fa-minus-circle:before {
|
||||
content: "\f056";
|
||||
content: "\f056";
|
||||
}
|
||||
.fa-times-circle:before {
|
||||
content: "\f057";
|
||||
content: "\f057";
|
||||
}
|
||||
.fa-check-circle:before {
|
||||
content: "\f058";
|
||||
content: "\f058";
|
||||
}
|
||||
.fa-question-circle:before {
|
||||
content: "\f059";
|
||||
content: "\f059";
|
||||
}
|
||||
.fa-info-circle:before {
|
||||
content: "\f05a";
|
||||
content: "\f05a";
|
||||
}
|
||||
.fa-crosshairs:before {
|
||||
content: "\f05b";
|
||||
content: "\f05b";
|
||||
}
|
||||
.fa-times-circle-o:before {
|
||||
content: "\f05c";
|
||||
content: "\f05c";
|
||||
}
|
||||
.fa-check-circle-o:before {
|
||||
content: "\f05d";
|
||||
content: "\f05d";
|
||||
}
|
||||
.fa-ban:before {
|
||||
content: "\f05e";
|
||||
content: "\f05e";
|
||||
}
|
||||
.fa-arrow-left:before {
|
||||
content: "\f060";
|
||||
content: "\f060";
|
||||
}
|
||||
.fa-arrow-right:before {
|
||||
content: "\f061";
|
||||
content: "\f061";
|
||||
}
|
||||
.fa-arrow-up:before {
|
||||
content: "\f062";
|
||||
content: "\f062";
|
||||
}
|
||||
.fa-arrow-down:before {
|
||||
content: "\f063";
|
||||
content: "\f063";
|
||||
}
|
||||
.fa-mail-forward:before {
|
||||
content: "\f064";
|
||||
content: "\f064";
|
||||
}
|
||||
.fa-share:before {
|
||||
content: "\f064";
|
||||
content: "\f064";
|
||||
}
|
||||
.fa-expand:before {
|
||||
content: "\f065";
|
||||
content: "\f065";
|
||||
}
|
||||
.fa-compress:before {
|
||||
content: "\f066";
|
||||
content: "\f066";
|
||||
}
|
||||
.fa-exclamation-circle:before {
|
||||
content: "\f06a";
|
||||
content: "\f06a";
|
||||
}
|
||||
.fa-eye:before {
|
||||
content: "\f06e";
|
||||
content: "\f06e";
|
||||
}
|
||||
.fa-eye-slash:before {
|
||||
content: "\f070";
|
||||
content: "\f070";
|
||||
}
|
||||
.fa-exclamation-triangle:before {
|
||||
content: "\f071";
|
||||
content: "\f071";
|
||||
}
|
||||
.fa-warning:before {
|
||||
content: "\f071";
|
||||
content: "\f071";
|
||||
}
|
||||
.fa-calendar:before {
|
||||
content: "\f073";
|
||||
content: "\f073";
|
||||
}
|
||||
.fa-random:before {
|
||||
content: "\f074";
|
||||
content: "\f074";
|
||||
}
|
||||
.fa-comment:before {
|
||||
content: "\f075";
|
||||
content: "\f075";
|
||||
}
|
||||
.fa-chevron-up:before {
|
||||
content: "\f077";
|
||||
content: "\f077";
|
||||
}
|
||||
.fa-chevron-down:before {
|
||||
content: "\f078";
|
||||
content: "\f078";
|
||||
}
|
||||
.fa-retweet:before {
|
||||
content: "\f079";
|
||||
content: "\f079";
|
||||
}
|
||||
.fa-folder:before {
|
||||
content: "\f07b";
|
||||
content: "\f07b";
|
||||
}
|
||||
.fa-folder-open:before {
|
||||
content: "\f07c";
|
||||
content: "\f07c";
|
||||
}
|
||||
.fa-arrows-v:before {
|
||||
content: "\f07d";
|
||||
content: "\f07d";
|
||||
}
|
||||
.fa-arrows-h:before {
|
||||
content: "\f07e";
|
||||
content: "\f07e";
|
||||
}
|
||||
.fa-cogs:before {
|
||||
content: "\f085";
|
||||
content: "\f085";
|
||||
}
|
||||
.fa-gears:before {
|
||||
content: "\f085";
|
||||
content: "\f085";
|
||||
}
|
||||
.fa-star-half:before {
|
||||
content: "\f089";
|
||||
content: "\f089";
|
||||
}
|
||||
.fa-heart-o:before {
|
||||
content: "\f08a";
|
||||
content: "\f08a";
|
||||
}
|
||||
.fa-sign-out:before {
|
||||
content: "\f08b";
|
||||
content: "\f08b";
|
||||
}
|
||||
.fa-thumb-tack:before {
|
||||
content: "\f08d";
|
||||
content: "\f08d";
|
||||
}
|
||||
.fa-external-link:before {
|
||||
content: "\f08e";
|
||||
content: "\f08e";
|
||||
}
|
||||
.fa-sign-in:before {
|
||||
content: "\f090";
|
||||
content: "\f090";
|
||||
}
|
||||
.fa-upload:before {
|
||||
content: "\f093";
|
||||
content: "\f093";
|
||||
}
|
||||
.fa-square-o:before {
|
||||
content: "\f096";
|
||||
content: "\f096";
|
||||
}
|
||||
.fa-bookmark-o:before {
|
||||
content: "\f097";
|
||||
content: "\f097";
|
||||
}
|
||||
.fa-hdd-o:before {
|
||||
content: "\f0a0";
|
||||
content: "\f0a0";
|
||||
}
|
||||
.fa-bell-o:before {
|
||||
content: "\f0a2";
|
||||
content: "\f0a2";
|
||||
}
|
||||
.fa-certificate:before {
|
||||
content: "\f0a3";
|
||||
content: "\f0a3";
|
||||
}
|
||||
.fa-arrow-circle-left:before {
|
||||
content: "\f0a8";
|
||||
content: "\f0a8";
|
||||
}
|
||||
.fa-arrow-circle-right:before {
|
||||
content: "\f0a9";
|
||||
content: "\f0a9";
|
||||
}
|
||||
.fa-arrow-circle-up:before {
|
||||
content: "\f0aa";
|
||||
content: "\f0aa";
|
||||
}
|
||||
.fa-arrow-circle-down:before {
|
||||
content: "\f0ab";
|
||||
content: "\f0ab";
|
||||
}
|
||||
.fa-wrench:before {
|
||||
content: "\f0ad";
|
||||
content: "\f0ad";
|
||||
}
|
||||
.fa-tasks:before {
|
||||
content: "\f0ae";
|
||||
content: "\f0ae";
|
||||
}
|
||||
.fa-filter:before {
|
||||
content: "\f0b0";
|
||||
content: "\f0b0";
|
||||
}
|
||||
.fa-briefcase:before {
|
||||
content: "\f0b1";
|
||||
content: "\f0b1";
|
||||
}
|
||||
.fa-arrows-alt:before {
|
||||
content: "\f0b2";
|
||||
content: "\f0b2";
|
||||
}
|
||||
.fa-cloud:before {
|
||||
content: "\f0c2";
|
||||
content: "\f0c2";
|
||||
}
|
||||
.fa-copy:before {
|
||||
content: "\f0c5";
|
||||
content: "\f0c5";
|
||||
}
|
||||
.fa-files-o:before {
|
||||
content: "\f0c5";
|
||||
content: "\f0c5";
|
||||
}
|
||||
.fa-floppy-o:before {
|
||||
content: "\f0c7";
|
||||
content: "\f0c7";
|
||||
}
|
||||
.fa-save:before {
|
||||
content: "\f0c7";
|
||||
content: "\f0c7";
|
||||
}
|
||||
.fa-square:before {
|
||||
content: "\f0c8";
|
||||
content: "\f0c8";
|
||||
}
|
||||
.fa-bars:before {
|
||||
content: "\f0c9";
|
||||
content: "\f0c9";
|
||||
}
|
||||
.fa-navicon:before {
|
||||
content: "\f0c9";
|
||||
content: "\f0c9";
|
||||
}
|
||||
.fa-reorder:before {
|
||||
content: "\f0c9";
|
||||
content: "\f0c9";
|
||||
}
|
||||
.fa-list-ul:before {
|
||||
content: "\f0ca";
|
||||
content: "\f0ca";
|
||||
}
|
||||
.fa-list-ol:before {
|
||||
content: "\f0cb";
|
||||
content: "\f0cb";
|
||||
}
|
||||
.fa-table:before {
|
||||
content: "\f0ce";
|
||||
content: "\f0ce";
|
||||
}
|
||||
.fa-caret-down:before {
|
||||
content: "\f0d7";
|
||||
content: "\f0d7";
|
||||
}
|
||||
.fa-caret-up:before {
|
||||
content: "\f0d8";
|
||||
content: "\f0d8";
|
||||
}
|
||||
.fa-caret-left:before {
|
||||
content: "\f0d9";
|
||||
content: "\f0d9";
|
||||
}
|
||||
.fa-caret-right:before {
|
||||
content: "\f0da";
|
||||
content: "\f0da";
|
||||
}
|
||||
.fa-columns:before {
|
||||
content: "\f0db";
|
||||
content: "\f0db";
|
||||
}
|
||||
.fa-sort:before {
|
||||
content: "\f0dc";
|
||||
content: "\f0dc";
|
||||
}
|
||||
.fa-unsorted:before {
|
||||
content: "\f0dc";
|
||||
content: "\f0dc";
|
||||
}
|
||||
.fa-sort-desc:before {
|
||||
content: "\f0dd";
|
||||
content: "\f0dd";
|
||||
}
|
||||
.fa-sort-down:before {
|
||||
content: "\f0dd";
|
||||
content: "\f0dd";
|
||||
}
|
||||
.fa-sort-asc:before {
|
||||
content: "\f0de";
|
||||
content: "\f0de";
|
||||
}
|
||||
.fa-sort-up:before {
|
||||
content: "\f0de";
|
||||
content: "\f0de";
|
||||
}
|
||||
.fa-rotate-left:before {
|
||||
content: "\f0e2";
|
||||
content: "\f0e2";
|
||||
}
|
||||
.fa-undo:before {
|
||||
content: "\f0e2";
|
||||
content: "\f0e2";
|
||||
}
|
||||
.fa-file-text-o:before {
|
||||
content: "\f0f6";
|
||||
content: "\f0f6";
|
||||
}
|
||||
.fa-plus-square:before {
|
||||
content: "\f0fe";
|
||||
content: "\f0fe";
|
||||
}
|
||||
.fa-angle-double-left:before {
|
||||
content: "\f100";
|
||||
content: "\f100";
|
||||
}
|
||||
.fa-angle-double-right:before {
|
||||
content: "\f101";
|
||||
content: "\f101";
|
||||
}
|
||||
.fa-angle-double-up:before {
|
||||
content: "\f102";
|
||||
content: "\f102";
|
||||
}
|
||||
.fa-angle-double-down:before {
|
||||
content: "\f103";
|
||||
content: "\f103";
|
||||
}
|
||||
.fa-angle-left:before {
|
||||
content: "\f104";
|
||||
content: "\f104";
|
||||
}
|
||||
.fa-angle-right:before {
|
||||
content: "\f105";
|
||||
content: "\f105";
|
||||
}
|
||||
.fa-angle-up:before {
|
||||
content: "\f106";
|
||||
content: "\f106";
|
||||
}
|
||||
.fa-angle-down:before {
|
||||
content: "\f107";
|
||||
content: "\f107";
|
||||
}
|
||||
.fa-circle-o:before {
|
||||
content: "\f10c";
|
||||
content: "\f10c";
|
||||
}
|
||||
.fa-spinner:before {
|
||||
content: "\f110";
|
||||
content: "\f110";
|
||||
}
|
||||
.fa-circle:before {
|
||||
content: "\f111";
|
||||
content: "\f111";
|
||||
}
|
||||
.fa-mail-reply:before {
|
||||
content: "\f112";
|
||||
content: "\f112";
|
||||
}
|
||||
.fa-reply:before {
|
||||
content: "\f112";
|
||||
content: "\f112";
|
||||
}
|
||||
.fa-folder-o:before {
|
||||
content: "\f114";
|
||||
content: "\f114";
|
||||
}
|
||||
.fa-folder-open-o:before {
|
||||
content: "\f115";
|
||||
content: "\f115";
|
||||
}
|
||||
.fa-keyboard-o:before {
|
||||
content: "\f11c";
|
||||
content: "\f11c";
|
||||
}
|
||||
.fa-terminal:before {
|
||||
content: "\f120";
|
||||
content: "\f120";
|
||||
}
|
||||
.fa-code:before {
|
||||
content: "\f121";
|
||||
content: "\f121";
|
||||
}
|
||||
.fa-mail-reply-all:before {
|
||||
content: "\f122";
|
||||
content: "\f122";
|
||||
}
|
||||
.fa-reply-all:before {
|
||||
content: "\f122";
|
||||
content: "\f122";
|
||||
}
|
||||
.fa-star-half-empty:before {
|
||||
content: "\f123";
|
||||
content: "\f123";
|
||||
}
|
||||
.fa-star-half-full:before {
|
||||
content: "\f123";
|
||||
content: "\f123";
|
||||
}
|
||||
.fa-star-half-o:before {
|
||||
content: "\f123";
|
||||
content: "\f123";
|
||||
}
|
||||
.fa-crop:before {
|
||||
content: "\f125";
|
||||
content: "\f125";
|
||||
}
|
||||
.fa-code-fork:before {
|
||||
content: "\f126";
|
||||
content: "\f126";
|
||||
}
|
||||
.fa-chain-broken:before {
|
||||
content: "\f127";
|
||||
content: "\f127";
|
||||
}
|
||||
.fa-unlink:before {
|
||||
content: "\f127";
|
||||
content: "\f127";
|
||||
}
|
||||
.fa-info:before {
|
||||
content: "\f129";
|
||||
content: "\f129";
|
||||
}
|
||||
.fa-exclamation:before {
|
||||
content: "\f12a";
|
||||
content: "\f12a";
|
||||
}
|
||||
.fa-rocket:before {
|
||||
content: "\f135";
|
||||
content: "\f135";
|
||||
}
|
||||
.fa-maxcdn:before {
|
||||
content: "\f136";
|
||||
content: "\f136";
|
||||
}
|
||||
.fa-chevron-circle-left:before {
|
||||
content: "\f137";
|
||||
content: "\f137";
|
||||
}
|
||||
.fa-chevron-circle-right:before {
|
||||
content: "\f138";
|
||||
content: "\f138";
|
||||
}
|
||||
.fa-chevron-circle-up:before {
|
||||
content: "\f139";
|
||||
content: "\f139";
|
||||
}
|
||||
.fa-chevron-circle-down:before {
|
||||
content: "\f13a";
|
||||
content: "\f13a";
|
||||
}
|
||||
.fa-ellipsis-h:before {
|
||||
content: "\f141";
|
||||
content: "\f141";
|
||||
}
|
||||
.fa-long-arrow-down:before {
|
||||
content: "\f175";
|
||||
content: "\f175";
|
||||
}
|
||||
.fa-long-arrow-up:before {
|
||||
content: "\f176";
|
||||
content: "\f176";
|
||||
}
|
||||
.fa-long-arrow-left:before {
|
||||
content: "\f177";
|
||||
content: "\f177";
|
||||
}
|
||||
.fa-long-arrow-right:before {
|
||||
content: "\f178";
|
||||
content: "\f178";
|
||||
}
|
||||
.fa-microchip:before {
|
||||
content: "\f2db";
|
||||
content: "\f2db";
|
||||
}
|
||||
.fa-arduino-cloud-download:before {
|
||||
content: "\e910";
|
||||
content: "\e910";
|
||||
}
|
||||
.fa-arduino-cloud-upload:before {
|
||||
content: "\e914";
|
||||
content: "\e914";
|
||||
}
|
||||
.fa-arduino-cloud:before {
|
||||
content: "\e915";
|
||||
content: "\e915";
|
||||
}
|
||||
.fa-arduino-cloud-filled:before {
|
||||
content: "\e912";
|
||||
content: "\e912";
|
||||
}
|
||||
.fa-arduino-cloud-offline:before {
|
||||
content: "\e913";
|
||||
content: "\e913";
|
||||
}
|
||||
.fa-arduino-cloud-filled-offline:before {
|
||||
content: "\e911";
|
||||
content: "\e911";
|
||||
}
|
||||
|
@@ -1,124 +1,124 @@
|
||||
#ide-updater-dialog-container > .dialogBlock {
|
||||
width: 546px;
|
||||
width: 546px;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .bold {
|
||||
font-weight: bold;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ide-updater-dialog--pre-download {
|
||||
display: flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ide-updater-dialog--downloading {
|
||||
flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ide-updater-dialog--logo-container {
|
||||
margin-right: var(--arduino-button-height);
|
||||
margin-right: var(--arduino-button-height);
|
||||
}
|
||||
|
||||
.ide-updater-dialog--logo {
|
||||
background: url('./ide-logo.png') round;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
background: url("./ide-logo.png") round;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
.dialogContent.ide-updater-dialog
|
||||
.ide-updater-dialog--content
|
||||
.ide-updater-dialog--new-version-text.dialogSection {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
margin-top: 0;
|
||||
min-width: 0;
|
||||
.ide-updater-dialog--content
|
||||
.ide-updater-dialog--new-version-text.dialogSection {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
margin-top: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog {
|
||||
color: var(--theia-editor-foreground);
|
||||
background-color: var(--theia-editor-background);
|
||||
font-size: 12px;
|
||||
overflow: auto;
|
||||
padding: 0 12px;
|
||||
cursor: text;
|
||||
width: 100%;
|
||||
color: var(--theia-editor-foreground);
|
||||
background-color: var(--theia-editor-background);
|
||||
font-size: 12px;
|
||||
overflow: auto;
|
||||
padding: 0 12px;
|
||||
cursor: text;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog .fallback {
|
||||
min-height: 180px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
min-height: 180px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog .fallback .spinner {
|
||||
align-self: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.dialogContent.ide-updater-dialog
|
||||
.ide-updater-dialog--content
|
||||
.ide-updater-dialog--new-version-text
|
||||
.dialogRow.changelog-container {
|
||||
align-items: flex-start;
|
||||
border: 1px solid var(--theia-editorWidget-border);
|
||||
border-radius: 2px;
|
||||
overflow: auto;
|
||||
max-height: 180px;
|
||||
.ide-updater-dialog--content
|
||||
.ide-updater-dialog--new-version-text
|
||||
.dialogRow.changelog-container {
|
||||
align-items: flex-start;
|
||||
border: 1px solid var(--theia-editorWidget-border);
|
||||
border-radius: 2px;
|
||||
overflow: auto;
|
||||
max-height: 180px;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog a {
|
||||
color: var(--theia-textLink-foreground);
|
||||
color: var(--theia-textLink-foreground);
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog a:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog code {
|
||||
background: var(--theia-textBlockQuote-background);
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
background: var(--theia-textBlockQuote-background);
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog a code {
|
||||
color: var(--theia-textLink-foreground);
|
||||
color: var(--theia-textLink-foreground);
|
||||
}
|
||||
|
||||
.ide-updater-dialog .buttons-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--arduino-button-height);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--arduino-button-height);
|
||||
}
|
||||
|
||||
.ide-updater-dialog .buttons-container a.theia-button {
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .buttons-container a.theia-button:hover {
|
||||
color: var(--theia-button-foreground);
|
||||
color: var(--theia-button-foreground);
|
||||
}
|
||||
|
||||
.ide-updater-dialog .buttons-container .push {
|
||||
margin-right: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.ide-updater-dialog--content {
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#ide-updater-dialog-container .skip-version-button {
|
||||
margin-left: 79px;
|
||||
margin-right: auto;
|
||||
margin-left: 79px;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* https://github.com/arduino/arduino-ide/pull/2027#issuecomment-1533174638 */
|
||||
/* https://github.com/eclipse-theia/theia/commit/1b5ff9ee459df14cedc0e8266dd02dae93fcd1bf#diff-d8d45a890507a01141c010ad4a6891edf2ae727cfa6dfe604cebbd667812cccbR68 */
|
||||
/* Use normal whitespace handling for the changelog content in the update dialog. */
|
||||
.p-Widget.dialogOverlay .dialogContent.ide-updater-dialog {
|
||||
white-space: normal;
|
||||
white-space: normal;
|
||||
}
|
||||
|
@@ -1,175 +1,177 @@
|
||||
@import './list-widget.css';
|
||||
@import './boards-config-dialog.css';
|
||||
@import './main.css';
|
||||
@import './dialogs.css';
|
||||
@import './monitor.css';
|
||||
@import './arduino-select.css';
|
||||
@import './status-bar.css';
|
||||
@import './terminal.css';
|
||||
@import './editor.css';
|
||||
@import './settings-dialog.css';
|
||||
@import './firmware-uploader-dialog.css';
|
||||
@import './ide-updater-dialog.css';
|
||||
@import './certificate-uploader-dialog.css';
|
||||
@import './user-fields-dialog.css';
|
||||
@import './debug.css';
|
||||
@import './sketchbook.css';
|
||||
@import './cloud-sketchbook.css';
|
||||
@import './fonts.css';
|
||||
@import './custom-codicon.css';
|
||||
@import './progress-bar.css';
|
||||
@import './settings-step-input.css';
|
||||
@import "./list-widget.css";
|
||||
@import "./boards-config-dialog.css";
|
||||
@import "./main.css";
|
||||
@import "./dialogs.css";
|
||||
@import "./monitor.css";
|
||||
@import "./arduino-select.css";
|
||||
@import "./status-bar.css";
|
||||
@import "./terminal.css";
|
||||
@import "./editor.css";
|
||||
@import "./settings-dialog.css";
|
||||
@import "./firmware-uploader-dialog.css";
|
||||
@import "./ide-updater-dialog.css";
|
||||
@import "./certificate-uploader-dialog.css";
|
||||
@import "./user-fields-dialog.css";
|
||||
@import "./debug.css";
|
||||
@import "./sketchbook.css";
|
||||
@import "./cloud-sketchbook.css";
|
||||
@import "./fonts.css";
|
||||
@import "./custom-codicon.css";
|
||||
@import "./progress-bar.css";
|
||||
@import "./settings-step-input.css";
|
||||
|
||||
:root {
|
||||
--arduino-button-height: 28px;
|
||||
--arduino-button-height: 28px;
|
||||
}
|
||||
|
||||
/* Revive of the `--theia-icon-loading`. The variable has been removed from Theia while IDE2 still uses is. */
|
||||
/* The SVG icons are still part of Theia (1.31.1) */
|
||||
/* https://github.com/arduino/arduino-ide/pull/1662#issuecomment-1324997134 */
|
||||
body {
|
||||
--theia-icon-loading: url(../icons/loading-light.svg);
|
||||
--theia-icon-loading-warning: url(../icons/loading-dark.svg);
|
||||
--theia-icon-loading: url(../icons/loading-light.svg);
|
||||
--theia-icon-loading-warning: url(../icons/loading-dark.svg);
|
||||
}
|
||||
body.theia-dark {
|
||||
--theia-icon-loading: url(../icons/loading-dark.svg);
|
||||
--theia-icon-loading-warning: url(../icons/loading-light.svg);
|
||||
--theia-icon-loading: url(../icons/loading-dark.svg);
|
||||
--theia-icon-loading-warning: url(../icons/loading-light.svg);
|
||||
}
|
||||
|
||||
.theia-input.warning:focus {
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
outline-offset: -1px;
|
||||
opacity: 1 !important;
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
outline-offset: -1px;
|
||||
opacity: 1 !important;
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
}
|
||||
|
||||
.theia-input.warning {
|
||||
background-color: var(--theia-warningBackground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
}
|
||||
|
||||
.theia-input.warning::placeholder {
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
color: var(--theia-warningForeground);
|
||||
background-color: var(--theia-warningBackground);
|
||||
}
|
||||
|
||||
.theia-input.error:focus {
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
outline-offset: -1px;
|
||||
opacity: 1 !important;
|
||||
color: var(--theia-errorForeground);
|
||||
background-color: var(--theia-errorBackground);
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
outline-offset: -1px;
|
||||
opacity: 1 !important;
|
||||
color: var(--theia-errorForeground);
|
||||
background-color: var(--theia-errorBackground);
|
||||
}
|
||||
|
||||
.theia-input.error {
|
||||
background-color: var(--theia-errorBackground);
|
||||
background-color: var(--theia-errorBackground);
|
||||
}
|
||||
|
||||
.theia-input.error::placeholder {
|
||||
color: var(--theia-errorForeground);
|
||||
background-color: var(--theia-errorBackground);
|
||||
color: var(--theia-errorForeground);
|
||||
background-color: var(--theia-errorBackground);
|
||||
}
|
||||
|
||||
/* Makes the sidepanel a bit wider when opening the widget */
|
||||
.p-DockPanel-widget {
|
||||
min-width: 220px;
|
||||
min-height: 20px;
|
||||
height: 220px;
|
||||
min-width: 220px;
|
||||
min-height: 20px;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
/* Overrule the default Theia CSS button styles. */
|
||||
button.theia-button,
|
||||
.theia-button {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-family: 'Open Sans Bold',sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
letter-spacing: .01em;
|
||||
line-height: 24px;
|
||||
outline: none;
|
||||
padding: 0 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-width: 2px;
|
||||
border-radius: 32px;
|
||||
text-transform: uppercase;
|
||||
transition: none;
|
||||
box-shadow: none;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-family: "Open Sans Bold", sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 24px;
|
||||
outline: none;
|
||||
padding: 0 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border-width: 2px;
|
||||
border-radius: 32px;
|
||||
text-transform: uppercase;
|
||||
transition: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
button.theia-button {
|
||||
height: var(--arduino-button-height);
|
||||
max-width: none;
|
||||
height: var(--arduino-button-height);
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.theia-button:active,
|
||||
.theia-button:focus {
|
||||
box-shadow: 0 0 0 2px var(--theia-focusBorder);
|
||||
box-shadow: 0 0 0 2px var(--theia-focusBorder);
|
||||
}
|
||||
|
||||
button.theia-button.secondary {
|
||||
border: 2px solid var(--theia-secondaryButton-foreground);
|
||||
border: 2px solid var(--theia-secondaryButton-foreground);
|
||||
}
|
||||
|
||||
button.theia-button[disabled], .theia-button[disabled] {
|
||||
opacity: 0.5;
|
||||
color: var(--theia-button-foreground);
|
||||
background-color: var(--theia-button-background);
|
||||
button.theia-button[disabled],
|
||||
.theia-button[disabled] {
|
||||
opacity: 0.5;
|
||||
color: var(--theia-button-foreground);
|
||||
background-color: var(--theia-button-background);
|
||||
}
|
||||
|
||||
button.secondary[disabled], .theia-button.secondary[disabled] {
|
||||
color: var(--theia-secondaryButton-foreground);
|
||||
background-color: var(--theia-secondaryButton-background);
|
||||
button.secondary[disabled],
|
||||
.theia-button.secondary[disabled] {
|
||||
color: var(--theia-secondaryButton-foreground);
|
||||
background-color: var(--theia-secondaryButton-background);
|
||||
}
|
||||
|
||||
button.theia-button.message-box-dialog-button {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* To make the progress-bar slightly thicker, and use the color from the status bar */
|
||||
.theia-progress-bar-container {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.theia-progress-bar {
|
||||
height: 4px;
|
||||
width: 3%;
|
||||
animation: progress-animation 1.3s 0s infinite
|
||||
cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
height: 4px;
|
||||
width: 3%;
|
||||
animation: progress-animation 1.3s 0s infinite
|
||||
cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.theia-notification-item-progressbar {
|
||||
height: 4px;
|
||||
width: 66%;
|
||||
height: 4px;
|
||||
width: 66%;
|
||||
}
|
||||
|
||||
.flex-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fa-reload {
|
||||
font-size: 14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.debug-toolbar .debug-action>div {
|
||||
font-family: var(--theia-ui-font-family);
|
||||
font-size: var(--theia-ui-font-size0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
min-height: inherit;
|
||||
.debug-toolbar .debug-action > div {
|
||||
font-family: var(--theia-ui-font-family);
|
||||
font-size: var(--theia-ui-font-size0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
min-height: inherit;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
.library-tab-icon {
|
||||
-webkit-mask: url('../icons/library-tab-icon.svg');
|
||||
mask: url('../icons/library-tab-icon.svg');
|
||||
-webkit-mask: url("../icons/library-tab-icon.svg");
|
||||
mask: url("../icons/library-tab-icon.svg");
|
||||
}
|
||||
|
||||
.arduino-list-widget {
|
||||
@@ -82,7 +82,7 @@
|
||||
}
|
||||
|
||||
.component-list-item .header .title .name {
|
||||
font-family: 'Open Sans Bold';
|
||||
font-family: "Open Sans Bold";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
@@ -112,7 +112,9 @@
|
||||
display: inline-block;
|
||||
justify-self: end;
|
||||
text-align: center;
|
||||
background-color: var(--theia-arduino-toolbar-dropdown-option-backgroundHover);
|
||||
background-color: var(
|
||||
--theia-arduino-toolbar-dropdown-option-backgroundHover
|
||||
);
|
||||
padding: 2px 4px 2px 4px;
|
||||
font-size: 12px;
|
||||
max-height: calc(1em + 4px);
|
||||
@@ -131,7 +133,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 4px;
|
||||
font-family: 'Open Sans';
|
||||
font-family: "Open Sans";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
@@ -175,8 +177,20 @@
|
||||
max-width: 8px;
|
||||
}
|
||||
|
||||
div.filterable-list-container > div > div > div > div:nth-child(1) > div.separator :first-child,
|
||||
div.filterable-list-container > div > div > div > div:nth-child(1) > div.separator :last-child {
|
||||
div.filterable-list-container
|
||||
> div
|
||||
> div
|
||||
> div
|
||||
> div:nth-child(1)
|
||||
> div.separator
|
||||
:first-child,
|
||||
div.filterable-list-container
|
||||
> div
|
||||
> div
|
||||
> div
|
||||
> div:nth-child(1)
|
||||
> div.separator
|
||||
:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -202,11 +216,11 @@ div.filterable-list-container > div > div > div > div:nth-child(1) > div.separat
|
||||
}
|
||||
|
||||
.component-list-item .theia-button.secondary.no-border {
|
||||
border: 2px solid var(--theia-button-foreground)
|
||||
border: 2px solid var(--theia-button-foreground);
|
||||
}
|
||||
|
||||
.component-list-item .theia-button.secondary.no-border:hover {
|
||||
border: 2px solid var(--theia-secondaryButton-foreground)
|
||||
border: 2px solid var(--theia-secondaryButton-foreground);
|
||||
}
|
||||
|
||||
.component-list-item .theia-button {
|
||||
|
@@ -1,11 +1,12 @@
|
||||
#theia-bottom-content-panel .p-TabBar[data-orientation='horizontal'].theia-app-bottom {
|
||||
#theia-bottom-content-panel
|
||||
.p-TabBar[data-orientation="horizontal"].theia-app-bottom {
|
||||
background: var(--theia-editorGroupHeader-tabsBackground);
|
||||
}
|
||||
|
||||
/* Avoid the Intellisense widget may be cover by the bottom panel partially.
|
||||
TODO: This issue may be resolved after monaco-editor upgrade */
|
||||
#theia-main-content-panel {
|
||||
z-index: auto
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
#theia-main-content-panel div[id^="code-editor-opener"] {
|
||||
@@ -23,7 +24,6 @@
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
|
||||
.p-TabBar-toolbar .item.arduino-tool-item > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -42,9 +42,15 @@
|
||||
background: var(--theia-arduino-toolbar-button-hoverBackground);
|
||||
}
|
||||
|
||||
.p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div.toggle-serial-monitor,
|
||||
.p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div.toggle-serial-plotter {
|
||||
background-color: var(--theia-arduino-toolbar-button-secondary-hoverBackground);
|
||||
.p-TabBar-toolbar
|
||||
.item.arduino-tool-item.enabled:hover
|
||||
> div.toggle-serial-monitor,
|
||||
.p-TabBar-toolbar
|
||||
.item.arduino-tool-item.enabled:hover
|
||||
> div.toggle-serial-plotter {
|
||||
background-color: var(
|
||||
--theia-arduino-toolbar-button-secondary-hoverBackground
|
||||
);
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
@@ -55,14 +61,14 @@
|
||||
}
|
||||
|
||||
.item.arduino-tool-item.toggled {
|
||||
background-color: unset;
|
||||
opacity: 1;
|
||||
border: none;
|
||||
background-color: unset;
|
||||
opacity: 1;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.item.arduino-tool-item.toggled .arduino-verify-sketch--toolbar,
|
||||
.item.arduino-tool-item.toggled .arduino-upload-sketch--toolbar {
|
||||
background-color: var(--theia-arduino-toolbar-toggleBackground) !important;
|
||||
background-color: var(--theia-arduino-toolbar-toggleBackground) !important;
|
||||
}
|
||||
|
||||
.arduino-tool-icon {
|
||||
@@ -91,7 +97,7 @@
|
||||
}
|
||||
|
||||
.arduino-start-debug-icon {
|
||||
-webkit-mask: url('../icons/debug-dark.svg') 50% 60%;
|
||||
-webkit-mask: url("../icons/debug-dark.svg") 50% 60%;
|
||||
-webkit-mask-size: 70%;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
display: flex;
|
||||
@@ -175,7 +181,12 @@
|
||||
background-color: var(--theia-terminal-background);
|
||||
}
|
||||
|
||||
.theia-output .monaco-editor .lines-content.monaco-editor-background .view-lines .view-line .mtk1:not(.theia-output-error):not(.theia-output-warning) {
|
||||
.theia-output
|
||||
.monaco-editor
|
||||
.lines-content.monaco-editor-background
|
||||
.view-lines
|
||||
.view-line
|
||||
.mtk1:not(.theia-output-error):not(.theia-output-warning) {
|
||||
color: var(--theia-terminal-foreground);
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
.monitor-tab-icon {
|
||||
-webkit-mask: url('../icons/monitor-tab-icon.svg');
|
||||
mask: url('../icons/monitor-tab-icon.svg');
|
||||
-webkit-mask: url("../icons/monitor-tab-icon.svg");
|
||||
mask: url("../icons/monitor-tab-icon.svg");
|
||||
}
|
||||
|
||||
.serial-monitor {
|
||||
@@ -10,8 +10,8 @@
|
||||
}
|
||||
|
||||
.serial-monitor-messages {
|
||||
white-space: 'pre';
|
||||
font-family: monospace
|
||||
white-space: "pre";
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.serial-monitor-messages pre {
|
||||
|
@@ -1,32 +1,32 @@
|
||||
.progress-bar {
|
||||
margin-top: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.progress-bar--outer {
|
||||
background: var(--theia-editorWidget-background);
|
||||
border-radius: 11px;
|
||||
height: 6px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--theia-editorWidget-background);
|
||||
border-radius: 11px;
|
||||
height: 6px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar--inner {
|
||||
transition: width 1s;
|
||||
height: 100%;
|
||||
background: var(--theia-progressBar-background);
|
||||
border-radius: 11px;
|
||||
transition: width 1s;
|
||||
height: 100%;
|
||||
background: var(--theia-progressBar-background);
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
.progress-bar--percentage {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.progress-bar--percentage-text {
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#arduino-settings-dialog-container > .dialogBlock {
|
||||
height: 531px;;
|
||||
height: 531px;
|
||||
max-width: 740px !important;
|
||||
width: calc(100% - 96px);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.settings-step-input-container {
|
||||
position: relative
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-step-input-element::-webkit-inner-spin-button,
|
||||
@@ -25,7 +25,7 @@
|
||||
right: 14px;
|
||||
}
|
||||
|
||||
.settings-step-input-container:hover>.settings-step-input-buttons-container {
|
||||
.settings-step-input-container:hover > .settings-step-input-buttons-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -47,4 +47,4 @@
|
||||
|
||||
.settings-step-input-button:hover {
|
||||
background: rgba(128, 128, 128, 0.8);
|
||||
}
|
||||
}
|
||||
|
@@ -1,37 +1,36 @@
|
||||
.sketchbook-tab-icon {
|
||||
-webkit-mask: url('./sketchbook.svg');
|
||||
mask: url('./sketchbook.svg');
|
||||
-webkit-mask: url("./sketchbook.svg");
|
||||
mask: url("./sketchbook.svg");
|
||||
}
|
||||
|
||||
.p-TabBar-tabIcon.sketchbook-tree-icon {
|
||||
background-color: var(--theia-foreground);
|
||||
-webkit-mask: url(./sketchbook-tree-icon.svg);
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
width: 19px !important;
|
||||
height: var(--theia-icon-size);
|
||||
-webkit-mask-size: 100%;
|
||||
background-color: var(--theia-foreground);
|
||||
-webkit-mask: url(./sketchbook-tree-icon.svg);
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
width: 19px !important;
|
||||
height: var(--theia-icon-size);
|
||||
-webkit-mask-size: 100%;
|
||||
}
|
||||
|
||||
.p-mod-current
|
||||
.sketchbook-tree-icon {
|
||||
background-color: var(--theia-foreground);
|
||||
-webkit-mask: url(./sketchbook-tree-icon-filled.svg);
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-size: 100%;
|
||||
.p-mod-current .sketchbook-tree-icon {
|
||||
background-color: var(--theia-foreground);
|
||||
-webkit-mask: url(./sketchbook-tree-icon-filled.svg);
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-size: 100%;
|
||||
}
|
||||
|
||||
.sketchbook-trees-container {
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sketchbook-trees-container .create-new {
|
||||
min-height: 58px;
|
||||
height: 58px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 58px;
|
||||
height: 58px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/*
|
||||
By default, theia-button has a left-margin. IDE2 does not need the left margin
|
||||
@@ -39,44 +38,43 @@ for the _New Remote? Sketch_. Otherwise, the button does not fit the default
|
||||
widget width.
|
||||
*/
|
||||
.sketchbook-trees-container .create-new .theia-button {
|
||||
margin-left: unset;
|
||||
margin-left: unset;
|
||||
}
|
||||
|
||||
.sketchbook-tree__opts {
|
||||
background-color: var(--theia-foreground);
|
||||
-webkit-mask: url(./sketchbook-opts-icon.svg);
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
width: var(--theia-icon-size);
|
||||
height: var(--theia-icon-size);
|
||||
background-color: var(--theia-foreground);
|
||||
-webkit-mask: url(./sketchbook-opts-icon.svg);
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
width: var(--theia-icon-size);
|
||||
height: var(--theia-icon-size);
|
||||
}
|
||||
|
||||
.active-sketch {
|
||||
font-weight: 500;
|
||||
background-color: var(--theia-list-activeSelectionBackground) !important;
|
||||
color: var(--theia-list-activeSelectionForeground) !important;
|
||||
|
||||
font-weight: 500;
|
||||
background-color: var(--theia-list-activeSelectionBackground) !important;
|
||||
color: var(--theia-list-activeSelectionForeground) !important;
|
||||
}
|
||||
|
||||
#arduino-sketchbook-tree-widget .theia-TreeNode {
|
||||
line-height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#arduino-sketchbook-tree-widget .theia-TreeNodeSegmentGrow {
|
||||
flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.theia-TreeNode .sketchbook-commands-icons {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.theia-TreeNode:hover .sketchbook-commands-icons,
|
||||
.theia-TreeNode.theia-mod-selected .sketchbook-commands-icons {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.theia-Tree:focus .theia-TreeNode.theia-mod-selected,
|
||||
.theia-Tree .ReactVirtualized__List:focus .theia-TreeNode.theia-mod-selected {
|
||||
background: var(--theia-list-inactiveSelectionBackground);
|
||||
color: var(--theia-list-inactiveSelectionForeground) !important;
|
||||
background: var(--theia-list-inactiveSelectionBackground);
|
||||
color: var(--theia-list-inactiveSelectionForeground) !important;
|
||||
}
|
||||
|
@@ -1,32 +1,32 @@
|
||||
.user-fields-container {
|
||||
max-height: 332px;
|
||||
overflow: auto;
|
||||
padding: 2px;
|
||||
max-height: 332px;
|
||||
overflow: auto;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.user-fields-list {
|
||||
margin: 16px 0;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.user-fields-dialog-content {
|
||||
width: 408px;
|
||||
max-height: 491px;
|
||||
width: 408px;
|
||||
max-height: 491px;
|
||||
}
|
||||
|
||||
.user-fields-dialog-content .field-label {
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.01em;
|
||||
text-align: left;
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.01em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.user-fields-dialog-content .theia-input {
|
||||
flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.user-fields-dialog-content .button-container {
|
||||
justify-content: flex-end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import {
|
||||
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
||||
CommonCommands,
|
||||
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
||||
} from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import type { OnWillStopAction } from '@theia/core/lib/browser/frontend-application';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser';
|
||||
import { isOSX } from '@theia/core';
|
||||
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';
|
||||
import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
|
||||
@injectable()
|
||||
export class CommonFrontendContribution extends TheiaCommonFrontendContribution {
|
||||
@@ -25,6 +25,7 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
|
||||
CommonCommands.PIN_TAB,
|
||||
CommonCommands.UNPIN_TAB,
|
||||
CommonCommands.NEW_UNTITLED_FILE,
|
||||
CommonCommands.NEW_UNTITLED_TEXT_FILE,
|
||||
]) {
|
||||
commandRegistry.unregisterCommand(command);
|
||||
}
|
||||
@@ -48,6 +49,7 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
|
||||
CommonCommands.ABOUT_COMMAND,
|
||||
CommonCommands.SAVE_WITHOUT_FORMATTING, // Patched for https://github.com/eclipse-theia/theia/pull/8877,
|
||||
CommonCommands.NEW_UNTITLED_FILE,
|
||||
CommonCommands.NEW_UNTITLED_TEXT_FILE,
|
||||
]) {
|
||||
registry.unregisterMenuAction(command);
|
||||
}
|
||||
|
@@ -1,4 +1,8 @@
|
||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Title, Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
|
@@ -1,44 +1,44 @@
|
||||
import debounce from 'p-debounce';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
|
||||
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
|
||||
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
|
||||
import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import {
|
||||
FileOperationError,
|
||||
FileOperationResult,
|
||||
} from '@theia/filesystem/lib/common/files';
|
||||
import debounce from 'p-debounce';
|
||||
import { SketchesService } from '../../../common/protocol';
|
||||
import {
|
||||
CurrentSketch,
|
||||
SketchesServiceClientImpl,
|
||||
} from '../../sketches-service-client-impl';
|
||||
import { maybeUpdateReadOnlyState } from '../monaco/monaco-editor-provider';
|
||||
import { DebugConfigurationModel } from './debug-configuration-model';
|
||||
import {
|
||||
FileOperationError,
|
||||
FileOperationResult,
|
||||
} from '@theia/filesystem/lib/common/files';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
|
||||
@injectable()
|
||||
export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
|
||||
private readonly sketchesService: SketchesService;
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
private readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
private readonly appStateService: FrontendApplicationStateService;
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
private readonly fileService: FileService;
|
||||
|
||||
protected onTempContentDidChangeEmitter =
|
||||
private onTempContentDidChangeEmitter =
|
||||
new Emitter<TheiaDebugConfigurationModel.JsonContent>();
|
||||
get onTempContentDidChange(): Event<TheiaDebugConfigurationModel.JsonContent> {
|
||||
return this.onTempContentDidChangeEmitter.event;
|
||||
}
|
||||
|
||||
protected override async doInit(): Promise<void> {
|
||||
this.watchLaunchConfigEditor();
|
||||
this.appStateService.reachedState('ready').then(async () => {
|
||||
const tempContent = await this.getTempLaunchJsonContent();
|
||||
if (!tempContent) {
|
||||
@@ -75,6 +75,19 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
return super.doInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener on current sketch change, and maybe updates the readonly state of the editor showing the debug configuration. aka the `launch.json`.
|
||||
*/
|
||||
private watchLaunchConfigEditor(): Disposable {
|
||||
return this.sketchesServiceClient.onCurrentSketchDidChange(() => {
|
||||
for (const widget of this.editorManager.all) {
|
||||
maybeUpdateReadOnlyState(widget, (uri) =>
|
||||
this.sketchesServiceClient.isReadOnly(uri)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override updateModels = debounce(async () => {
|
||||
await this.appStateService.reachedState('ready');
|
||||
const roots = await this.workspaceService.roots;
|
||||
@@ -111,7 +124,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
this.updateCurrent();
|
||||
}, 500);
|
||||
|
||||
protected async getTempLaunchJsonContent(): Promise<
|
||||
private async getTempLaunchJsonContent(): Promise<
|
||||
(TheiaDebugConfigurationModel.JsonContent & { uri: URI }) | URI | undefined
|
||||
> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
|
@@ -0,0 +1,57 @@
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import React from '@theia/core/shared/react';
|
||||
import { DebugAction } from '@theia/debug/lib/browser/view/debug-action';
|
||||
import { DebugConfigurationSelect as TheiaDebugConfigurationSelect } from '@theia/debug/lib/browser/view/debug-configuration-select';
|
||||
import { DebugConfigurationWidget as TheiaDebugConfigurationWidget } from '@theia/debug/lib/browser/view/debug-configuration-widget';
|
||||
|
||||
/**
|
||||
* Patched to programmatically update the debug config <select> in the widget.
|
||||
*/
|
||||
@injectable()
|
||||
export class DebugConfigurationWidget extends TheiaDebugConfigurationWidget {
|
||||
override render(): React.ReactNode {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DebugAction
|
||||
run={this.start}
|
||||
label={nls.localizeByDefault('Start Debugging')}
|
||||
iconClass="debug-start"
|
||||
ref={this.setStepRef}
|
||||
/>
|
||||
{/* The customized select component that will refresh when the config manager did change */}
|
||||
<DebugConfigurationSelect
|
||||
manager={this.manager}
|
||||
quickInputService={this.quickInputService}
|
||||
isMultiRoot={this.workspaceService.isMultiRootWorkspaceOpened}
|
||||
/>
|
||||
<DebugAction
|
||||
run={this.openConfiguration}
|
||||
label={nls.localizeByDefault('Open {0}', '"launch.json"')}
|
||||
iconClass="settings-gear"
|
||||
/>
|
||||
<DebugAction
|
||||
run={this.openConsole}
|
||||
label={nls.localizeByDefault('Debug Console')}
|
||||
iconClass="terminal"
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DebugConfigurationSelect extends TheiaDebugConfigurationSelect {
|
||||
private readonly toDisposeOnUnmount = new DisposableCollection();
|
||||
|
||||
override componentDidMount(): void {
|
||||
super.componentDidMount();
|
||||
this.toDisposeOnUnmount.push(
|
||||
this['manager'].onDidChange(() => this.refreshDebugConfigurations())
|
||||
);
|
||||
}
|
||||
|
||||
override componentWillUnmount(): void {
|
||||
this.toDisposeOnUnmount.dispose();
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { DebugSession } from '@theia/debug/lib/browser/debug-session';
|
||||
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||
import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
@injectable()
|
||||
export class DebugSessionManager extends TheiaDebugSessionManager {
|
||||
@inject(WorkspaceService)
|
||||
private readonly workspaceService: WorkspaceService;
|
||||
|
||||
protected override doStart(
|
||||
sessionId: string,
|
||||
options: DebugConfigurationSessionOptions
|
||||
): Promise<DebugSession> {
|
||||
this.syncCurrentOptions(options);
|
||||
return super.doStart(sessionId, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the debug config manager knows about the currently started options, and it's not the currently selected one, select it.
|
||||
*/
|
||||
private syncCurrentOptions(options: DebugConfigurationSessionOptions): void {
|
||||
const knownConfigOptions = this.debugConfigurationManager.find(
|
||||
options.configuration,
|
||||
options.workspaceFolderUri ??
|
||||
this.workspaceService
|
||||
.tryGetRoots()
|
||||
.map((stat) => stat.resource.toString())[0]
|
||||
);
|
||||
if (
|
||||
knownConfigOptions &&
|
||||
!deepEqual(knownConfigOptions, this.debugConfigurationManager.current)
|
||||
) {
|
||||
this.debugConfigurationManager.current = knownConfigOptions;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,15 +1,20 @@
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { LOCKED_CLASS, lock } from '@theia/core/lib/browser/widgets/widget';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { EditorServiceOverrides, MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||
import { SketchesServiceClientImpl } from '../../sketches-service-client-impl';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Title, Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import type { ReferencesModel } from '@theia/monaco-editor-core/esm/vs/editor/contrib/gotoSymbol/browser/referencesModel';
|
||||
|
||||
import {
|
||||
EditorServiceOverrides,
|
||||
MonacoEditor,
|
||||
} from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||
import { SketchesServiceClientImpl } from '../../sketches-service-client-impl';
|
||||
|
||||
type CancelablePromise = Promise<ReferencesModel> & {
|
||||
cancel: () => void;
|
||||
@@ -39,7 +44,9 @@ export class MonacoEditorProvider extends TheiaMonacoEditorProvider {
|
||||
|
||||
private installCustomReferencesController(editor: MonacoEditor): Disposable {
|
||||
const control = editor.getControl();
|
||||
const referencesController: any = control.getContribution('editor.contrib.referencesController');
|
||||
const referencesController: any = control.getContribution(
|
||||
'editor.contrib.referencesController'
|
||||
);
|
||||
const originalToggleWidget = referencesController.toggleWidget;
|
||||
const toDispose = new DisposableCollection();
|
||||
const toDisposeBeforeToggleWidget = new DisposableCollection();
|
||||
@@ -97,3 +104,30 @@ export class MonacoEditorProvider extends TheiaMonacoEditorProvider {
|
||||
editor.updateOptions({ readOnly });
|
||||
}
|
||||
}
|
||||
|
||||
// Theia cannot dynamically set an editor to writable once it was readonly.
|
||||
export function maybeUpdateReadOnlyState(
|
||||
widget: EditorWidget,
|
||||
isReadOnly: (uri: string | URI | monaco.Uri) => boolean
|
||||
): void {
|
||||
const editor = widget.editor;
|
||||
if (!(editor instanceof MonacoEditor)) {
|
||||
return;
|
||||
}
|
||||
const model = editor.document;
|
||||
const oldReadOnly = model.readOnly;
|
||||
const resource = model['resource'];
|
||||
const newReadOnly = Boolean(resource.isReadonly) || isReadOnly(resource.uri);
|
||||
if (oldReadOnly !== newReadOnly) {
|
||||
editor.getControl().updateOptions({ readOnly: newReadOnly });
|
||||
if (newReadOnly) {
|
||||
lock(widget.title);
|
||||
} else {
|
||||
unlock(widget.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unlock(title: Title<Widget>): void {
|
||||
title.className = title.className.replace(LOCKED_CLASS, '').trim();
|
||||
}
|
||||
|
@@ -19,7 +19,9 @@ export class MonacoTextModelService extends TheiaMonacoTextModelService {
|
||||
const factory = this.factories
|
||||
.getContributions()
|
||||
.find(({ scheme }) => resource.uri.scheme === scheme);
|
||||
const readOnly = this.sketchesServiceClient.isReadOnly(resource.uri);
|
||||
const readOnly =
|
||||
Boolean(resource.isReadonly) ||
|
||||
this.sketchesServiceClient.isReadOnly(resource.uri);
|
||||
return factory
|
||||
? factory.createModel(resource)
|
||||
: new MaybeReadonlyMonacoEditorModel(
|
||||
@@ -75,7 +77,7 @@ class MaybeReadonlyMonacoEditorModel extends SilentMonacoEditorModel {
|
||||
}
|
||||
this._dirty = dirty;
|
||||
if (dirty === false) {
|
||||
(this as any).updateSavedVersionId();
|
||||
this['updateSavedVersionId']();
|
||||
}
|
||||
this.onDirtyChangedEmitter.fire(undefined);
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ import {
|
||||
} from '@theia/monaco/lib/browser/monaco-theming-service';
|
||||
import { MonacoThemeRegistry as TheiaMonacoThemeRegistry } from '@theia/monaco/lib/browser/textmate/monaco-theme-registry';
|
||||
import type { ThemeMix } from '@theia/monaco/lib/browser/textmate/monaco-theme-types';
|
||||
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import { HostedPluginSupport } from '../../hosted/hosted-plugin-support';
|
||||
import { ArduinoThemes, compatibleBuiltInTheme } from '../core/theming';
|
||||
import { WindowServiceExt } from '../core/window-service-ext';
|
||||
|
||||
|
@@ -14,7 +14,8 @@ export class FileNavigatorContribution extends TheiaFileNavigatorContribution {
|
||||
constructor(
|
||||
@inject(FileNavigatorPreferences)
|
||||
protected override readonly fileNavigatorPreferences: FileNavigatorPreferences,
|
||||
@inject(OpenerService) protected override readonly openerService: OpenerService,
|
||||
@inject(OpenerService)
|
||||
protected override readonly openerService: OpenerService,
|
||||
@inject(FileNavigatorFilter)
|
||||
protected override readonly fileNavigatorFilter: FileNavigatorFilter,
|
||||
@inject(WorkspaceService)
|
||||
|
@@ -8,7 +8,9 @@ import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@th
|
||||
|
||||
@injectable()
|
||||
export class OutputToolbarContribution extends TheiaOutputToolbarContribution {
|
||||
override async registerToolbarItems(registry: TabBarToolbarRegistry): Promise<void> {
|
||||
override async registerToolbarItems(
|
||||
registry: TabBarToolbarRegistry
|
||||
): Promise<void> {
|
||||
await super.registerToolbarItems(registry); // Why is it async?
|
||||
// It's a hack. Currently, it's not possible to unregister a toolbar contribution via API.
|
||||
(
|
||||
|
@@ -5,9 +5,13 @@ import {
|
||||
PluginContributions,
|
||||
HostedPluginSupport as TheiaHostedPluginSupport,
|
||||
} from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import { HostedPluginSupport } from '../../hosted/hosted-plugin-support';
|
||||
|
||||
@injectable()
|
||||
export class HostedPluginSupport extends TheiaHostedPluginSupport {
|
||||
export class HostedPluginSupportImpl
|
||||
extends TheiaHostedPluginSupport
|
||||
implements HostedPluginSupport
|
||||
{
|
||||
private readonly onDidLoadEmitter = new Emitter<void>();
|
||||
private readonly onDidCloseConnectionEmitter = new Emitter<void>();
|
||||
|
||||
|
@@ -8,7 +8,10 @@ export class ScmContribution extends TheiaScmContribution {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
protected override setStatusBarEntry(id: string, entry: StatusBarEntry): void {
|
||||
protected override setStatusBarEntry(
|
||||
id: string,
|
||||
entry: StatusBarEntry
|
||||
): void {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { MaybePromise } from '@theia/core';
|
||||
import { Dialog, DialogError } from '@theia/core/lib/browser/dialogs';
|
||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
|
||||
@@ -10,13 +9,14 @@ import type {
|
||||
Progress,
|
||||
ProgressUpdate,
|
||||
} from '@theia/core/lib/common/message-service-protocol';
|
||||
import type { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { UUID } from '@theia/core/shared/@phosphor/coreutils';
|
||||
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||
import { inject } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
WorkspaceInputDialog as TheiaWorkspaceInputDialog,
|
||||
WorkspaceInputDialogProps,
|
||||
} from '@theia/workspace/lib/browser/workspace-input-dialog';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog {
|
||||
private skipShowErrorMessageOnOpen: boolean;
|
||||
@@ -161,7 +161,7 @@ export class WorkspaceInputDialogWithProgress<
|
||||
|
||||
const cancellationSource = new CancellationTokenSource();
|
||||
const progress: Progress = {
|
||||
id: v4(),
|
||||
id: UUID.uuid4(),
|
||||
cancel: () => cancellationSource.cancel(),
|
||||
report: (update: ProgressUpdate) => {
|
||||
this.setProgressMessage(update);
|
||||
|
@@ -1 +1 @@
|
||||
Sketchcache = is a cache that holds sketches and fileStat objects.
|
||||
Sketchcache = is a cache that holds sketches and fileStat objects.
|
||||
|
@@ -1,4 +1,8 @@
|
||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { CloudSketchbookCompositeWidget } from './cloud-sketchbook-composite-widget';
|
||||
import { SketchbookWidget } from '../sketchbook/sketchbook-widget';
|
||||
import { ArduinoPreferences } from '../../arduino-preferences';
|
||||
|
@@ -20,8 +20,22 @@ export const InstallManually = nls.localize(
|
||||
'arduino/common/installManually',
|
||||
'Install Manually'
|
||||
);
|
||||
export const SelectManually = nls.localize(
|
||||
'arduino/common/selectManually',
|
||||
'Select Manually'
|
||||
);
|
||||
|
||||
export const serialMonitorWidgetLabel = nls.localize(
|
||||
'arduino/common/serialMonitor',
|
||||
'Serial Monitor'
|
||||
);
|
||||
|
||||
export const noBoardSelected = nls.localize(
|
||||
'arduino/common/noBoardSelected',
|
||||
'No board selected'
|
||||
);
|
||||
|
||||
export const noSketchOpened = nls.localize(
|
||||
'arduino/common/noSketchOpened',
|
||||
'No sketch opened'
|
||||
);
|
||||
|
@@ -80,6 +80,22 @@ export interface BoardsService
|
||||
fqbn: string;
|
||||
protocol: string;
|
||||
}): Promise<BoardUserField[]>;
|
||||
/**
|
||||
* Checks whether the debugging is enabled with the FQBN, programmer, current sketch, and custom board options.
|
||||
*
|
||||
* When the debugging is enabled, the promise resolves with the FQBN to use with the debugger. This is the same
|
||||
* FQBN given in the `CheckDebugEnabledParams#fqbn` but cleaned up of the board options that do not affect the debugger configuration.
|
||||
* It may be used by clients/IDE to group slightly different boards option selections under the same debug configuration.
|
||||
*/
|
||||
checkDebugEnabled(params: CheckDebugEnabledParams): Promise<string>;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export interface BoardSearch extends Searchable.Options {
|
||||
@@ -330,10 +346,10 @@ export interface BoardDetails {
|
||||
readonly requiredTools: Tool[];
|
||||
readonly configOptions: ConfigOption[];
|
||||
readonly programmers: Programmer[];
|
||||
readonly debuggingSupported: boolean;
|
||||
readonly VID: string;
|
||||
readonly PID: string;
|
||||
readonly buildProperties: string[];
|
||||
readonly defaultProgrammerId?: string;
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
@@ -425,6 +441,18 @@ export namespace Programmer {
|
||||
);
|
||||
}
|
||||
}
|
||||
export function isProgrammer(arg: unknown): arg is Programmer {
|
||||
return (
|
||||
typeof arg === 'object' &&
|
||||
arg !== null &&
|
||||
(<Programmer>arg).id !== undefined &&
|
||||
typeof (<Programmer>arg).id === 'string' &&
|
||||
(<Programmer>arg).name !== undefined &&
|
||||
typeof (<Programmer>arg).name === 'string' &&
|
||||
(<Programmer>arg).platform !== undefined &&
|
||||
typeof (<Programmer>arg).platform === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
export namespace Board {
|
||||
export function is(board: any): board is Board {
|
||||
|
@@ -67,7 +67,7 @@ export namespace LibrarySearch {
|
||||
'Contributed',
|
||||
'Retired',
|
||||
] as const;
|
||||
export type Type = typeof TypeLiterals[number];
|
||||
export type Type = (typeof TypeLiterals)[number];
|
||||
export namespace Type {
|
||||
export function is(arg: unknown): arg is Type {
|
||||
return typeof arg === 'string' && TypeLiterals.includes(arg as Type);
|
||||
@@ -96,7 +96,7 @@ export namespace LibrarySearch {
|
||||
'Timing',
|
||||
'Uncategorized',
|
||||
] as const;
|
||||
export type Topic = typeof TopicLiterals[number];
|
||||
export type Topic = (typeof TopicLiterals)[number];
|
||||
export namespace Topic {
|
||||
export function is(arg: unknown): arg is Topic {
|
||||
return typeof arg === 'string' && TopicLiterals.includes(arg as Topic);
|
||||
|
@@ -121,7 +121,7 @@ export interface SketchesService {
|
||||
* 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: Sketch): Promise<string[]>;
|
||||
tempBuildPath(sketch: SketchRef): Promise<string[]>;
|
||||
}
|
||||
|
||||
export interface SketchRef {
|
||||
@@ -308,7 +308,7 @@ export namespace Sketch {
|
||||
export namespace Extensions {
|
||||
export const DEFAULT = '.ino';
|
||||
export const MAIN = [DEFAULT, '.pde'];
|
||||
export const SOURCE = ['.c', '.cpp', '.S'];
|
||||
export const SOURCE = ['.c', '.cpp', '.S', '.cxx', '.cc'];
|
||||
export const CODE_FILES = [
|
||||
...MAIN,
|
||||
...SOURCE,
|
||||
|
@@ -7,7 +7,7 @@ import {
|
||||
CHANNEL_REQUEST_RELOAD,
|
||||
MenuDto,
|
||||
} from '@theia/core/lib/electron-common/electron-api';
|
||||
import { v4 } from 'uuid';
|
||||
import { UUID } from '@theia/core/shared/@phosphor/coreutils';
|
||||
import type { Sketch } from '../common/protocol/sketches-service';
|
||||
import {
|
||||
CHANNEL_APP_INFO,
|
||||
@@ -43,7 +43,7 @@ function convertMenu(
|
||||
}
|
||||
|
||||
return menu.map((item) => {
|
||||
let nodeId = v4();
|
||||
let nodeId = UUID.uuid4();
|
||||
if (item.execute) {
|
||||
if (!item.id) {
|
||||
throw new Error(
|
||||
|
@@ -1,35 +1,33 @@
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import type { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
|
||||
import { environment } from '@theia/application-package/lib/environment';
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
contentTracing,
|
||||
ipcMain,
|
||||
Event as ElectronEvent,
|
||||
ipcMain,
|
||||
} from '@theia/core/electron-shared/electron';
|
||||
import { fork } from 'node:child_process';
|
||||
import { AddressInfo } from 'node:net';
|
||||
import { join, isAbsolute, resolve } from 'node:path';
|
||||
import { promises as fs, rm, rmSync } from 'node:fs';
|
||||
import type { MaybePromise, Mutable } from '@theia/core/lib/common/types';
|
||||
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
|
||||
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props';
|
||||
import { environment } from '@theia/application-package/lib/environment';
|
||||
import {
|
||||
ElectronMainApplication as TheiaElectronMainApplication,
|
||||
ElectronMainExecutionParams,
|
||||
} from '@theia/core/lib/electron-main/electron-main-application';
|
||||
import { URI } from '@theia/core/shared/vscode-uri';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import * as os from '@theia/core/lib/common/os';
|
||||
import { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window';
|
||||
import { IsTempSketch } from '../../node/is-temp-sketch';
|
||||
import { ErrnoException } from '../../node/utils/errors';
|
||||
import { isAccessibleSketchPath } from '../../node/sketches-service-impl';
|
||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { isObject, MaybePromise, Mutable } from '@theia/core/lib/common/types';
|
||||
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token';
|
||||
import {
|
||||
ElectronMainApplication as TheiaElectronMainApplication,
|
||||
ElectronMainExecutionParams,
|
||||
} from '@theia/core/lib/electron-main/electron-main-application';
|
||||
import type { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window';
|
||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { URI } from '@theia/core/shared/vscode-uri';
|
||||
import { log as logToFile, setup as setupFileLog } from 'node-log-rotate';
|
||||
import { fork } from 'node:child_process';
|
||||
import { promises as fs, rm, rmSync } from 'node:fs';
|
||||
import type { AddressInfo } from 'node:net';
|
||||
import { isAbsolute, join, resolve } from 'node:path';
|
||||
import { Sketch } from '../../common/protocol';
|
||||
import {
|
||||
AppInfo,
|
||||
@@ -39,9 +37,71 @@ import {
|
||||
CHANNEL_SHOW_PLOTTER_WINDOW,
|
||||
isShowPlotterWindowParams,
|
||||
} from '../../electron-common/electron-arduino';
|
||||
import { IsTempSketch } from '../../node/is-temp-sketch';
|
||||
import { isAccessibleSketchPath } from '../../node/sketches-service-impl';
|
||||
import { ErrnoException } from '../../node/utils/errors';
|
||||
|
||||
app.commandLine.appendSwitch('disable-http-cache');
|
||||
|
||||
const consoleLogFunctionNames = [
|
||||
'log',
|
||||
'trace',
|
||||
'debug',
|
||||
'info',
|
||||
'warn',
|
||||
'error',
|
||||
] as const;
|
||||
type ConsoleLogSeverity = (typeof consoleLogFunctionNames)[number];
|
||||
interface ConsoleLogParams {
|
||||
readonly severity: ConsoleLogSeverity;
|
||||
readonly message: string;
|
||||
}
|
||||
function isConsoleLogParams(arg: unknown): arg is ConsoleLogParams {
|
||||
return (
|
||||
isObject<ConsoleLogParams>(arg) &&
|
||||
typeof arg.message === 'string' &&
|
||||
typeof arg.severity === 'string' &&
|
||||
consoleLogFunctionNames.includes(arg.severity as ConsoleLogSeverity)
|
||||
);
|
||||
}
|
||||
|
||||
// Patch for on Linux when `XDG_CONFIG_HOME` is not available, `node-log-rotate` creates the folder with `undefined` name.
|
||||
// See https://github.com/lemon-sour/node-log-rotate/issues/23 and https://github.com/arduino/arduino-ide/issues/394.
|
||||
// If the IDE2 is running on Linux, and the `XDG_CONFIG_HOME` variable is not available, set it to avoid the `undefined` folder.
|
||||
// From the specs: https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
// "If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used."
|
||||
function enableFileLogger() {
|
||||
const os = require('os');
|
||||
const util = require('util');
|
||||
if (os.platform() === 'linux' && !process.env['XDG_CONFIG_HOME']) {
|
||||
const { join } = require('path');
|
||||
const home = process.env['HOME'];
|
||||
const xdgConfigHome = home
|
||||
? join(home, '.config')
|
||||
: join(os.homedir(), '.config');
|
||||
process.env['XDG_CONFIG_HOME'] = xdgConfigHome;
|
||||
}
|
||||
setupFileLog({
|
||||
appName: 'Arduino IDE',
|
||||
maxSize: 10 * 1024 * 1024,
|
||||
});
|
||||
for (const name of consoleLogFunctionNames) {
|
||||
const original = console[name];
|
||||
console[name] = function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
const messages = Object.values(arguments);
|
||||
const message = util.format(...messages);
|
||||
original(message);
|
||||
logToFile(message);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const isProductionMode = !environment.electron.isDevMode();
|
||||
if (isProductionMode) {
|
||||
enableFileLogger();
|
||||
}
|
||||
|
||||
interface WorkspaceOptions {
|
||||
file: string;
|
||||
x: number;
|
||||
@@ -185,7 +245,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
|
||||
private attachFileAssociations(cwd: string): void {
|
||||
// OSX: register open-file event
|
||||
if (os.isOSX) {
|
||||
if (isOSX) {
|
||||
app.on('open-file', async (event, path) => {
|
||||
event.preventDefault();
|
||||
const resolvedPath = await this.resolvePath(path, cwd);
|
||||
@@ -495,9 +555,14 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
);
|
||||
console.log(`Starting backend process. PID: ${backendProcess.pid}`);
|
||||
return new Promise((resolve, reject) => {
|
||||
// The backend server main file is also supposed to send the resolved http(s) server port via IPC.
|
||||
backendProcess.on('message', (address: AddressInfo) => {
|
||||
resolve(address.port);
|
||||
// The forked backend process sends the resolved http(s) server port via IPC, and forwards the log messages.
|
||||
backendProcess.on('message', (arg: unknown) => {
|
||||
if (isConsoleLogParams(arg)) {
|
||||
const { message, severity } = arg;
|
||||
console[severity](message);
|
||||
} else if (isAddressInfo(arg)) {
|
||||
resolve(arg.port);
|
||||
}
|
||||
});
|
||||
backendProcess.on('error', (error) => {
|
||||
reject(error);
|
||||
@@ -703,7 +768,7 @@ class InterruptWorkspaceRestoreError extends Error {
|
||||
async function updateFrontendApplicationConfigFromPackageJson(
|
||||
config: FrontendApplicationConfig
|
||||
): Promise<FrontendApplicationConfig> {
|
||||
if (environment.electron.isDevMode()) {
|
||||
if (!isProductionMode) {
|
||||
console.debug(
|
||||
'Skipping frontend application configuration customizations. Running in dev mode.'
|
||||
);
|
||||
@@ -777,3 +842,9 @@ function updateAppInfo(
|
||||
});
|
||||
return toUpdate;
|
||||
}
|
||||
|
||||
function isAddressInfo(arg: unknown): arg is Pick<AddressInfo, 'port'> {
|
||||
// Cannot do the type-guard on all properties, but the port is sufficient as the address is always `localhost`.
|
||||
// For example, the `family` might be absent if the address is IPv6.
|
||||
return isObject<AddressInfo>(arg) && typeof arg.port === 'number';
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ export function createServer(
|
||||
}
|
||||
|
||||
export async function startServer(server: http.Server): Promise<string> {
|
||||
let portTimer: NodeJS.Timer;
|
||||
let portTimer: NodeJS.Timeout;
|
||||
|
||||
function cancelPortTimer() {
|
||||
clearTimeout(portTimer);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { ClientDuplexStream } from '@grpc/grpc-js';
|
||||
import type { ClientReadableStream } from '@grpc/grpc-js';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
@@ -9,9 +9,9 @@ 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 { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
|
||||
import { UUID } from '@theia/core/shared/@phosphor/coreutils';
|
||||
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
||||
import { isDeepStrictEqual } from 'util';
|
||||
import { v4 } from 'uuid';
|
||||
import { Unknown } from '../common/nls';
|
||||
import {
|
||||
Board,
|
||||
@@ -30,9 +30,9 @@ import type { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/
|
||||
import { CoreClientAware } from './core-client-provider';
|
||||
import { ServiceError } from './service-error';
|
||||
|
||||
type Duplex = ClientDuplexStream<BoardListWatchRequest, BoardListWatchResponse>;
|
||||
type Stream = ClientReadableStream<BoardListWatchResponse>;
|
||||
interface StreamWrapper extends Disposable {
|
||||
readonly stream: Duplex;
|
||||
readonly stream: Stream;
|
||||
readonly uuid: string; // For logging only
|
||||
}
|
||||
|
||||
@@ -121,34 +121,15 @@ export class BoardDiscovery
|
||||
return Disposable.create(() => clearTimeout(timer));
|
||||
}
|
||||
|
||||
private async requestStartWatch(
|
||||
req: BoardListWatchRequest,
|
||||
duplex: Duplex
|
||||
): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (
|
||||
!duplex.write(req, (err: Error | undefined) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
})
|
||||
) {
|
||||
duplex.once('drain', resolve);
|
||||
} else {
|
||||
process.nextTick(resolve);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async createWrapper(
|
||||
client: ArduinoCoreServiceClient
|
||||
client: ArduinoCoreServiceClient,
|
||||
req: BoardListWatchRequest
|
||||
): Promise<StreamWrapper> {
|
||||
if (this.wrapper) {
|
||||
throw new Error(`Duplex was already set.`);
|
||||
}
|
||||
const stream = client
|
||||
.boardListWatch()
|
||||
.boardListWatch(req)
|
||||
.on('end', () => {
|
||||
this.logger.info('received end');
|
||||
this.onStreamDidEndEmitter.fire();
|
||||
@@ -168,7 +149,7 @@ export class BoardDiscovery
|
||||
});
|
||||
const wrapper = {
|
||||
stream,
|
||||
uuid: v4(),
|
||||
uuid: UUID.uuid4(),
|
||||
dispose: () => {
|
||||
this.logger.info('disposing requesting cancel');
|
||||
// Cancelling the stream will kill the discovery `builtin:mdns-discovery process`.
|
||||
@@ -202,14 +183,11 @@ export class BoardDiscovery
|
||||
this.watching = new Deferred();
|
||||
this.logger.info('start new deferred');
|
||||
const { client, instance } = await this.coreClient;
|
||||
const wrapper = await this.createWrapper(client);
|
||||
wrapper.stream.on('data', (resp) => this.onBoardListWatchResponse(resp));
|
||||
this.logger.info('start request start watch');
|
||||
await this.requestStartWatch(
|
||||
new BoardListWatchRequest().setInstance(instance),
|
||||
wrapper.stream
|
||||
const wrapper = await this.createWrapper(
|
||||
client,
|
||||
new BoardListWatchRequest().setInstance(instance)
|
||||
);
|
||||
this.logger.info('start requested start watch');
|
||||
wrapper.stream.on('data', (resp) => this.onBoardListWatchResponse(resp));
|
||||
this.watching.resolve();
|
||||
this.logger.info('start resolved watching');
|
||||
}
|
||||
|
@@ -1,27 +1,38 @@
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { notEmpty } from '@theia/core/lib/common/objects';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
BoardsService,
|
||||
Installable,
|
||||
BoardsPackage,
|
||||
Board,
|
||||
BoardDetails,
|
||||
BoardSearch,
|
||||
BoardUserField,
|
||||
BoardWithPackage,
|
||||
BoardsPackage,
|
||||
BoardsService,
|
||||
CheckDebugEnabledParams,
|
||||
ConfigOption,
|
||||
ConfigValue,
|
||||
DetectedPorts,
|
||||
Installable,
|
||||
NotificationServiceServer,
|
||||
Programmer,
|
||||
ResponseService,
|
||||
NotificationServiceServer,
|
||||
DetectedPorts,
|
||||
BoardWithPackage,
|
||||
BoardUserField,
|
||||
BoardSearch,
|
||||
sortComponents,
|
||||
SortGroup,
|
||||
platformInstallFailed,
|
||||
createPlatformIdentifier,
|
||||
platformIdentifierEquals,
|
||||
platformInstallFailed,
|
||||
sortComponents,
|
||||
} from '../common/protocol';
|
||||
import { BoardDiscovery } from './board-discovery';
|
||||
import {
|
||||
BoardDetailsRequest,
|
||||
BoardDetailsResponse,
|
||||
BoardListAllRequest,
|
||||
BoardListAllResponse,
|
||||
BoardSearchRequest,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/board_pb';
|
||||
import { Platform } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
||||
import {
|
||||
PlatformInstallRequest,
|
||||
PlatformListRequest,
|
||||
@@ -30,25 +41,16 @@ import {
|
||||
PlatformSearchResponse,
|
||||
PlatformUninstallRequest,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/core_pb';
|
||||
import { Platform } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
||||
import { BoardDiscovery } from './board-discovery';
|
||||
import { CoreClientAware } from './core-client-provider';
|
||||
import {
|
||||
BoardDetailsRequest,
|
||||
BoardDetailsResponse,
|
||||
BoardListAllRequest,
|
||||
BoardListAllResponse,
|
||||
BoardSearchRequest,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/board_pb';
|
||||
import { IsDebugSupportedRequest } from './cli-protocol/cc/arduino/cli/commands/v1/debug_pb';
|
||||
import {
|
||||
ListProgrammersAvailableForUploadRequest,
|
||||
ListProgrammersAvailableForUploadResponse,
|
||||
SupportedUserFieldsRequest,
|
||||
SupportedUserFieldsResponse,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
|
||||
import { CoreClientAware } from './core-client-provider';
|
||||
import { ExecuteWithProgress } from './grpc-progressible';
|
||||
import { ServiceError } from './service-error';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceImpl
|
||||
@@ -99,8 +101,6 @@ export class BoardsServiceImpl
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const debuggingSupported = detailsResp.getDebuggingSupported();
|
||||
|
||||
const requiredTools = detailsResp.getToolsDependenciesList().map((t) => ({
|
||||
name: t.getName(),
|
||||
packager: t.getPackager(),
|
||||
@@ -146,6 +146,7 @@ export class BoardsServiceImpl
|
||||
platform: p.getPlatform(),
|
||||
}
|
||||
);
|
||||
const defaultProgrammerId = detailsResp.getDefaultProgrammerId();
|
||||
|
||||
let VID = 'N/A';
|
||||
let PID = 'N/A';
|
||||
@@ -164,13 +165,43 @@ export class BoardsServiceImpl
|
||||
requiredTools,
|
||||
configOptions,
|
||||
programmers,
|
||||
debuggingSupported,
|
||||
VID,
|
||||
PID,
|
||||
buildProperties,
|
||||
...(defaultProgrammerId ? { defaultProgrammerId } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
async checkDebugEnabled(params: CheckDebugEnabledParams): Promise<string> {
|
||||
const { fqbn, programmer } = params;
|
||||
const { client, instance } = await this.coreClient;
|
||||
const req = new IsDebugSupportedRequest()
|
||||
.setInstance(instance)
|
||||
.setFqbn(fqbn)
|
||||
.setProgrammer(programmer);
|
||||
try {
|
||||
const debugFqbn = await new Promise<string>((resolve, reject) =>
|
||||
client.isDebugSupported(req, (err, resp) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (resp.getDebuggingSupported()) {
|
||||
const debugFqbn = resp.getDebugFqbn();
|
||||
if (debugFqbn) {
|
||||
resolve(debugFqbn);
|
||||
}
|
||||
}
|
||||
reject(new Error(`Debugging is not supported.`));
|
||||
})
|
||||
);
|
||||
return debugFqbn;
|
||||
} catch (err) {
|
||||
console.error(`Failed to get debug config: ${fqbn}, ${programmer}`, err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async getBoardPackage(options: {
|
||||
id: string;
|
||||
}): Promise<BoardsPackage | undefined> {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user