mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-10-04 09:08:31 +00:00
Compare commits
113 Commits
0.1.0
...
2.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f34f594653 | ||
![]() |
1dc7a89dd9 | ||
![]() |
1280a344a7 | ||
![]() |
f1c80041fe | ||
![]() |
ec1abcc989 | ||
![]() |
1c03d12165 | ||
![]() |
9180f4e378 | ||
![]() |
516c79276c | ||
![]() |
e639d7da06 | ||
![]() |
754097877b | ||
![]() |
b847cff615 | ||
![]() |
b3deb2fd34 | ||
![]() |
b2641f56be | ||
![]() |
ba8885c8c8 | ||
![]() |
3e92567d52 | ||
![]() |
19613de1b4 | ||
![]() |
01ef138d9a | ||
![]() |
39b8a602c7 | ||
![]() |
57c50fefe3 | ||
![]() |
23877f162c | ||
![]() |
96f0722d56 | ||
![]() |
48c6c53b9b | ||
![]() |
b8647f16ad | ||
![]() |
cfe9e8ec95 | ||
![]() |
291179489f | ||
![]() |
71cfa06fc2 | ||
![]() |
68b1f8d4f2 | ||
![]() |
e957ac4331 | ||
![]() |
b65867d2f4 | ||
![]() |
a8e60698a8 | ||
![]() |
52b0fd35a3 | ||
![]() |
b1ab6df8b7 | ||
![]() |
911875665d | ||
![]() |
113fe38850 | ||
![]() |
770e0b592a | ||
![]() |
79bf0a123f | ||
![]() |
8eaf03a299 | ||
![]() |
f36d261dcd | ||
![]() |
a99093624f | ||
![]() |
1f544b2656 | ||
![]() |
1742c53015 | ||
![]() |
e33af0d78a | ||
![]() |
24dfffa976 | ||
![]() |
6626701bc9 | ||
![]() |
66b711f43c | ||
![]() |
c6b125011e | ||
![]() |
f6b5dd24e2 | ||
![]() |
14919bba1b | ||
![]() |
0b89cc4a3b | ||
![]() |
284181b874 | ||
![]() |
db2967084f | ||
![]() |
1b6d9eccdc | ||
![]() |
258b1e903e | ||
![]() |
00a3ee34c8 | ||
![]() |
f1bffaab2d | ||
![]() |
3191a09562 | ||
![]() |
40905a058c | ||
![]() |
e7b1a27401 | ||
![]() |
4d5a046aa8 | ||
![]() |
c024a8d3d1 | ||
![]() |
7696e2c4c9 | ||
![]() |
1acf13c397 | ||
![]() |
cff2c95684 | ||
![]() |
1a531db0b7 | ||
![]() |
2e00e2db35 | ||
![]() |
ca1b288706 | ||
![]() |
41eeb337f9 | ||
![]() |
39b2e49edb | ||
![]() |
138afbf7fd | ||
![]() |
01e42dafde | ||
![]() |
2831acc5b5 | ||
![]() |
acbb7d32b2 | ||
![]() |
781747fe80 | ||
![]() |
874c3efa2c | ||
![]() |
7b364ebe60 | ||
![]() |
a96449f557 | ||
![]() |
c78e474790 | ||
![]() |
30136b0ef2 | ||
![]() |
53b06aef67 | ||
![]() |
6535c70686 | ||
![]() |
6ff58ebe7c | ||
![]() |
7068b9b1d3 | ||
![]() |
e755a1cd7e | ||
![]() |
def93ea32f | ||
![]() |
5f5193932f | ||
![]() |
f26dae185b | ||
![]() |
fbebfc7cca | ||
![]() |
c3eb3e4622 | ||
![]() |
30421f0de4 | ||
![]() |
daa25794ef | ||
![]() |
ec8df37c2d | ||
![]() |
cb24571eeb | ||
![]() |
2f8e28b296 | ||
![]() |
524fbbdf40 | ||
![]() |
7a37aa2e2f | ||
![]() |
56ff86629c | ||
![]() |
1c9fcd0cdf | ||
![]() |
b5d7c3b45d | ||
![]() |
525e688d70 | ||
![]() |
d7f4d0c18e | ||
![]() |
6ae7404092 | ||
![]() |
6b1b9c0524 | ||
![]() |
717db95c90 | ||
![]() |
7536c3a485 | ||
![]() |
ec7df884ee | ||
![]() |
24b6d84d27 | ||
![]() |
4435696949 | ||
![]() |
6aa3ff8044 | ||
![]() |
4b44113f2c | ||
![]() |
c6ad0f582a | ||
![]() |
4b8b468e53 | ||
![]() |
ba319b23d9 | ||
![]() |
70278fed6f |
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. Windows]
|
||||||
|
- Version: [e.g. 2.0.0]
|
||||||
|
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
135
.github/workflows/build.yml
vendored
135
.github/workflows/build.yml
vendored
@@ -1,14 +1,15 @@
|
|||||||
name: Arduino Pro IDE
|
name: Arduino IDE
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
tags:
|
tags:
|
||||||
- '[0-9]+.[0-9]+.[0-9]+*'
|
- '[0-9]+.[0-9]+.[0-9]+*'
|
||||||
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 3 * * *' # run every day at 3AM (https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule)
|
- cron: '0 3 * * *' # run every day at 3AM (https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule)
|
||||||
|
|
||||||
@@ -18,23 +19,20 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
config:
|
config:
|
||||||
- os: windows-2016
|
- os: windows-latest
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
# - os: rsora-rpi-arm # self-hosted armhf
|
|
||||||
runs-on: ${{ matrix.config.os }}
|
runs-on: ${{ matrix.config.os }}
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
env:
|
|
||||||
CERTIFICATE_PATH: /tmp/macos_signing_certificate.p12
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Node.js 10.x
|
- name: Install Node.js 12.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: '10.x'
|
node-version: '12.14.1'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- name: Install Python 2.7
|
- name: Install Python 2.7
|
||||||
@@ -42,31 +40,33 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: '2.7'
|
python-version: '2.7'
|
||||||
|
|
||||||
- name: Generate signing certificate file [macOS]
|
|
||||||
if: runner.OS == 'macOS'
|
|
||||||
run: |
|
|
||||||
# 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
|
|
||||||
echo "${{ secrets.APPLE_SIGNING_CERTIFICATE_P12 }}" | base64 --decode > "${{ env.CERTIFICATE_PATH }}"
|
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
AC_USERNAME: ${{ secrets.AC_USERNAME }}
|
AC_USERNAME: ${{ secrets.AC_USERNAME }}
|
||||||
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
|
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
IS_NIGHTLY: ${{ github.event_name == 'schedule' }}
|
IS_NIGHTLY: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }}
|
||||||
IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') }}
|
IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
run: |
|
run: |
|
||||||
# electron-builder will try to sign during the Windows job if these environment variables are defined
|
# See: https://www.electron.build/code-signing
|
||||||
if [ "${{ runner.OS }}" = "macOS" ]; then
|
if [ "${{ runner.OS }}" = "macOS" ]; then
|
||||||
# See: https://www.electron.build/code-signing
|
export CSC_LINK="${{ runner.temp }}/signing_certificate.p12"
|
||||||
export CSC_LINK="${{ env.CERTIFICATE_PATH }}"
|
# 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
|
||||||
|
echo "${{ secrets.APPLE_SIGNING_CERTIFICATE_P12 }}" | base64 --decode > "$CSC_LINK"
|
||||||
|
|
||||||
export CSC_KEY_PASSWORD="${{ secrets.KEYCHAIN_PASSWORD }}"
|
export CSC_KEY_PASSWORD="${{ secrets.KEYCHAIN_PASSWORD }}"
|
||||||
|
|
||||||
|
elif [ "${{ runner.OS }}" = "Windows" ]; then
|
||||||
|
export CSC_LINK="${{ runner.temp }}/signing_certificate.pfx"
|
||||||
|
echo "${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PFX }}" | base64 --decode > "$CSC_LINK"
|
||||||
|
|
||||||
|
export CSC_KEY_PASSWORD="${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
yarn --cwd ./electron/packager/
|
yarn --cwd ./electron/packager/
|
||||||
yarn --cwd ./electron/packager/ package
|
yarn --cwd ./electron/packager/ package
|
||||||
|
|
||||||
@@ -76,9 +76,51 @@ jobs:
|
|||||||
name: build-artifacts
|
name: build-artifacts
|
||||||
path: electron/build/dist/build-artifacts/
|
path: electron/build/dist/build-artifacts/
|
||||||
|
|
||||||
publish:
|
changelog:
|
||||||
needs: build
|
needs: build
|
||||||
if: github.event_name == 'schedule'
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
BODY: ${{ steps.changelog.outputs.BODY }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
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/') }}
|
||||||
|
run: |
|
||||||
|
export LATEST_TAG=$(git describe --abbrev=0)
|
||||||
|
export GIT_LOG=$(git log --pretty=" - %s [%h]" $LATEST_TAG..HEAD | sed 's/ *$//g')
|
||||||
|
if [ "$IS_RELEASE" = true ]; then
|
||||||
|
export BODY=$(echo -e "$GIT_LOG")
|
||||||
|
else
|
||||||
|
export LATEST_TAG_WITH_LINK=$(echo "[$LATEST_TAG](https://github.com/arduino/arduino-ide/releases/tag/$LATEST_TAG)")
|
||||||
|
if [ -z "$GIT_LOG" ]; then
|
||||||
|
export BODY="There were no changes since version $LATEST_TAG_WITH_LINK."
|
||||||
|
else
|
||||||
|
export BODY=$(echo -e "Changes since version $LATEST_TAG_WITH_LINK:\n$GIT_LOG")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo -e "$BODY"
|
||||||
|
OUTPUT_SAFE_BODY="${BODY//'%'/'%25'}"
|
||||||
|
OUTPUT_SAFE_BODY="${OUTPUT_SAFE_BODY//$'\n'/'%0A'}"
|
||||||
|
OUTPUT_SAFE_BODY="${OUTPUT_SAFE_BODY//$'\r'/'%0D'}"
|
||||||
|
echo "::set-output name=BODY::$OUTPUT_SAFE_BODY"
|
||||||
|
echo "$BODY" > CHANGELOG.txt
|
||||||
|
|
||||||
|
- name: Upload Changelog [GitHub Actions]
|
||||||
|
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: build-artifacts
|
||||||
|
path: CHANGELOG.txt
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: changelog
|
||||||
|
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download [GitHub Actions]
|
- name: Download [GitHub Actions]
|
||||||
@@ -88,16 +130,17 @@ jobs:
|
|||||||
path: build-artifacts
|
path: build-artifacts
|
||||||
|
|
||||||
- name: Publish Nightly [S3]
|
- name: Publish Nightly [S3]
|
||||||
uses: kittaakos/upload-s3-action@v0.0.1
|
uses: docker://plugins/s3
|
||||||
with:
|
env:
|
||||||
aws_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
PLUGIN_SOURCE: "build-artifacts/*"
|
||||||
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
PLUGIN_STRIP_PREFIX: "build-artifacts/"
|
||||||
aws_bucket: ${{ secrets.DOWNLOADS_BUCKET }}
|
PLUGIN_TARGET: "/arduino-ide/nightly"
|
||||||
source_dir: build-artifacts/
|
PLUGIN_BUCKET: ${{ secrets.DOWNLOADS_BUCKET }}
|
||||||
destination_dir: arduino-pro-ide/nightly/
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
|
||||||
release:
|
release:
|
||||||
needs: build
|
needs: changelog
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -107,27 +150,27 @@ jobs:
|
|||||||
name: build-artifacts
|
name: build-artifacts
|
||||||
path: build-artifacts
|
path: build-artifacts
|
||||||
|
|
||||||
- name: Create Release [GitHub]
|
- name: Get Tag
|
||||||
uses: actions/create-release@v1
|
id: tag_name
|
||||||
env:
|
run: |
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
echo ::set-output name=TAG_NAME::${GITHUB_REF#refs/tags/}
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: Publish Release [GitHub]
|
- name: Publish Release [GitHub]
|
||||||
uses: svenstaro/upload-release-action@v1-release
|
uses: svenstaro/upload-release-action@2.2.0
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
release_name: ${{ steps.tag_name.outputs.TAG_NAME }}
|
||||||
file: build-artifacts/*
|
file: build-artifacts/*
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file_glob: true
|
file_glob: true
|
||||||
|
body: ${{ needs.changelog.outputs.BODY }}
|
||||||
|
|
||||||
- name: Publish Release [S3]
|
- name: Publish Release [S3]
|
||||||
uses: kittaakos/upload-s3-action@v0.0.1
|
uses: docker://plugins/s3
|
||||||
with:
|
env:
|
||||||
aws_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
PLUGIN_SOURCE: "build-artifacts/*"
|
||||||
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
PLUGIN_STRIP_PREFIX: "build-artifacts/"
|
||||||
aws_bucket: ${{ secrets.DOWNLOADS_BUCKET }}
|
PLUGIN_TARGET: "/arduino-ide"
|
||||||
source_dir: build-artifacts/
|
PLUGIN_BUCKET: ${{ secrets.DOWNLOADS_BUCKET }}
|
||||||
destination_dir: arduino-pro-ide/
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
122
.github/workflows/check-certificates.yml
vendored
Normal file
122
.github/workflows/check-certificates.yml
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
name: Check for issues with signing certificates
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# run every 10 hours
|
||||||
|
- cron: "0 */10 * * *"
|
||||||
|
# workflow_dispatch event allows the workflow to be triggered manually.
|
||||||
|
# This could be used to run an immediate check after updating certificate secrets.
|
||||||
|
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Begin notifications when there are less than this many days remaining before expiration
|
||||||
|
EXPIRATION_WARNING_PERIOD: 30
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-certificates:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
certificate:
|
||||||
|
- identifier: macOS signing certificate # Text used to identify the certificate in notifications
|
||||||
|
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12 # The name of the secret that contains the certificate
|
||||||
|
password-secret: KEYCHAIN_PASSWORD # The name of the secret that contains the certificate password
|
||||||
|
- identifier: Windows signing certificate
|
||||||
|
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX
|
||||||
|
password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set certificate path environment variable
|
||||||
|
run: |
|
||||||
|
# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable
|
||||||
|
echo "CERTIFICATE_PATH=${{ runner.temp }}/certificate.p12" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Decode certificate
|
||||||
|
env:
|
||||||
|
CERTIFICATE: ${{ secrets[matrix.certificate.certificate-secret] }}
|
||||||
|
run: |
|
||||||
|
echo "${{ env.CERTIFICATE }}" | base64 --decode > "${{ env.CERTIFICATE_PATH }}"
|
||||||
|
|
||||||
|
- name: Verify certificate
|
||||||
|
env:
|
||||||
|
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }}
|
||||||
|
run: |
|
||||||
|
(
|
||||||
|
openssl pkcs12 \
|
||||||
|
-in "${{ env.CERTIFICATE_PATH }}" \
|
||||||
|
-noout -passin env:CERTIFICATE_PASSWORD
|
||||||
|
) || (
|
||||||
|
echo "::error::Verification of ${{ matrix.certificate.identifier }} failed!!!"
|
||||||
|
exit 1
|
||||||
|
)
|
||||||
|
|
||||||
|
# See: https://github.com/rtCamp/action-slack-notify
|
||||||
|
- name: Slack notification of certificate verification failure
|
||||||
|
if: failure()
|
||||||
|
uses: rtCamp/action-slack-notify@v2.1.0
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.TEAM_TOOLING_CHANNEL_SLACK_WEBHOOK }}
|
||||||
|
SLACK_MESSAGE: |
|
||||||
|
:warning::warning::warning::warning:
|
||||||
|
WARNING: ${{ github.repository }} ${{ matrix.certificate.identifier }} verification failed!!!
|
||||||
|
:warning::warning::warning::warning:
|
||||||
|
SLACK_COLOR: danger
|
||||||
|
MSG_MINIMAL: true
|
||||||
|
|
||||||
|
- name: Get days remaining before certificate expiration date
|
||||||
|
env:
|
||||||
|
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }}
|
||||||
|
id: get-days-before-expiration
|
||||||
|
run: |
|
||||||
|
EXPIRATION_DATE="$(
|
||||||
|
(
|
||||||
|
openssl pkcs12 \
|
||||||
|
-in "${{ env.CERTIFICATE_PATH }}" \
|
||||||
|
-clcerts \
|
||||||
|
-nodes \
|
||||||
|
-passin env:CERTIFICATE_PASSWORD
|
||||||
|
) | (
|
||||||
|
openssl x509 \
|
||||||
|
-noout \
|
||||||
|
-enddate
|
||||||
|
) | (
|
||||||
|
grep \
|
||||||
|
--max-count=1 \
|
||||||
|
--only-matching \
|
||||||
|
--perl-regexp \
|
||||||
|
'notAfter=(\K.*)'
|
||||||
|
)
|
||||||
|
)"
|
||||||
|
|
||||||
|
DAYS_BEFORE_EXPIRATION="$((($(date --utc --date="$EXPIRATION_DATE" +%s) - $(date --utc +%s)) / 60 / 60 / 24))"
|
||||||
|
|
||||||
|
# Display the expiration information in the log
|
||||||
|
echo "Certificate expiration date: $EXPIRATION_DATE"
|
||||||
|
echo "Days remaining before expiration: $DAYS_BEFORE_EXPIRATION"
|
||||||
|
|
||||||
|
echo "::set-output name=days::$DAYS_BEFORE_EXPIRATION"
|
||||||
|
|
||||||
|
- name: Check if expiration notification period has been reached
|
||||||
|
id: check-expiration
|
||||||
|
run: |
|
||||||
|
if [[ ${{ steps.get-days-before-expiration.outputs.days }} -lt ${{ env.EXPIRATION_WARNING_PERIOD }} ]]; then
|
||||||
|
echo "::error::${{ matrix.certificate.identifier }} will expire in ${{ steps.get-days-before-expiration.outputs.days }} days!!!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Slack notification of pending certificate expiration
|
||||||
|
# Don't send spurious expiration notification if verification fails
|
||||||
|
if: failure() && steps.check-expiration.outcome == 'failure'
|
||||||
|
uses: rtCamp/action-slack-notify@v2.1.0
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.TEAM_TOOLING_CHANNEL_SLACK_WEBHOOK }}
|
||||||
|
SLACK_MESSAGE: |
|
||||||
|
:warning::warning::warning::warning:
|
||||||
|
WARNING: ${{ github.repository }} ${{ matrix.certificate.identifier }} will expire in ${{ steps.get-days-before-expiration.outputs.days }} days!!!
|
||||||
|
:warning::warning::warning::warning:
|
||||||
|
SLACK_COLOR: danger
|
||||||
|
MSG_MINIMAL: true
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ node_modules/
|
|||||||
lib/
|
lib/
|
||||||
downloads/
|
downloads/
|
||||||
build/
|
build/
|
||||||
|
Examples/
|
||||||
!electron/build/
|
!electron/build/
|
||||||
src-gen/
|
src-gen/
|
||||||
*webpack.config.js
|
*webpack.config.js
|
||||||
|
24
.gitpod.yml
24
.gitpod.yml
@@ -1,24 +0,0 @@
|
|||||||
image:
|
|
||||||
file: Dockerfile
|
|
||||||
|
|
||||||
ports:
|
|
||||||
- port: 3000
|
|
||||||
onOpen: open-preview
|
|
||||||
- port: 5900
|
|
||||||
onOpen: ignore
|
|
||||||
- port: 6080
|
|
||||||
onOpen: ignore
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- init: >
|
|
||||||
yarn &&
|
|
||||||
yarn --cwd ./browser-app start
|
|
||||||
|
|
||||||
github:
|
|
||||||
prebuilds:
|
|
||||||
master: true
|
|
||||||
branches: true
|
|
||||||
pullRequests: true
|
|
||||||
pullRequestsFromForks: true
|
|
||||||
addComment: false
|
|
||||||
addBadge: false
|
|
46
.vscode/tasks.json
vendored
46
.vscode/tasks.json
vendored
@@ -4,7 +4,18 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "Arduino Pro IDE - Start Browser App",
|
"label": "Arduino IDE - Rebuild Electron App",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "yarn rebuild:browser && yarn rebuild:electron",
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "new",
|
||||||
|
"clear": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Arduino IDE - Start Browser App",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "yarn --cwd ./browser-app start",
|
"command": "yarn --cwd ./browser-app start",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
@@ -15,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Arduino Pro IDE - Watch IDE Extension",
|
"label": "Arduino IDE - Watch IDE Extension",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "yarn --cwd ./arduino-ide-extension watch",
|
"command": "yarn --cwd ./arduino-ide-extension watch",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
@@ -24,20 +35,9 @@
|
|||||||
"panel": "new",
|
"panel": "new",
|
||||||
"clear": false
|
"clear": false
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{
|
{
|
||||||
"label": "Arduino Pro IDE - Watch Debugger Extension",
|
"label": "Arduino IDE - Watch Browser App",
|
||||||
"type": "shell",
|
|
||||||
"command": "yarn --cwd ./arduino-debugger-extension watch",
|
|
||||||
"group": "build",
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "always",
|
|
||||||
"panel": "new",
|
|
||||||
"clear": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Arduino Pro IDE - Watch Browser App",
|
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "yarn --cwd ./browser-app watch",
|
"command": "yarn --cwd ./browser-app watch",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Arduino Pro IDE - Watch Electron App",
|
"label": "Arduino IDE - Watch Electron App",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "yarn --cwd ./electron-app watch",
|
"command": "yarn --cwd ./electron-app watch",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
@@ -59,21 +59,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Arduino Pro IDE - Watch All [Browser]",
|
"label": "Arduino IDE - Watch All [Browser]",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"Arduino Pro IDE - Watch IDE Extension",
|
"Arduino IDE - Watch IDE Extension",
|
||||||
"Arduino Pro IDE - Watch Debugger Extension",
|
"Arduino IDE - Watch Browser App"
|
||||||
"Arduino Pro IDE - Watch Browser App"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Arduino Pro IDE - Watch All [Electron]",
|
"label": "Arduino IDE - Watch All [Electron]",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"Arduino Pro IDE - Watch IDE Extension",
|
"Arduino IDE - Watch IDE Extension",
|
||||||
"Arduino Pro IDE - Watch Debugger Extension",
|
"Arduino IDE - Watch Electron App"
|
||||||
"Arduino Pro IDE - Watch Electron App"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
98
README.md
98
README.md
@@ -1,46 +1,50 @@
|
|||||||
# Arduino Pro IDE
|
# Arduino IDE
|
||||||
|
|
||||||
[](https://github.com/bcmi-labs/arduino-editor/actions?query=workflow%3A%22Arduino+Pro+IDE%22)
|
[](https://github.com/arduino/arduino-ide/actions?query=workflow%3A%22Arduino+IDE%22)
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
You can download the latest version of the Arduino Pro IDE application for the supported platforms from the [GitHub release page](https://github.com/arduino/arduino-pro-ide/releases) or following the links in the following table.
|
You can download the latest version of the Arduino IDE application for the supported platforms from the [GitHub release page](https://github.com/arduino/arduino-ide/releases) or following the links in the following table.
|
||||||
|
|
||||||
#### Latest version
|
#### Latest version
|
||||||
|
|
||||||
Platform | 32 bit | 64 bit |
|
Platform | 32 bit | 64 bit |
|
||||||
--------- | ------------------------ | ------------------------ |
|
--------- | ------------------------ | ------------------------------------------------------------------------------ |
|
||||||
Linux | | [Linux 64 bit] |
|
Linux | | [Linux 64 bit] |
|
||||||
Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...] |
|
Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...] |
|
||||||
Windows | | [Windows 64 bit] |
|
Windows | | [Windows 64 bit installer]<br />[Windows 64 bit MSI]<br />[Windows 64 bit ZIP] |
|
||||||
macOS | | [macOS 64 bit] |
|
macOS | | [macOS 64 bit] |
|
||||||
|
|
||||||
[🚧 Work in progress...]: https://github.com/arduino/arduino-pro-ide/issues/287
|
[🚧 Work in progress...]: https://github.com/arduino/arduino-ide/issues/287
|
||||||
[Linux 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/arduino-pro-ide_latest_Linux_64bit.zip
|
[Linux 64 bit]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_Linux_64bit.zip
|
||||||
[Windows 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/arduino-pro-ide_latest_Windows_64bit.zip
|
[Windows 64 bit installer]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_Windows_64bit.exe
|
||||||
[macOS 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/arduino-pro-ide_latest_macOS_64bit.dmg
|
[Windows 64 bit MSI]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_Windows_64bit.msi
|
||||||
|
[Windows 64 bit ZIP]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_Windows_64bit.zip
|
||||||
|
[macOS 64 bit]: https://downloads.arduino.cc/arduino-ide/arduino-ide_latest_macOS_64bit.dmg
|
||||||
|
|
||||||
#### Previous versions
|
#### Previous versions
|
||||||
|
|
||||||
These are available from the [GitHub releases page](https://github.com/arduino/arduino-pro-ide/releases).
|
These are available from the [GitHub releases page](https://github.com/arduino/arduino-ide/releases).
|
||||||
|
|
||||||
#### Nightly builds
|
#### Nightly builds
|
||||||
|
|
||||||
These builds are generated every day at 03:00 GMT from the `master` branch and
|
These builds are generated every day at 03:00 GMT from the `main` branch and
|
||||||
should be considered unstable. In order to get the latest nightly build
|
should be considered unstable. In order to get the latest nightly build
|
||||||
available for the supported platform, use the following links:
|
available for the supported platform, use the following links:
|
||||||
|
|
||||||
Platform | 32 bit | 64 bit |
|
Platform | 32 bit | 64 bit |
|
||||||
--------- | ------------------------ | ------------------------ |
|
--------- | ------------------------ | ------------------------------------------------------------------------------------------------------ |
|
||||||
Linux | | [Nightly Linux 64 bit] |
|
Linux | | [Nightly Linux 64 bit] |
|
||||||
Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...] |
|
Linux ARM | [🚧 Work in progress...] | [🚧 Work in progress...] |
|
||||||
Windows | | [Nightly Windows 64 bit] |
|
Windows | | [Nightly Windows 64 bit installer]<br />[Nightly Windows 64 bit MSI]<br />[Nightly Windows 64 bit ZIP] |
|
||||||
macOS | | [Nightly macOS 64 bit] |
|
macOS | | [Nightly macOS 64 bit] |
|
||||||
|
|
||||||
[🚧 Work in progress...]: https://github.com/arduino/arduino-pro-ide/issues/287
|
[🚧 Work in progress...]: https://github.com/arduino/arduino-ide/issues/287
|
||||||
[Nightly Linux 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/nightly/arduino-pro-ide_nightly-latest_Linux_64bit.zip
|
[Nightly Linux 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Linux_64bit.zip
|
||||||
[Nightly Windows 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/nightly/arduino-pro-ide_nightly-latest_Windows_64bit.zip
|
[Nightly Windows 64 bit installer]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.exe
|
||||||
[Nightly macOS 64 bit]: https://downloads.arduino.cc/arduino-pro-ide/nightly/arduino-pro-ide_nightly-latest_macOS_64bit.dmg
|
[Nightly Windows 64 bit MSI]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.msi
|
||||||
|
[Nightly Windows 64 bit ZIP]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_Windows_64bit.zip
|
||||||
|
[Nightly macOS 64 bit]: https://downloads.arduino.cc/arduino-ide/nightly/arduino-ide_nightly-latest_macOS_64bit.dmg
|
||||||
|
|
||||||
> These links return an HTTP `302: Found` response, redirecting to latest
|
> These links return an HTTP `302: Found` response, redirecting to latest
|
||||||
generated builds by replacing `latest` with the latest available build
|
generated builds by replacing `latest` with the latest available build
|
||||||
@@ -50,7 +54,7 @@ macOS | | [Nightly macOS 64 bit] |
|
|||||||
### Build from source
|
### Build from source
|
||||||
|
|
||||||
If you’re familiar with TypeScript, the [Theia IDE](https://theia-ide.org/), and if you want to contribute to the
|
If you’re familiar with TypeScript, the [Theia IDE](https://theia-ide.org/), and if you want to contribute to the
|
||||||
project, you should be able to build the Arduino Pro IDE locally. Please refer to the [Theia IDE prerequisites](https://github.com/theia-ide/theia/blob/master/doc/) documentation for the setup instructions.
|
project, you should be able to build the Arduino IDE locally. Please refer to the [Theia IDE prerequisites](https://github.com/theia-ide/theia/blob/master/doc/) documentation for the setup instructions.
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
```sh
|
```sh
|
||||||
@@ -71,8 +75,8 @@ yarn start
|
|||||||
|
|
||||||
This project is built on [GitHub Actions](https://github.com/bcmi-labs/arduino-editor/actions?query=workflow%3A%22Arduino+Pro+IDE%22).
|
This project is built on [GitHub Actions](https://github.com/bcmi-labs/arduino-editor/actions?query=workflow%3A%22Arduino+Pro+IDE%22).
|
||||||
|
|
||||||
- _Snapshot_ builds run when changes are pushed to the `master` branch, or when a PR is created against the `master` branch. For the sake of the review and verification process, the build artifacts can be downloaded from the GitHub Actions page. Note: [due to a limitation](https://github.com/actions/upload-artifact/issues/80#issuecomment-630030144) with the GH Actions UI, you cannot download a particular build, but you have to get all together inside the `build-artifacts.zip`.
|
- _Snapshot_ builds run when changes are pushed to the `main` branch, or when a PR is created against the `main` branch. For the sake of the review and verification process, the build artifacts can be downloaded from the GitHub Actions page. Note: [due to a limitation](https://github.com/actions/upload-artifact/issues/80#issuecomment-630030144) with the GH Actions UI, you cannot download a particular build, but you have to get all together inside the `build-artifacts.zip`.
|
||||||
- _Nightly_ builds run every day at 03:00 GMT from the `master` branch.
|
- _Nightly_ builds run every day at 03:00 GMT from the `main` branch.
|
||||||
- _Release_ builds run when a new tag is pushed to the remote. The tag must follow the [semver](https://semver.org/). For instance, `1.2.3` is a correct tag, but `v2.3.4` won't work. Steps to trigger a new release build:
|
- _Release_ builds run when a new tag is pushed to the remote. The tag must follow the [semver](https://semver.org/). For instance, `1.2.3` is a correct tag, but `v2.3.4` won't work. Steps to trigger a new release build:
|
||||||
- Create a local tag:
|
- Create a local tag:
|
||||||
```sh
|
```sh
|
||||||
@@ -83,6 +87,38 @@ This project is built on [GitHub Actions](https://github.com/bcmi-labs/arduino-e
|
|||||||
git push origin 1.2.3
|
git push origin 1.2.3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Creating a GH release
|
||||||
|
This section guides you through how to create a new release. Let's assume the current version is `0.1.3` and you want to release `0.2.0`.
|
||||||
|
|
||||||
|
- Make sure the `main` state represents what you want to release and you're on `main`.
|
||||||
|
- Prepare a release-candidate build on a branch:
|
||||||
|
```bash
|
||||||
|
git branch 0.2.0-rc \
|
||||||
|
&& git checkout 0.2.0-rc
|
||||||
|
```
|
||||||
|
- Bump up the version number. It must be a valid [semver](https://semver.org/) and must be greater than the current one:
|
||||||
|
```bash
|
||||||
|
yarn update:version 0.2.0
|
||||||
|
```
|
||||||
|
- This should generate multiple outgoing changes with the version update.
|
||||||
|
- Commit your changes and push to the remote:
|
||||||
|
```bash
|
||||||
|
git add . \
|
||||||
|
&& git commit -s -m "Updated versions to 0.2.0" \
|
||||||
|
&& git push
|
||||||
|
```
|
||||||
|
- Create the GH PR the workflow starts automatically.
|
||||||
|
- Once you're happy with the RC, merge the changes to the `main`.
|
||||||
|
- Create a tag and push it:
|
||||||
|
```bash
|
||||||
|
git tag -a 0.2.0 -m "0.2.0" \
|
||||||
|
&& git push origin 0.2.0
|
||||||
|
```
|
||||||
|
- The release build starts automatically and uploads the artifacts with the changelog to the Pro IDE [release page](https://github.com/arduino/arduino-ide/releases).
|
||||||
|
- If you do not want to release the `EXE` and `MSI` installers, wipe them manually.
|
||||||
|
- If you do not like the generated changelog, modify it and update the GH release.
|
||||||
|
|
||||||
|
|
||||||
### FAQ
|
### FAQ
|
||||||
|
|
||||||
- Q: Can I manually change the version of the [`arduino-cli`](https://github.com/arduino/arduino-cli/) used by the IDE?
|
- Q: Can I manually change the version of the [`arduino-cli`](https://github.com/arduino/arduino-cli/) used by the IDE?
|
||||||
@@ -90,9 +126,9 @@ This project is built on [GitHub Actions](https://github.com/bcmi-labs/arduino-e
|
|||||||
|
|
||||||
- Q: I have understood that not all versions of the CLI is compatible with my version of IDE but how can I manually update the `arduino-cli` inside the IDE?
|
- Q: I have understood that not all versions of the CLI is compatible with my version of IDE but how can I manually update the `arduino-cli` inside the IDE?
|
||||||
- A: [Get](https://arduino.github.io/arduino-cli/installation) the desired version of `arduino-cli` for your platform and manually replace the one inside the IDE. The CLI can be found inside the IDE at:
|
- A: [Get](https://arduino.github.io/arduino-cli/installation) the desired version of `arduino-cli` for your platform and manually replace the one inside the IDE. The CLI can be found inside the IDE at:
|
||||||
- Windows: `C:\path\to\Arduino Pro IDE\resources\app\node_modules\arduino-ide-extension\build\arduino-cli.exe`,
|
- Windows: `C:\path\to\Arduino IDE\resources\app\node_modules\arduino-ide-extension\build\arduino-cli.exe`,
|
||||||
- macOS: `/path/to/Arduino Pro IDE.app/Contents/Resources/app/node_modules/arduino-ide-extension/build/arduino-cli`, and
|
- macOS: `/path/to/Arduino IDE.app/Contents/Resources/app/node_modules/arduino-ide-extension/build/arduino-cli`, and
|
||||||
- Linux: `/path/to/Arduino Pro IDE/resources/app/node_modules/arduino-ide-extension/build/arduino-cli`.
|
- Linux: `/path/to/Arduino IDE/resources/app/node_modules/arduino-ide-extension/build/arduino-cli`.
|
||||||
|
|
||||||
### Architecture overview
|
### Architecture overview
|
||||||
|
|
||||||
|
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "arduino-debugger-extension",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "An extension for debugging Arduino programs",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@theia/debug": "next",
|
|
||||||
"arduino-ide-extension": "0.1.0",
|
|
||||||
"cdt-gdb-adapter": "^0.0.14",
|
|
||||||
"vscode-debugadapter": "^1.26.0",
|
|
||||||
"vscode-debugprotocol": "^1.26.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"prepare": "yarn run clean && yarn run build",
|
|
||||||
"clean": "rimraf lib",
|
|
||||||
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
|
|
||||||
"build": "tsc && yarn lint",
|
|
||||||
"watch": "tsc -w"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"lib",
|
|
||||||
"src"
|
|
||||||
],
|
|
||||||
"theiaExtensions": [
|
|
||||||
{
|
|
||||||
"backend": "lib/node/arduino-debug-backend-module",
|
|
||||||
"frontend": "lib/browser/arduino-debug-frontend-module"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
import { DebugConfigurationManager } from "@theia/debug/lib/browser/debug-configuration-manager";
|
|
||||||
import { injectable } from "inversify";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ArduinoDebugConfigurationManager extends DebugConfigurationManager {
|
|
||||||
|
|
||||||
get defaultDebugger(): Promise<string | undefined> {
|
|
||||||
return this.debug.getDebuggersForLanguage('ino').then(debuggers => {
|
|
||||||
if (debuggers.length === 0)
|
|
||||||
return undefined;
|
|
||||||
return debuggers[0].type;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async selectDebugType(): Promise<string | undefined> {
|
|
||||||
const widget = this.editorManager.currentEditor;
|
|
||||||
if (!widget) {
|
|
||||||
return this.defaultDebugger;
|
|
||||||
}
|
|
||||||
const { languageId } = widget.editor.document;
|
|
||||||
const debuggers = await this.debug.getDebuggersForLanguage(languageId);
|
|
||||||
if (debuggers.length === 0) {
|
|
||||||
return this.defaultDebugger;
|
|
||||||
}
|
|
||||||
return this.quickPick.show(debuggers.map(
|
|
||||||
({ label, type }) => ({ label, value: type }),
|
|
||||||
{ placeholder: 'Select Environment' })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createDefaultConfiguration(): Promise<void> {
|
|
||||||
const { model } = this;
|
|
||||||
if (model) {
|
|
||||||
await this.doCreate(model);
|
|
||||||
await this.updateModels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,133 +0,0 @@
|
|||||||
import { injectable, inject } from 'inversify';
|
|
||||||
import { MenuModelRegistry, Path, MessageService, Command, CommandRegistry } from '@theia/core';
|
|
||||||
import { KeybindingRegistry } from '@theia/core/lib/browser';
|
|
||||||
import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
|
||||||
import { DebugFrontendApplicationContribution, DebugCommands } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
|
||||||
import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options";
|
|
||||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
|
||||||
import { FileSystem } from '@theia/filesystem/lib/common';
|
|
||||||
import URI from '@theia/core/lib/common/uri';
|
|
||||||
import { EditorManager } from '@theia/editor/lib/browser';
|
|
||||||
import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode";
|
|
||||||
import { SketchesService } from 'arduino-ide-extension/lib/common/protocol/sketches-service';
|
|
||||||
import { ArduinoToolbar } from 'arduino-ide-extension/lib/browser/toolbar/arduino-toolbar';
|
|
||||||
import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager';
|
|
||||||
|
|
||||||
export namespace ArduinoDebugCommands {
|
|
||||||
export const START_DEBUG: Command = {
|
|
||||||
id: 'arduino-start-debug',
|
|
||||||
label: 'Start Debugging'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendApplicationContribution {
|
|
||||||
|
|
||||||
@inject(EditorMode)
|
|
||||||
protected readonly editorMode: EditorMode;
|
|
||||||
|
|
||||||
@inject(WorkspaceService)
|
|
||||||
protected readonly workspaceService: WorkspaceService;
|
|
||||||
|
|
||||||
@inject(SketchesService)
|
|
||||||
protected readonly sketchesService: SketchesService;
|
|
||||||
|
|
||||||
@inject(FileSystem)
|
|
||||||
protected readonly fileSystem: FileSystem;
|
|
||||||
|
|
||||||
@inject(EditorManager)
|
|
||||||
protected readonly editorManager: EditorManager;
|
|
||||||
|
|
||||||
@inject(MessageService)
|
|
||||||
protected readonly messageService: MessageService;
|
|
||||||
|
|
||||||
async start(noDebug?: boolean, debugSessionOptions?: DebugSessionOptions): Promise<void> {
|
|
||||||
const configurations = this.configurations as ArduinoDebugConfigurationManager;
|
|
||||||
let current = debugSessionOptions ? debugSessionOptions : configurations.current;
|
|
||||||
// If no configurations are currently present, create them
|
|
||||||
if (!current) {
|
|
||||||
await configurations.createDefaultConfiguration();
|
|
||||||
current = configurations.current;
|
|
||||||
}
|
|
||||||
if (current) {
|
|
||||||
if (noDebug !== undefined) {
|
|
||||||
current = {
|
|
||||||
...current,
|
|
||||||
configuration: {
|
|
||||||
...current.configuration,
|
|
||||||
noDebug
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (current.configuration.type === 'arduino') {
|
|
||||||
const wsStat = this.workspaceService.workspace;
|
|
||||||
let sketchFileURI: URI | undefined;
|
|
||||||
if (wsStat && await this.sketchesService.isSketchFolder(wsStat.uri)) {
|
|
||||||
const wsPath = new Path(wsStat.uri);
|
|
||||||
const sketchFilePath = wsPath.join(wsPath.name + '.ino').toString();
|
|
||||||
sketchFileURI = new URI(sketchFilePath);
|
|
||||||
} else if (this.editorManager.currentEditor) {
|
|
||||||
const editorURI = this.editorManager.currentEditor.getResourceUri();
|
|
||||||
if (editorURI && editorURI.path && editorURI.path.ext === '.ino') {
|
|
||||||
sketchFileURI = editorURI;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sketchFileURI) {
|
|
||||||
await this.editorManager.open(sketchFileURI);
|
|
||||||
await this.manager.start(current);
|
|
||||||
} else {
|
|
||||||
this.messageService.error('Please open a sketch file to start debugging.')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.manager.start(current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeLayout(): Promise<void> {
|
|
||||||
if (this.editorMode.proMode) {
|
|
||||||
return super.initializeLayout();
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
registerMenus(menus: MenuModelRegistry): void {
|
|
||||||
if (this.editorMode.proMode) {
|
|
||||||
super.registerMenus(menus);
|
|
||||||
menus.unregisterMenuAction(DebugCommands.START_NO_DEBUG);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
|
||||||
if (this.editorMode.proMode) {
|
|
||||||
super.registerKeybindings(keybindings);
|
|
||||||
keybindings.unregisterKeybinding({
|
|
||||||
command: DebugCommands.START_NO_DEBUG.id,
|
|
||||||
keybinding: 'ctrl+f5'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerToolbarItems(toolbar: TabBarToolbarRegistry): void {
|
|
||||||
super.registerToolbarItems(toolbar);
|
|
||||||
toolbar.registerItem({
|
|
||||||
id: ArduinoDebugCommands.START_DEBUG.id,
|
|
||||||
command: ArduinoDebugCommands.START_DEBUG.id,
|
|
||||||
tooltip: 'Start Debugging',
|
|
||||||
priority: 3
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCommands(registry: CommandRegistry): void {
|
|
||||||
super.registerCommands(registry);
|
|
||||||
registry.registerCommand(ArduinoDebugCommands.START_DEBUG, {
|
|
||||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
|
||||||
isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
|
||||||
execute: () => {
|
|
||||||
registry.executeCommand(DebugCommands.START.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
import { ContainerModule } from 'inversify';
|
|
||||||
import { VariableContribution } from '@theia/variable-resolver/lib/browser';
|
|
||||||
import { ArduinoVariableResolver } from './arduino-variable-resolver';
|
|
||||||
import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
|
||||||
import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
|
||||||
import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
|
|
||||||
import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager';
|
|
||||||
import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution';
|
|
||||||
import { ArduinoDebugSessionManager } from './arduino-debug-session-manager';
|
|
||||||
|
|
||||||
import '../../src/browser/style/index.css';
|
|
||||||
|
|
||||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
||||||
bind(ArduinoVariableResolver).toSelf().inSingletonScope();
|
|
||||||
bind(VariableContribution).toService(ArduinoVariableResolver);
|
|
||||||
rebind(DebugSessionManager).to(ArduinoDebugSessionManager).inSingletonScope();
|
|
||||||
rebind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope();
|
|
||||||
rebind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution);
|
|
||||||
});
|
|
@@ -1,14 +0,0 @@
|
|||||||
import { DebugSessionManager } from "@theia/debug/lib/browser/debug-session-manager";
|
|
||||||
import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options";
|
|
||||||
|
|
||||||
export class ArduinoDebugSessionManager extends DebugSessionManager {
|
|
||||||
|
|
||||||
start(options: DebugSessionOptions) {
|
|
||||||
if (options.configuration.type === 'arduino' && this.sessions.find(s => s.configuration.type === 'arduino')) {
|
|
||||||
this.messageService.info('A debug session is already running. You must stop the running session before starting a new one.')
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
}
|
|
||||||
return super.start(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser';
|
|
||||||
import { injectable, inject } from 'inversify';
|
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
|
||||||
import { BoardsServiceClientImpl } from 'arduino-ide-extension/lib/browser/boards/boards-service-client-impl';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ArduinoVariableResolver implements VariableContribution {
|
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
|
||||||
|
|
||||||
@inject(MessageService)
|
|
||||||
protected readonly messageService: MessageService
|
|
||||||
|
|
||||||
registerVariables(variables: VariableRegistry): void {
|
|
||||||
variables.registerVariable(<Variable>{
|
|
||||||
name: 'fqbn',
|
|
||||||
description: 'Qualified name of the selected board',
|
|
||||||
resolve: this.resolveFqbn.bind(this),
|
|
||||||
});
|
|
||||||
variables.registerVariable({
|
|
||||||
name: 'port',
|
|
||||||
description: 'Selected upload port',
|
|
||||||
resolve: this.resolvePort.bind(this)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async resolveFqbn(): Promise<string | undefined> {
|
|
||||||
const { boardsConfig } = this.boardsServiceClient;
|
|
||||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
|
||||||
this.messageService.error('No board selected. Please select a board for debugging.');
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return boardsConfig.selectedBoard.fqbn;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async resolvePort(): Promise<string | undefined> {
|
|
||||||
const { boardsConfig } = this.boardsServiceClient;
|
|
||||||
if (!boardsConfig || !boardsConfig.selectedPort) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return boardsConfig.selectedPort.address;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
.arduino-start-debug-icon {
|
|
||||||
-webkit-mask: url('debug-dark.svg') 50%;
|
|
||||||
mask: url('debug-dark.svg') 50%;
|
|
||||||
-webkit-mask-size: 100%;
|
|
||||||
mask-size: 100%;
|
|
||||||
-webkit-mask-repeat: no-repeat;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--theia-ui-button-font-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.arduino-start-debug {
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
@@ -1,89 +0,0 @@
|
|||||||
import * as path from 'path';
|
|
||||||
import { injectable, inject } from 'inversify';
|
|
||||||
import { DebugAdapterContribution, DebugAdapterExecutable } from '@theia/debug/lib/common/debug-model';
|
|
||||||
import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration';
|
|
||||||
import { IJSONSchema } from '@theia/core/lib/common/json-schema';
|
|
||||||
import { ArduinoDaemonImpl } from 'arduino-ide-extension/lib/node/arduino-daemon-impl';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ArduinoDebugAdapterContribution implements DebugAdapterContribution {
|
|
||||||
|
|
||||||
readonly type = 'arduino';
|
|
||||||
readonly label = 'Arduino';
|
|
||||||
readonly languages = ['c', 'cpp', 'ino'];
|
|
||||||
|
|
||||||
@inject(ArduinoDaemonImpl) daemon: ArduinoDaemonImpl;
|
|
||||||
|
|
||||||
getSchemaAttributes(): IJSONSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'properties': {
|
|
||||||
'sketch': {
|
|
||||||
'type': 'string',
|
|
||||||
'description': 'path to the sketch root ino file',
|
|
||||||
'default': '${file}',
|
|
||||||
},
|
|
||||||
'pauseAtMain': {
|
|
||||||
'description': 'If enabled the debugger will pause at the start of the main function.',
|
|
||||||
'type': 'boolean',
|
|
||||||
'default': false
|
|
||||||
},
|
|
||||||
'debugDebugAdapter': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': 'Start the debug adapter in debug mode (with --inspect-brk)',
|
|
||||||
'default': false
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
provideDebugAdapterExecutable(config: DebugConfiguration): DebugAdapterExecutable {
|
|
||||||
const debugAdapterMain = path.join(__dirname, 'debug-adapter', 'main');
|
|
||||||
if (config.debugDebugAdapter) {
|
|
||||||
return {
|
|
||||||
command: process.execPath,
|
|
||||||
args: ['--inspect-brk', debugAdapterMain],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
modulePath: debugAdapterMain,
|
|
||||||
args: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provideDebugConfigurations(): DebugConfiguration[] {
|
|
||||||
return [
|
|
||||||
<DebugConfiguration>{
|
|
||||||
name: this.label,
|
|
||||||
type: this.type,
|
|
||||||
request: 'launch'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
async resolveDebugConfiguration(config: DebugConfiguration): Promise<DebugConfiguration> {
|
|
||||||
const startFunction = config.pauseAtMain ? 'main' : 'setup';
|
|
||||||
const res: ActualDebugConfig = {
|
|
||||||
...config,
|
|
||||||
arduinoCli: await this.daemon.getExecPath(),
|
|
||||||
fqbn: '${fqbn}',
|
|
||||||
uploadPort: '${port}',
|
|
||||||
initCommands: [
|
|
||||||
`-break-insert -t --function ${startFunction}`
|
|
||||||
]
|
|
||||||
}
|
|
||||||
if (!res.sketch) {
|
|
||||||
res.sketch = '${file}';
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ActualDebugConfig extends DebugConfiguration {
|
|
||||||
arduinoCli?: string;
|
|
||||||
sketch?: string;
|
|
||||||
fqbn?: string;
|
|
||||||
uploadPort?: string;
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
import { ContainerModule } from 'inversify';
|
|
||||||
import { DebugAdapterContribution } from '@theia/debug/lib/common/debug-model';
|
|
||||||
import { ArduinoDebugAdapterContribution } from './arduino-debug-adapter-contribution';
|
|
||||||
|
|
||||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
||||||
bind(DebugAdapterContribution).to(ArduinoDebugAdapterContribution).inSingletonScope();
|
|
||||||
});
|
|
@@ -1,140 +0,0 @@
|
|||||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
|
||||||
import { GDBDebugSession, FrameVariableReference } from 'cdt-gdb-adapter/dist/GDBDebugSession';
|
|
||||||
import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend';
|
|
||||||
import * as mi from 'cdt-gdb-adapter/dist/mi';
|
|
||||||
import { ArduinoGDBBackend } from './arduino-gdb-backend';
|
|
||||||
import { ArduinoVariableHandler } from './arduino-variable-handler';
|
|
||||||
import { Scope, OutputEvent } from 'vscode-debugadapter';
|
|
||||||
|
|
||||||
export interface ArduinoLaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
|
||||||
arduinoCli?: string;
|
|
||||||
sketch?: string;
|
|
||||||
fqbn?: string;
|
|
||||||
uploadPort?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLOBAL_HANDLE_ID = 0xFE;
|
|
||||||
const STATIC_HANDLES_START = 0x010000;
|
|
||||||
const STATIC_HANDLES_FINISH = 0x01FFFF;
|
|
||||||
|
|
||||||
export class ArduinoDebugSession extends GDBDebugSession {
|
|
||||||
|
|
||||||
private _variableHandler: ArduinoVariableHandler;
|
|
||||||
|
|
||||||
get arduinoBackend(): ArduinoGDBBackend {
|
|
||||||
return this.gdb as ArduinoGDBBackend;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get variableHandler() {
|
|
||||||
if (this._variableHandler) {
|
|
||||||
return this._variableHandler;
|
|
||||||
}
|
|
||||||
if (!this.gdb) {
|
|
||||||
throw new Error("GDB backend is not ready.");
|
|
||||||
}
|
|
||||||
const handler = new ArduinoVariableHandler(this, this.frameHandles, this.variableHandles);
|
|
||||||
this._variableHandler = handler;
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createBackend(): GDBBackend {
|
|
||||||
return new ArduinoGDBBackend();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): Promise<void> {
|
|
||||||
try {
|
|
||||||
await this.gdb.sendCommand('-interpreter-exec console "monitor reset halt"')
|
|
||||||
await mi.sendExecContinue(this.gdb);
|
|
||||||
this.sendResponse(response);
|
|
||||||
} catch (err) {
|
|
||||||
this.sendErrorResponse(response, 100, err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): Promise<void> {
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
const message = 'Pause is not supported on Windows. Please stop the debug session and set a breakpoint instead.';
|
|
||||||
this.sendEvent(new OutputEvent(message));
|
|
||||||
this.sendErrorResponse(response, 1, message);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return super.pauseRequest(response, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async disconnectRequest(response: DebugProtocol.DisconnectResponse): Promise<void> {
|
|
||||||
try {
|
|
||||||
if (this.isRunning) {
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
// We cannot pause on Windows
|
|
||||||
this.arduinoBackend.kill();
|
|
||||||
this.sendResponse(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Need to pause first
|
|
||||||
const waitPromise = new Promise(resolve => this.waitPaused = resolve);
|
|
||||||
this.gdb.pause();
|
|
||||||
await waitPromise;
|
|
||||||
}
|
|
||||||
await this.gdb.sendGDBExit();
|
|
||||||
this.sendResponse(response);
|
|
||||||
} catch (err) {
|
|
||||||
this.sendErrorResponse(response, 1, err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
|
||||||
try {
|
|
||||||
const frame: FrameVariableReference = {
|
|
||||||
type: 'frame',
|
|
||||||
frameHandle: args.frameId,
|
|
||||||
};
|
|
||||||
// const pins: ObjectVariableReference = {
|
|
||||||
// type: "object",
|
|
||||||
// varobjName: "__pins",
|
|
||||||
// frameHandle: 42000,
|
|
||||||
// }
|
|
||||||
|
|
||||||
response.body = {
|
|
||||||
scopes: [
|
|
||||||
// new Scope('Pins', this.variableHandles.create(pins), false),
|
|
||||||
new Scope('Local', this.variableHandles.create(frame), false),
|
|
||||||
new Scope('Global', GLOBAL_HANDLE_ID, false),
|
|
||||||
// new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false)
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
this.sendResponse(response);
|
|
||||||
} catch (err) {
|
|
||||||
this.sendErrorResponse(response, 1, err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise<void> {
|
|
||||||
try {
|
|
||||||
response.body = {
|
|
||||||
variables: [] as DebugProtocol.Variable[]
|
|
||||||
};
|
|
||||||
const ref = this.variableHandles.get(args.variablesReference);
|
|
||||||
if (args.variablesReference === GLOBAL_HANDLE_ID) {
|
|
||||||
// Use hardcoded global handle to load and store global variables
|
|
||||||
response.body.variables = await this.variableHandler.getGlobalVariables();
|
|
||||||
} else if (args.variablesReference >= STATIC_HANDLES_START && args.variablesReference <= STATIC_HANDLES_FINISH) {
|
|
||||||
// Use STATIC_HANDLES_START to shift the framehandles back
|
|
||||||
const frameHandle = args.variablesReference - STATIC_HANDLES_START;
|
|
||||||
response.body.variables = await this.variableHandler.getStaticVariables(frameHandle);
|
|
||||||
} else if (ref && ref.type === 'frame') {
|
|
||||||
// List variables for current frame
|
|
||||||
response.body.variables = await this.handleVariableRequestFrame(ref);
|
|
||||||
} else if (ref && ref.varobjName === '__pins') {
|
|
||||||
response.body.variables = await this.variableHandler.handlePinStatusRequest();
|
|
||||||
} else if (ref && ref.type === 'object') {
|
|
||||||
// List data under any variable
|
|
||||||
response.body.variables = await this.handleVariableRequestObject(ref);
|
|
||||||
}
|
|
||||||
this.sendResponse(response);
|
|
||||||
} catch (err) {
|
|
||||||
this.sendErrorResponse(response, 1, err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,72 +0,0 @@
|
|||||||
import * as path from 'path';
|
|
||||||
import * as fs from 'arduino-ide-extension/lib/node/fs-extra'
|
|
||||||
import { spawn } from 'child_process';
|
|
||||||
import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend';
|
|
||||||
import { MIFrameInfo } from 'cdt-gdb-adapter/dist/mi';
|
|
||||||
import { ArduinoLaunchRequestArguments } from './arduino-debug-session';
|
|
||||||
import { ArduinoParser } from './arduino-parser';
|
|
||||||
|
|
||||||
export class ArduinoGDBBackend extends GDBBackend {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.parser = new ArduinoParser(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
spawn(requestArgs: ArduinoLaunchRequestArguments): Promise<void> {
|
|
||||||
if (!requestArgs.sketch) {
|
|
||||||
throw new Error('Missing argument: sketch');
|
|
||||||
}
|
|
||||||
if (!requestArgs.fqbn) {
|
|
||||||
throw new Error('Missing argument: fqbn')
|
|
||||||
}
|
|
||||||
const sketchFS = fs.statSync(requestArgs.sketch);
|
|
||||||
const sketchDir = sketchFS.isFile() ? path.dirname(requestArgs.sketch) : requestArgs.sketch;
|
|
||||||
const command = requestArgs.arduinoCli || 'arduino-cli';
|
|
||||||
const args = [
|
|
||||||
'debug',
|
|
||||||
'-p', requestArgs.uploadPort || 'none',
|
|
||||||
'-b', requestArgs.fqbn,
|
|
||||||
'--interpreter', 'mi2',
|
|
||||||
sketchDir
|
|
||||||
];
|
|
||||||
const proc = spawn(command, args);
|
|
||||||
this.proc = proc;
|
|
||||||
this.out = proc.stdin;
|
|
||||||
return (this.parser as ArduinoParser).parseFull(proc);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendFileExecAndSymbols(): Promise<void> {
|
|
||||||
// The program file is already sent by `arduino-cli`
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendExecInterrupt(threadId?: number) {
|
|
||||||
let command = '-exec-interrupt';
|
|
||||||
if (threadId) {
|
|
||||||
command += ` --thread ${threadId}`;
|
|
||||||
}
|
|
||||||
return this.sendCommand(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendStackInfoFrame(threadId: number, frameId: number): Promise<{ frame: MIFrameInfo }> {
|
|
||||||
const command = `-stack-info-frame --thread ${threadId} --frame ${frameId}`;
|
|
||||||
return this.sendCommand(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTargetDetach(): Promise<void> {
|
|
||||||
return this.sendCommand('-target-detach');
|
|
||||||
}
|
|
||||||
|
|
||||||
kill(): void {
|
|
||||||
if (!this.proc) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
spawn('taskkill', ['/pid', this.proc.pid.toString(), '/f', '/t']);
|
|
||||||
} else {
|
|
||||||
this.proc.kill('SIGKILL');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,76 +0,0 @@
|
|||||||
import { ChildProcessWithoutNullStreams } from 'child_process';
|
|
||||||
import { Readable } from 'stream';
|
|
||||||
import { MIParser } from "cdt-gdb-adapter/dist/MIParser";
|
|
||||||
|
|
||||||
const LINE_REGEX = /(.*)(\r?\n)/;
|
|
||||||
|
|
||||||
export class ArduinoParser extends MIParser {
|
|
||||||
|
|
||||||
protected rejectReady?: (error: Error) => void;
|
|
||||||
|
|
||||||
parseFull(proc: ChildProcessWithoutNullStreams): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// Detect errors when the child process could not be spawned
|
|
||||||
proc.on('error', reject);
|
|
||||||
|
|
||||||
this.waitReady = () => {
|
|
||||||
this.rejectReady = undefined;
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
this.rejectReady = (error: Error) => {
|
|
||||||
this.waitReady = undefined;
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
// Detect unexpected termination
|
|
||||||
proc.on('exit', () => {
|
|
||||||
if (this.rejectReady) {
|
|
||||||
this.rejectReady(new Error('The gdb debugger terminated unexpectedly.'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.readInputStream(proc.stdout);
|
|
||||||
this.readErrorStream(proc.stderr);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private readInputStream(stream: Readable) {
|
|
||||||
let buff = '';
|
|
||||||
stream.on('data', chunk => {
|
|
||||||
buff += chunk.toString();
|
|
||||||
let regexArray = LINE_REGEX.exec(buff);
|
|
||||||
while (regexArray) {
|
|
||||||
const line = regexArray[1];
|
|
||||||
this.line = line;
|
|
||||||
this.pos = 0;
|
|
||||||
this.handleLine();
|
|
||||||
// Detect error emitted as log message
|
|
||||||
if (this.rejectReady && line.toLowerCase().startsWith('&"error')) {
|
|
||||||
this.pos = 1;
|
|
||||||
this.rejectReady(new Error(this.handleCString() || regexArray[1]));
|
|
||||||
this.rejectReady = undefined;
|
|
||||||
}
|
|
||||||
buff = buff.substring(regexArray[1].length + regexArray[2].length);
|
|
||||||
regexArray = LINE_REGEX.exec(buff);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private readErrorStream(stream: Readable) {
|
|
||||||
let buff = '';
|
|
||||||
stream.on('data', chunk => {
|
|
||||||
buff += chunk.toString();
|
|
||||||
let regexArray = LINE_REGEX.exec(buff);
|
|
||||||
while (regexArray) {
|
|
||||||
const line = regexArray[1];
|
|
||||||
this.gdb.emit('consoleStreamOutput', line + '\n', 'stderr');
|
|
||||||
// Detect error emitted on the stderr stream
|
|
||||||
if (this.rejectReady && line.toLowerCase().startsWith('error')) {
|
|
||||||
this.rejectReady(new Error(line));
|
|
||||||
this.rejectReady = undefined;
|
|
||||||
}
|
|
||||||
buff = buff.substring(regexArray[1].length + regexArray[2].length);
|
|
||||||
regexArray = LINE_REGEX.exec(buff);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,115 +0,0 @@
|
|||||||
import * as path from 'path';
|
|
||||||
import { DebugProtocol } from "vscode-debugprotocol";
|
|
||||||
import { Handles } from 'vscode-debugadapter/lib/handles';
|
|
||||||
import { FrameReference, VariableReference } from "cdt-gdb-adapter/dist/GDBDebugSession";
|
|
||||||
import { VarManager } from 'cdt-gdb-adapter/dist/varManager';
|
|
||||||
import * as mi from 'cdt-gdb-adapter/dist/mi';
|
|
||||||
import { ArduinoDebugSession } from "./arduino-debug-session";
|
|
||||||
import { ArduinoGDBBackend } from './arduino-gdb-backend';
|
|
||||||
|
|
||||||
export class ArduinoVariableHandler {
|
|
||||||
|
|
||||||
protected readonly gdb: ArduinoGDBBackend;
|
|
||||||
protected readonly varMgr: VarManager;
|
|
||||||
|
|
||||||
protected globalHandle: number;
|
|
||||||
|
|
||||||
constructor(protected readonly session: ArduinoDebugSession,
|
|
||||||
protected frameHandles: Handles<FrameReference>,
|
|
||||||
protected variableHandles: Handles<VariableReference>) {
|
|
||||||
this.gdb = session.arduinoBackend;
|
|
||||||
this.varMgr = new VarManager(this.gdb);
|
|
||||||
}
|
|
||||||
|
|
||||||
createGlobalHandle() {
|
|
||||||
this.globalHandle = this.frameHandles.create({
|
|
||||||
threadId: -1,
|
|
||||||
frameId: -1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** TODO */
|
|
||||||
async getGlobalVariables(): Promise<DebugProtocol.Variable[]> {
|
|
||||||
throw new Error('Global variables are not supported yet.');
|
|
||||||
const frame = this.frameHandles.get(this.globalHandle);
|
|
||||||
const symbolInfo: any[] = [] // this.symbolTable.getGlobalVariables();
|
|
||||||
const variables: DebugProtocol.Variable[] = [];
|
|
||||||
|
|
||||||
for (const symbol of symbolInfo) {
|
|
||||||
const name = `global_var_${symbol.name}`;
|
|
||||||
const variable = await this.getVariables(frame, name, symbol.name, -1);
|
|
||||||
variables.push(variable);
|
|
||||||
}
|
|
||||||
|
|
||||||
return variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** TODO */
|
|
||||||
async getStaticVariables(frameHandle: number): Promise<DebugProtocol.Variable[]> {
|
|
||||||
throw new Error('Static variables are not supported yet.');
|
|
||||||
const frame = this.frameHandles.get(frameHandle);
|
|
||||||
const result = await this.gdb.sendStackInfoFrame(frame.threadId, frame.frameId);
|
|
||||||
const file = path.normalize(result.frame.file || '');
|
|
||||||
const symbolInfo: any[] = [] // this.symbolTable.getStaticVariables(file);
|
|
||||||
const variables: DebugProtocol.Variable[] = [];
|
|
||||||
|
|
||||||
// Fetch stack depth to obtain frameId/threadId/depth tuple
|
|
||||||
const stackDepth = await mi.sendStackInfoDepth(this.gdb, { maxDepth: 100 });
|
|
||||||
const depth = parseInt(stackDepth.depth, 10);
|
|
||||||
|
|
||||||
for (const symbol of symbolInfo) {
|
|
||||||
const name = `${file}_static_var_${symbol.name}`;
|
|
||||||
const variable = await this.getVariables(frame, name, symbol.name, depth);
|
|
||||||
variables.push(variable);
|
|
||||||
}
|
|
||||||
|
|
||||||
return variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getVariables(frame: FrameReference, name: string, expression: string, depth: number): Promise<DebugProtocol.Variable> {
|
|
||||||
let global = this.varMgr.getVar(frame.frameId, frame.threadId, depth, name);
|
|
||||||
|
|
||||||
if (global) {
|
|
||||||
// Update value if it is already loaded
|
|
||||||
const vup = await mi.sendVarUpdate(this.gdb, { name });
|
|
||||||
const update = vup.changelist[0];
|
|
||||||
if (update && update.in_scope === 'true' && update.name === global.varname) {
|
|
||||||
global.value = update.value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// create var in GDB and store it in the varMgr
|
|
||||||
const varCreateResponse = await mi.sendVarCreate(this.gdb, {
|
|
||||||
name,
|
|
||||||
frame: 'current',
|
|
||||||
expression,
|
|
||||||
});
|
|
||||||
|
|
||||||
global = this.varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: expression,
|
|
||||||
value: (global.value === void 0) ? '<unknown>' : global.value,
|
|
||||||
type: global.type,
|
|
||||||
variablesReference: parseInt(global.numchild, 10) > 0
|
|
||||||
? this.variableHandles.create({
|
|
||||||
frameHandle: this.globalHandle,
|
|
||||||
type: 'object',
|
|
||||||
varobjName: global.varname,
|
|
||||||
})
|
|
||||||
: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async handlePinStatusRequest(): Promise<DebugProtocol.Variable[]> {
|
|
||||||
const variables: DebugProtocol.Variable[] = [];
|
|
||||||
variables.push({
|
|
||||||
name: "D2",
|
|
||||||
type: "gpio",
|
|
||||||
value: "0x00",
|
|
||||||
variablesReference: 0
|
|
||||||
})
|
|
||||||
return variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,10 +0,0 @@
|
|||||||
import * as process from 'process';
|
|
||||||
import { logger } from 'vscode-debugadapter/lib/logger';
|
|
||||||
import { ArduinoDebugSession } from './arduino-debug-session';
|
|
||||||
import { DebugSession } from 'vscode-debugadapter';
|
|
||||||
|
|
||||||
process.on('uncaughtException', (err: any) => {
|
|
||||||
logger.error(JSON.stringify(err));
|
|
||||||
});
|
|
||||||
|
|
||||||
DebugSession.run(ArduinoDebugSession);
|
|
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"noEmitOnError": true,
|
|
||||||
"noImplicitThis": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"downlevelIteration": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"module": "commonjs",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"target": "es6",
|
|
||||||
"outDir": "lib",
|
|
||||||
"lib": [
|
|
||||||
"es6",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"jsx": "react",
|
|
||||||
"sourceMap": true,
|
|
||||||
"skipLibCheck": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"../node_modules/@theia/monaco/src/typings/monaco/index.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"rules": {
|
|
||||||
"class-name": true,
|
|
||||||
"comment-format": [true, "check-space"],
|
|
||||||
"curly": false,
|
|
||||||
"forin": false,
|
|
||||||
"indent": [true, "spaces"],
|
|
||||||
"max-line-length": [true, 180],
|
|
||||||
"no-trailing-whitespace": false,
|
|
||||||
"no-unused-expression": true,
|
|
||||||
"no-var-keyword": true,
|
|
||||||
"one-line": [true,
|
|
||||||
"check-open-brace",
|
|
||||||
"check-catch",
|
|
||||||
"check-else",
|
|
||||||
"check-whitespace"
|
|
||||||
],
|
|
||||||
"radix": true,
|
|
||||||
"trailing-comma": [false],
|
|
||||||
"triple-equals": [true, "allow-null-check"],
|
|
||||||
"typedef-whitespace": [true, {
|
|
||||||
"call-signature": "nospace",
|
|
||||||
"index-signature": "nospace",
|
|
||||||
"parameter": "nospace",
|
|
||||||
"property-declaration": "nospace",
|
|
||||||
"variable-declaration": "nospace"
|
|
||||||
}],
|
|
||||||
"variable-name": false,
|
|
||||||
"whitespace": [true,
|
|
||||||
"check-branch",
|
|
||||||
"check-decl",
|
|
||||||
"check-operator",
|
|
||||||
"check-separator",
|
|
||||||
"check-type"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
## Arduino IDE Extension
|
## Arduino IDE Extension
|
||||||
|
|
||||||
Arduino Pro IDE is based on Theia, and most of its IDE features, UIs and customizations are implemented in this Theia extension.
|
Arduino IDE is based on Theia, and most of its IDE features, UIs and customizations are implemented in this Theia extension.
|
||||||
|
|
||||||
### IDE Services
|
### IDE Services
|
||||||
|
|
||||||
@@ -50,3 +50,7 @@ The Config Service knows about your system, like for example the default sketch
|
|||||||
- [src/node/config-service-impl.ts](./src/node/config-service-impl.ts) implements the service backend:
|
- [src/node/config-service-impl.ts](./src/node/config-service-impl.ts) implements the service backend:
|
||||||
- getting the `arduino-cli` version and configuration
|
- getting the `arduino-cli` version and configuration
|
||||||
- checking whether a file is in a data or sketch directory
|
- 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.
|
||||||
|
@@ -1,117 +0,0 @@
|
|||||||
{
|
|
||||||
"$id": "http://arduino.cc/arduino-cli.json",
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"type": "object",
|
|
||||||
"title": "Arduino CLI Configuration",
|
|
||||||
"properties": {
|
|
||||||
"board_manager": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Board Manager Configuration",
|
|
||||||
"properties": {
|
|
||||||
"additional_urls": {
|
|
||||||
"type": "array",
|
|
||||||
"description": "If your board requires 3rd party core packages to work, you can list the URLs to additional package indexes in the Arduino CLI configuration file.",
|
|
||||||
"items": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "URL pointing to the 3rd party core package index JSON.",
|
|
||||||
"pattern": "^(.*)$"
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"daemon": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "CLI Daemon Configuration",
|
|
||||||
"properties": {
|
|
||||||
"port": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"number"
|
|
||||||
],
|
|
||||||
"description": "The CLI daemon port where the gRPC clients can connect to.",
|
|
||||||
"pattern": "^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$",
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"directories": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Directories Configuration",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to the the data folder where core packages will be stored.",
|
|
||||||
"pattern": "^(.*)$"
|
|
||||||
},
|
|
||||||
"downloads": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to the staging folder.",
|
|
||||||
"pattern": "^(.*)$"
|
|
||||||
},
|
|
||||||
"user": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to the sketchbooks.",
|
|
||||||
"pattern": "^(.*)$"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"logging": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Logging Configuration",
|
|
||||||
"properties": {
|
|
||||||
"file": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to the file where logs will be written.",
|
|
||||||
"pattern": "^(.*)$"
|
|
||||||
},
|
|
||||||
"format": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The output format for the logs, can be 'text' or 'json'",
|
|
||||||
"enum": [
|
|
||||||
"text",
|
|
||||||
"json"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"level": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Messages with this level and above will be logged.",
|
|
||||||
"enum": [
|
|
||||||
"trace",
|
|
||||||
"debug",
|
|
||||||
"info",
|
|
||||||
"warning",
|
|
||||||
"error",
|
|
||||||
"fatal",
|
|
||||||
"panic"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"telemetry": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Telemetry Configuration",
|
|
||||||
"properties": {
|
|
||||||
"addr": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Address to the telemetry endpoint. Must be a full address with host, address, and port. For instance, ':9090' represents 'localhost:9090'",
|
|
||||||
"pattern": "^(.*)$"
|
|
||||||
},
|
|
||||||
"enabled": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether the telemetry is enabled or not."
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"// TODOs": [
|
|
||||||
"additionalProperties should be true. See the new telemetry entry"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "arduino-ide-extension",
|
"name": "arduino-ide-extension",
|
||||||
"version": "0.1.0",
|
"version": "2.0.0-beta.2",
|
||||||
"description": "An extension for Theia building the Arduino IDE",
|
"description": "An extension for Theia building the Arduino IDE",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "yarn download-cli && yarn download-ls && yarn run clean && yarn run build",
|
"prepare": "yarn download-cli && yarn download-ls && yarn clean && yarn download-examples && yarn build",
|
||||||
"clean": "rimraf lib",
|
"clean": "rimraf lib",
|
||||||
"download-cli": "node ./scripts/download-cli.js",
|
"download-cli": "node ./scripts/download-cli.js",
|
||||||
"download-ls": "node ./scripts/download-ls.js",
|
"download-ls": "node ./scripts/download-ls.js",
|
||||||
|
"download-examples": "node ./scripts/download-examples.js",
|
||||||
"generate-protocol": "node ./scripts/generate-protocol.js",
|
"generate-protocol": "node ./scripts/generate-protocol.js",
|
||||||
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
|
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
|
||||||
"build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint",
|
"build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint",
|
||||||
@@ -22,7 +23,6 @@
|
|||||||
"@theia/editor": "next",
|
"@theia/editor": "next",
|
||||||
"@theia/filesystem": "next",
|
"@theia/filesystem": "next",
|
||||||
"@theia/git": "next",
|
"@theia/git": "next",
|
||||||
"@theia/languages": "next",
|
|
||||||
"@theia/markers": "next",
|
"@theia/markers": "next",
|
||||||
"@theia/monaco": "next",
|
"@theia/monaco": "next",
|
||||||
"@theia/navigator": "next",
|
"@theia/navigator": "next",
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
"@types/ncp": "^2.0.4",
|
"@types/ncp": "^2.0.4",
|
||||||
"@types/ps-tree": "^1.1.0",
|
"@types/ps-tree": "^1.1.0",
|
||||||
"@types/react-select": "^3.0.0",
|
"@types/react-select": "^3.0.0",
|
||||||
|
"@types/react-tabs": "^2.3.2",
|
||||||
"@types/sinon": "^7.5.2",
|
"@types/sinon": "^7.5.2",
|
||||||
"@types/temp": "^0.8.34",
|
"@types/temp": "^0.8.34",
|
||||||
"@types/which": "^1.3.1",
|
"@types/which": "^1.3.1",
|
||||||
@@ -56,8 +57,10 @@
|
|||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"p-queue": "^5.0.0",
|
"p-queue": "^5.0.0",
|
||||||
"ps-tree": "^1.2.0",
|
"ps-tree": "^1.2.0",
|
||||||
|
"react-disable": "^0.1.0",
|
||||||
"react-select": "^3.0.4",
|
"react-select": "^3.0.4",
|
||||||
"semver": "^6.3.0",
|
"react-tabs": "^3.1.2",
|
||||||
|
"semver": "^7.3.2",
|
||||||
"string-natural-compare": "^2.0.3",
|
"string-natural-compare": "^2.0.3",
|
||||||
"temp": "^0.9.1",
|
"temp": "^0.9.1",
|
||||||
"tree-kill": "^1.2.1",
|
"tree-kill": "^1.2.1",
|
||||||
@@ -99,7 +102,8 @@
|
|||||||
"lib",
|
"lib",
|
||||||
"src",
|
"src",
|
||||||
"build",
|
"build",
|
||||||
"data"
|
"data",
|
||||||
|
"examples"
|
||||||
],
|
],
|
||||||
"theiaExtensions": [
|
"theiaExtensions": [
|
||||||
{
|
{
|
||||||
@@ -112,6 +116,14 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"frontend": "lib/browser/boards/quick-open/boards-quick-open-module"
|
"frontend": "lib/browser/boards/quick-open/boards-quick-open-module"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"electronMain": "lib/electron-main/arduino-electron-main-module"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"arduino": {
|
||||||
|
"cli": {
|
||||||
|
"version": "0.16.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,62 +1,141 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
// The links to the downloads as of today (02.09.) are the followings:
|
|
||||||
// In order to get the latest nightly build for your platform use the following links replacing <DATE> with the current date, using the format YYYYMMDD (i.e for 2019/Aug/06 use 20190806 )
|
|
||||||
// Linux 64 bit: https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-<DATE>_Linux_64bit.tar.gz
|
|
||||||
// Linux ARM 64 bit: https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-<DATE>_Linux_ARM64.tar.gz
|
|
||||||
// Windows 64 bit: https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-<DATE>_Windows_64bit.zip
|
|
||||||
// Mac OSX: https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-<DATE>_macOS_64bit.tar.gz
|
|
||||||
// [...]
|
|
||||||
// redirecting to latest generated builds by replacing latest with the latest available build date, using the format YYYYMMDD (i.e for 2019/Aug/06 latest is replaced with 20190806
|
|
||||||
|
|
||||||
(() => {
|
(async () => {
|
||||||
|
|
||||||
const DEFAULT_VERSION = '0.12.0'; // require('moment')().format('YYYYMMDD');
|
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const temp = require('temp');
|
||||||
const shell = require('shelljs');
|
const shell = require('shelljs');
|
||||||
|
const semver = require('semver');
|
||||||
|
const moment = require('moment');
|
||||||
const downloader = require('./downloader');
|
const downloader = require('./downloader');
|
||||||
|
|
||||||
const yargs = require('yargs')
|
const version = (() => {
|
||||||
.option('cli-version', {
|
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
||||||
alias: 'cv',
|
if (!pkg) {
|
||||||
default: DEFAULT_VERSION,
|
return undefined;
|
||||||
describe: `The version of the 'arduino-cli' to download, or 'nightly-latest'. Defaults to ${DEFAULT_VERSION}.`
|
|
||||||
})
|
|
||||||
.option('force-download', {
|
|
||||||
alias: 'fd',
|
|
||||||
default: false,
|
|
||||||
describe: `If set, this script force downloads the 'arduino-cli' even if it already exists on the file system.`
|
|
||||||
})
|
|
||||||
.version(false).parse();
|
|
||||||
|
|
||||||
const version = yargs['cli-version'];
|
|
||||||
const force = yargs['force-download'];
|
|
||||||
const { platform, arch } = process;
|
|
||||||
|
|
||||||
const build = path.join(__dirname, '..', 'build');
|
|
||||||
const cli = path.join(build, `arduino-cli${platform === 'win32' ? '.exe' : ''}`);
|
|
||||||
|
|
||||||
const suffix = (() => {
|
|
||||||
switch (platform) {
|
|
||||||
case 'darwin': return 'macOS_64bit.tar.gz';
|
|
||||||
case 'win32': return 'Windows_64bit.zip';
|
|
||||||
case 'linux': {
|
|
||||||
switch (arch) {
|
|
||||||
case 'arm': return 'Linux_ARMv7.tar.gz';
|
|
||||||
case 'arm64': return 'Linux_ARM64.tar.gz';
|
|
||||||
case 'x64': return 'Linux_64bit.tar.gz';
|
|
||||||
default: return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default: return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { arduino } = pkg;
|
||||||
|
if (!arduino) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cli } = arduino;
|
||||||
|
if (!cli) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { version } = cli;
|
||||||
|
return version;
|
||||||
})();
|
})();
|
||||||
if (!suffix) {
|
|
||||||
shell.echo(`The CLI is not available for ${platform} ${arch}.`);
|
if (!version) {
|
||||||
|
shell.echo(`Could not retrieve CLI version info from the 'package.json'.`);
|
||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `https://downloads.arduino.cc/arduino-cli${version.startsWith('nightly-') ? '/nightly' : ''}/arduino-cli_${version}_${suffix}`;
|
const { platform, arch } = process;
|
||||||
downloader.downloadUnzipFile(url, cli, 'arduino-cli', force);
|
const buildFolder = path.join(__dirname, '..', 'build');
|
||||||
|
const cliName = `arduino-cli${platform === 'win32' ? '.exe' : ''}`;
|
||||||
|
const destinationPath = path.join(buildFolder, cliName);
|
||||||
|
|
||||||
|
if (typeof version === 'string') {
|
||||||
|
const suffix = (() => {
|
||||||
|
switch (platform) {
|
||||||
|
case 'darwin': return 'macOS_64bit.tar.gz';
|
||||||
|
case 'win32': return 'Windows_64bit.zip';
|
||||||
|
case 'linux': {
|
||||||
|
switch (arch) {
|
||||||
|
case 'arm': return 'Linux_ARMv7.tar.gz';
|
||||||
|
case 'arm64': return 'Linux_ARM64.tar.gz';
|
||||||
|
case 'x64': return 'Linux_64bit.tar.gz';
|
||||||
|
default: return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: return undefined;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
if (!suffix) {
|
||||||
|
shell.echo(`The CLI is not available for ${platform} ${arch}.`);
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
if (semver.valid(version)) {
|
||||||
|
const url = `https://downloads.arduino.cc/arduino-cli/arduino-cli_${version}_${suffix}`;
|
||||||
|
shell.echo(`📦 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(`🌙 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);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
if (!repo) {
|
||||||
|
shell.echo(`Could not retrieve 'repo' from ${JSON.stringify(version)}`);
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
const url = `https://github.com/${owner}/${repo}.git`;
|
||||||
|
shell.echo(`Building CLI from ${url}. Commitish: ${commitish ? commitish : 'HEAD'}`);
|
||||||
|
|
||||||
|
if (fs.existsSync(destinationPath)) {
|
||||||
|
shell.echo(`Skipping the CLI build because it already exists: ${destinationPath}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shell.mkdir('-p', buildFolder).code !== 0) {
|
||||||
|
shell.echo('Could not create build folder.');
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempRepoPath = temp.mkdirSync();
|
||||||
|
shell.echo(`>>> Cloning CLI source to ${tempRepoPath}...`);
|
||||||
|
if (shell.exec(`git clone ${url} ${tempRepoPath}`).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo('<<< Cloned CLI repo.')
|
||||||
|
|
||||||
|
if (commitish) {
|
||||||
|
shell.echo(`>>> Checking out ${commitish}...`);
|
||||||
|
if (shell.exec(`git -C ${tempRepoPath} checkout ${commitish}`).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo(`<<< Checked out ${commitish}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.echo(`>>> Building the CLI...`);
|
||||||
|
if (shell.exec('go build', { cwd: tempRepoPath }).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo('<<< CLI build done.')
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.join(tempRepoPath, cliName))) {
|
||||||
|
shell.echo(`Could not find the CLI at ${path.join(tempRepoPath, cliName)}.`);
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const builtCliPath = path.join(tempRepoPath, cliName);
|
||||||
|
shell.echo(`>>> Copying CLI from ${builtCliPath} to ${destinationPath}...`);
|
||||||
|
if (shell.cp(builtCliPath, destinationPath).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo(`<<< Copied the CLI.`);
|
||||||
|
|
||||||
|
shell.echo('<<< Verifying CLI...');
|
||||||
|
if (!fs.existsSync(destinationPath)) {
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo('>>> Verified CLI.');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
33
arduino-ide-extension/scripts/download-examples.js
Normal file
33
arduino-ide-extension/scripts/download-examples.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
// The version to use.
|
||||||
|
const version = '1.9.0';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const shell = require('shelljs');
|
||||||
|
const { v4 } = require('uuid');
|
||||||
|
|
||||||
|
const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`);
|
||||||
|
if (shell.mkdir('-p', repository).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shell.exec(`git clone https://github.com/arduino/arduino-examples.git ${repository}`).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const destination = path.join(__dirname, '..', 'Examples');
|
||||||
|
shell.mkdir('-p', destination);
|
||||||
|
shell.cp('-fR', path.join(repository, 'examples', '*'), destination);
|
||||||
|
|
||||||
|
})();
|
@@ -6,7 +6,7 @@
|
|||||||
(() => {
|
(() => {
|
||||||
|
|
||||||
const DEFAULT_ALS_VERSION = 'nightly';
|
const DEFAULT_ALS_VERSION = 'nightly';
|
||||||
const DEFAULT_CLANGD_VERSION = '9.0.0';
|
const DEFAULT_CLANGD_VERSION = 'snapshot_20210124';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const shell = require('shelljs');
|
const shell = require('shelljs');
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
.option('clangd-version', {
|
.option('clangd-version', {
|
||||||
alias: 'cv',
|
alias: 'cv',
|
||||||
default: DEFAULT_CLANGD_VERSION,
|
default: DEFAULT_CLANGD_VERSION,
|
||||||
choices: ['8.0.1', '9.0.0'],
|
choices: ['snapshot_20210124'],
|
||||||
describe: `The version of 'clangd' to download. Defaults to ${DEFAULT_CLANGD_VERSION}.`
|
describe: `The version of 'clangd' to download. Defaults to ${DEFAULT_CLANGD_VERSION}.`
|
||||||
})
|
})
|
||||||
.option('force-download', {
|
.option('force-download', {
|
||||||
@@ -38,35 +38,35 @@
|
|||||||
const { platform, arch } = process;
|
const { platform, arch } = process;
|
||||||
|
|
||||||
const build = path.join(__dirname, '..', 'build');
|
const build = path.join(__dirname, '..', 'build');
|
||||||
const alsTarget = path.join(build, `arduino-language-server${platform === 'win32' ? '.exe' : ''}`);
|
const lsExecutablePath = path.join(build, `arduino-language-server${platform === 'win32' ? '.exe' : ''}`);
|
||||||
|
|
||||||
let clangdTarget, alsSuffix, clangdSuffix;
|
let clangdExecutablePath, lsSuffix, clangdPrefix;
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case 'darwin':
|
case 'darwin':
|
||||||
clangdTarget = path.join(build, 'bin', 'clangd')
|
clangdExecutablePath = path.join(build, 'bin', 'clangd')
|
||||||
alsSuffix = 'Darwin_amd64.zip';
|
lsSuffix = 'macOS_amd64.zip';
|
||||||
clangdSuffix = 'macos.zip';
|
clangdPrefix = 'mac';
|
||||||
break;
|
break;
|
||||||
case 'linux':
|
case 'linux':
|
||||||
clangdTarget = path.join(build, 'bin', 'clangd')
|
clangdExecutablePath = path.join(build, 'bin', 'clangd')
|
||||||
alsSuffix = 'Linux_amd64.zip';
|
lsSuffix = 'Linux_amd64.zip';
|
||||||
clangdSuffix = 'linux.zip'
|
clangdPrefix = 'linux'
|
||||||
break;
|
break;
|
||||||
case 'win32':
|
case 'win32':
|
||||||
clangdTarget = path.join(build, 'clangd.exe')
|
clangdExecutablePath = path.join(build, 'bin', 'clangd.exe')
|
||||||
alsSuffix = 'Windows_NT_amd64.zip';
|
lsSuffix = 'Windows_amd64.zip';
|
||||||
clangdSuffix = 'windows.zip';
|
clangdPrefix = 'windows';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!alsSuffix) {
|
if (!lsSuffix) {
|
||||||
shell.echo(`The arduino-language-server is not available for ${platform} ${arch}.`);
|
shell.echo(`The arduino-language-server is not available for ${platform} ${arch}.`);
|
||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${alsVersion === 'nightly' ? 'nightly/arduino-language-server' : 'arduino-language-server_' + alsVersion}_${alsSuffix}`;
|
const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${alsVersion === 'nightly' ? 'nightly/arduino-language-server' : 'arduino-language-server_' + alsVersion}_${lsSuffix}`;
|
||||||
downloader.downloadUnzipAll(alsUrl, build, alsTarget, force);
|
downloader.downloadUnzipAll(alsUrl, build, lsExecutablePath, force);
|
||||||
|
|
||||||
const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd_${clangdVersion}_${clangdSuffix}`;
|
const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd-${clangdPrefix}-${clangdVersion}.zip`;
|
||||||
downloader.downloadUnzipAll(clangdUrl, build, clangdTarget, force);
|
downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, { strip: 1 }); // `strip`: the new clangd (12.x) is zipped into a folder, so we have to strip the outmost folder.
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@@ -21,9 +21,9 @@ process.on('uncaughtException', error => {
|
|||||||
* @param url {string} Download URL
|
* @param url {string} Download URL
|
||||||
* @param targetFile {string} Path to the file to copy from the decompressed archive
|
* @param targetFile {string} Path to the file to copy from the decompressed archive
|
||||||
* @param filePrefix {string} Prefix of the file name found in the archive
|
* @param filePrefix {string} Prefix of the file name found in the archive
|
||||||
* @param force {boolean} Whether to download even if the target file exists
|
* @param force {boolean} Whether to download even if the target file exists. `false` by default.
|
||||||
*/
|
*/
|
||||||
exports.downloadUnzipFile = async (url, targetFile, filePrefix, force) => {
|
exports.downloadUnzipFile = async (url, targetFile, filePrefix, force = false) => {
|
||||||
if (fs.existsSync(targetFile) && !force) {
|
if (fs.existsSync(targetFile) && !force) {
|
||||||
shell.echo(`Skipping download because file already exists: ${targetFile}`);
|
shell.echo(`Skipping download because file already exists: ${targetFile}`);
|
||||||
return;
|
return;
|
||||||
@@ -79,7 +79,7 @@ exports.downloadUnzipFile = async (url, targetFile, filePrefix, force) => {
|
|||||||
* @param targetFile {string} Path to the main file expected after decompressing
|
* @param targetFile {string} Path to the main file expected after decompressing
|
||||||
* @param force {boolean} Whether to download even if the target file exists
|
* @param force {boolean} Whether to download even if the target file exists
|
||||||
*/
|
*/
|
||||||
exports.downloadUnzipAll = async (url, targetDir, targetFile, force) => {
|
exports.downloadUnzipAll = async (url, targetDir, targetFile, force, decompressOptions = undefined) => {
|
||||||
if (fs.existsSync(targetFile) && !force) {
|
if (fs.existsSync(targetFile) && !force) {
|
||||||
shell.echo(`Skipping download because file already exists: ${targetFile}`);
|
shell.echo(`Skipping download because file already exists: ${targetFile}`);
|
||||||
return;
|
return;
|
||||||
@@ -96,12 +96,16 @@ exports.downloadUnzipAll = async (url, targetDir, targetFile, force) => {
|
|||||||
shell.echo(`<<< Download succeeded.`);
|
shell.echo(`<<< Download succeeded.`);
|
||||||
|
|
||||||
shell.echo('>>> Decompressing...');
|
shell.echo('>>> Decompressing...');
|
||||||
const files = await decompress(data, targetDir, {
|
let options = {
|
||||||
plugins: [
|
plugins: [
|
||||||
unzip(),
|
unzip(),
|
||||||
untargz()
|
untargz()
|
||||||
]
|
]
|
||||||
});
|
};
|
||||||
|
if (decompressOptions) {
|
||||||
|
options = Object.assign(options, decompressOptions)
|
||||||
|
}
|
||||||
|
const files = await decompress(data, targetDir, options);
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
shell.echo('Error ocurred while decompressing the archive.');
|
shell.echo('Error ocurred while decompressing the archive.');
|
||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
|
@@ -16,23 +16,98 @@
|
|||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shell.exec(`git clone https://github.com/arduino/arduino-cli.git ${repository}`).code !== 0) {
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { arduino } = pkg;
|
||||||
|
if (!arduino) {
|
||||||
|
return { owner: 'arduino', repo: 'arduino-cli' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cli } = arduino;
|
||||||
|
if (!cli) {
|
||||||
|
return { owner: 'arduino', repo: 'arduino-cli' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { version } = cli;
|
||||||
|
if (!version) {
|
||||||
|
return { owner: 'arduino', repo: 'arduino-cli' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof version === 'string') {
|
||||||
|
return { owner: 'arduino', repo: 'arduino-cli' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
if (!repo) {
|
||||||
|
shell.echo(`Could not retrieve 'repo' from ${JSON.stringify(version)}`);
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { owner, repo, commitish };
|
||||||
|
})();
|
||||||
|
|
||||||
|
const url = `https://github.com/${owner}/${repo}.git`;
|
||||||
|
shell.echo(`>>> Cloning repository from '${url}'...`);
|
||||||
|
if (shell.exec(`git clone ${url} ${repository}`).code !== 0) {
|
||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
|
shell.echo(`<<< Repository cloned.`);
|
||||||
|
|
||||||
const { platform } = process;
|
const { platform } = process;
|
||||||
const build = path.join(__dirname, '..', 'build');
|
const build = path.join(__dirname, '..', 'build');
|
||||||
const cli = path.join(build, `arduino-cli${platform === 'win32' ? '.exe' : ''}`);
|
const cli = path.join(build, `arduino-cli${platform === 'win32' ? '.exe' : ''}`);
|
||||||
const rawVersion = shell.exec(`${cli} version`).trim();
|
const versionJson = shell.exec(`${cli} version --format json`).trim();
|
||||||
if (!rawVersion) {
|
if (!versionJson) {
|
||||||
shell.echo(`Could not retrieve the CLI version from ${cli}.`);
|
shell.echo(`Could not retrieve the CLI version from ${cli}.`);
|
||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
const version = rawVersion.substring(rawVersion.lastIndexOf('Commit:') + 'Commit:'.length).trim();
|
// As of today (28.01.2021), the `VersionString` can be one of the followings:
|
||||||
if (version) {
|
// - `nightly-YYYYMMDD` stands for the nightly build, we use the , the `commitish` from the `package.json` to check out the code.
|
||||||
if (shell.exec(`git -C ${repository} checkout ${version} -b ${version}`).code !== 0) {
|
// - `0.0.0-git` for local builds, we use the `commitish` from the `package.json` to check out the code and generate the APIs.
|
||||||
|
// - `git-snapshot` for local build executed via `task build`. We do not do this.
|
||||||
|
// - rest, we assume it is a valid semver and has the corresponding tagged code, we use the tag to generate the APIs from the `proto` files.
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"Application": "arduino-cli",
|
||||||
|
"VersionString": "nightly-20210126",
|
||||||
|
"Commit": "079bb6c6",
|
||||||
|
"Status": "alpha",
|
||||||
|
"Date": "2021-01-26T01:46:31Z"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const versionObject = JSON.parse(versionJson);
|
||||||
|
const version = versionObject.VersionString;
|
||||||
|
if (version && !version.startsWith('nightly-') && version !== '0.0.0-git' && version !== 'git-snapshot') {
|
||||||
|
shell.echo(`>>> Checking out tagged version: '${version}'...`);
|
||||||
|
shell.exec(`git -C ${repository} fetch --all --tags`);
|
||||||
|
if (shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`).code !== 0) {
|
||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
|
shell.echo(`<<< Checked out tagged version: '${commitish}'.`);
|
||||||
|
} else if (commitish) {
|
||||||
|
shell.echo(`>>> Checking out commitish from 'package.json': '${commitish}'...`);
|
||||||
|
if (shell.exec(`git -C ${repository} checkout ${commitish}`).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo(`<<< Checked out commitish from 'package.json': '${commitish}'.`);
|
||||||
|
} else if (versionObject.Commit) {
|
||||||
|
shell.echo(`>>> Checking out commitish from the CLI: '${versionObject.Commit}'...`);
|
||||||
|
if (shell.exec(`git -C ${repository} checkout ${versionObject.Commit}`).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo(`<<< Checked out commitish from the CLI: '${versionObject.Commit}'.`);
|
||||||
|
} else {
|
||||||
|
shell.echo(`WARN: no 'git checkout'. Generating from the HEAD revision.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.echo('>>> Generating TS/JS API from:');
|
shell.echo('>>> Generating TS/JS API from:');
|
||||||
|
@@ -20,11 +20,4 @@ export namespace ArduinoCommands {
|
|||||||
id: 'arduino-open-boards-dialog'
|
id: 'arduino-open-boards-dialog'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TOGGLE_ADVANCED_MODE: Command = {
|
|
||||||
id: 'arduino-toggle-advanced-mode'
|
|
||||||
};
|
|
||||||
export const TOGGLE_ADVANCED_MODE_TOOLBAR: Command = {
|
|
||||||
id: 'arduino-toggle-advanced-mode-toolbar'
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,53 +0,0 @@
|
|||||||
import { injectable, inject } from 'inversify';
|
|
||||||
import { ILogger } from '@theia/core/lib/common/logger';
|
|
||||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
|
||||||
import { ArduinoDaemonClient } from '../common/protocol';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ArduinoDaemonClientImpl implements ArduinoDaemonClient {
|
|
||||||
|
|
||||||
@inject(ILogger)
|
|
||||||
protected readonly logger: ILogger;
|
|
||||||
|
|
||||||
@inject(MessageService)
|
|
||||||
protected readonly messageService: MessageService;
|
|
||||||
|
|
||||||
protected readonly onStartedEmitter = new Emitter<void>();
|
|
||||||
protected readonly onStoppedEmitter = new Emitter<void>();
|
|
||||||
protected _isRunning = false;
|
|
||||||
|
|
||||||
notifyStopped(): void {
|
|
||||||
if (this._isRunning) {
|
|
||||||
this._isRunning = false;
|
|
||||||
this.onStoppedEmitter.fire();
|
|
||||||
this.info('The CLI daemon process has stopped.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyStarted(): void {
|
|
||||||
if (!this._isRunning) {
|
|
||||||
this._isRunning = true;
|
|
||||||
this.onStartedEmitter.fire();
|
|
||||||
this.info('The CLI daemon process has started.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get onDaemonStarted(): Event<void> {
|
|
||||||
return this.onStartedEmitter.event;
|
|
||||||
}
|
|
||||||
|
|
||||||
get onDaemonStopped(): Event<void> {
|
|
||||||
return this.onStoppedEmitter.event;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRunning(): boolean {
|
|
||||||
return this._isRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected info(message: string): void {
|
|
||||||
this.messageService.info(message, { timeout: 3000 });
|
|
||||||
this.logger.info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,4 +1,5 @@
|
|||||||
import { MAIN_MENU_BAR, MenuContribution, MenuModelRegistry, SelectionService } from '@theia/core';
|
const debounce = require('lodash.debounce');
|
||||||
|
import { MAIN_MENU_BAR, MenuContribution, MenuModelRegistry, SelectionService, ILogger } from '@theia/core';
|
||||||
import {
|
import {
|
||||||
ContextMenuRenderer,
|
ContextMenuRenderer,
|
||||||
FrontendApplication, FrontendApplicationContribution,
|
FrontendApplication, FrontendApplicationContribution,
|
||||||
@@ -13,7 +14,6 @@ import { MessageService } from '@theia/core/lib/common/message-service';
|
|||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { EditorMainMenu, EditorManager } from '@theia/editor/lib/browser';
|
import { EditorMainMenu, EditorManager } from '@theia/editor/lib/browser';
|
||||||
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
|
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
|
||||||
import { FileSystem } from '@theia/filesystem/lib/common';
|
|
||||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||||
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
||||||
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||||
@@ -24,8 +24,9 @@ import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspac
|
|||||||
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
||||||
import { inject, injectable, postConstruct } from 'inversify';
|
import { inject, injectable, postConstruct } from 'inversify';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { remote } from 'electron';
|
||||||
import { MainMenuManager } from '../common/main-menu-manager';
|
import { MainMenuManager } from '../common/main-menu-manager';
|
||||||
import { BoardsService, BoardsServiceClient, CoreService, Port, SketchesService, ToolOutputServiceClient } from '../common/protocol';
|
import { BoardsService, CoreService, Port, SketchesService, ExecutableService } from '../common/protocol';
|
||||||
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
||||||
import { ConfigService } from '../common/protocol/config-service';
|
import { ConfigService } from '../common/protocol/config-service';
|
||||||
import { FileSystemExt } from '../common/protocol/filesystem-ext';
|
import { FileSystemExt } from '../common/protocol/filesystem-ext';
|
||||||
@@ -33,7 +34,7 @@ import { ArduinoCommands } from './arduino-commands';
|
|||||||
import { BoardsConfig } from './boards/boards-config';
|
import { BoardsConfig } from './boards/boards-config';
|
||||||
import { BoardsConfigDialog } from './boards/boards-config-dialog';
|
import { BoardsConfigDialog } from './boards/boards-config-dialog';
|
||||||
import { BoardsDataStore } from './boards/boards-data-store';
|
import { BoardsDataStore } from './boards/boards-data-store';
|
||||||
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
|
import { BoardsServiceProvider } from './boards/boards-service-provider';
|
||||||
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
||||||
import { EditorMode } from './editor-mode';
|
import { EditorMode } from './editor-mode';
|
||||||
import { ArduinoMenus } from './menu/arduino-menus';
|
import { ArduinoMenus } from './menu/arduino-menus';
|
||||||
@@ -41,11 +42,20 @@ import { MonitorConnection } from './monitor/monitor-connection';
|
|||||||
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
||||||
import { WorkspaceService } from './theia/workspace/workspace-service';
|
import { WorkspaceService } from './theia/workspace/workspace-service';
|
||||||
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
||||||
|
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||||
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||||
|
import { OutputService } from '../common/protocol/output-service';
|
||||||
|
import { ArduinoPreferences } from './arduino-preferences';
|
||||||
|
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
|
||||||
|
import { SaveAsSketch } from './contributions/save-as-sketch';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ArduinoFrontendContribution implements FrontendApplicationContribution,
|
export class ArduinoFrontendContribution implements FrontendApplicationContribution,
|
||||||
TabBarToolbarContribution, CommandContribution, MenuContribution, ColorContribution {
|
TabBarToolbarContribution, CommandContribution, MenuContribution, ColorContribution {
|
||||||
|
|
||||||
|
@inject(ILogger)
|
||||||
|
protected logger: ILogger;
|
||||||
|
|
||||||
@inject(MessageService)
|
@inject(MessageService)
|
||||||
protected readonly messageService: MessageService;
|
protected readonly messageService: MessageService;
|
||||||
|
|
||||||
@@ -55,15 +65,8 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
|||||||
@inject(CoreService)
|
@inject(CoreService)
|
||||||
protected readonly coreService: CoreService;
|
protected readonly coreService: CoreService;
|
||||||
|
|
||||||
@inject(ToolOutputServiceClient)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly toolOutputServiceClient: ToolOutputServiceClient;
|
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
|
||||||
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
|
|
||||||
|
|
||||||
// Unused but do not remove it. It's required by DI, otherwise `init` method is not called.
|
|
||||||
@inject(BoardsServiceClient)
|
|
||||||
protected readonly boardsServiceClient: BoardsServiceClient;
|
|
||||||
|
|
||||||
@inject(SelectionService)
|
@inject(SelectionService)
|
||||||
protected readonly selectionService: SelectionService;
|
protected readonly selectionService: SelectionService;
|
||||||
@@ -77,8 +80,8 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
|||||||
@inject(FileDialogService)
|
@inject(FileDialogService)
|
||||||
protected readonly fileDialogService: FileDialogService;
|
protected readonly fileDialogService: FileDialogService;
|
||||||
|
|
||||||
@inject(FileSystem)
|
@inject(FileService)
|
||||||
protected readonly fileSystem: FileSystem;
|
protected readonly fileSystem: FileService;
|
||||||
|
|
||||||
@inject(SketchesService)
|
@inject(SketchesService)
|
||||||
protected readonly sketchService: SketchesService;
|
protected readonly sketchService: SketchesService;
|
||||||
@@ -140,6 +143,23 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
|||||||
@inject(FileSystemExt)
|
@inject(FileSystemExt)
|
||||||
protected readonly fileSystemExt: FileSystemExt;
|
protected readonly fileSystemExt: FileSystemExt;
|
||||||
|
|
||||||
|
@inject(HostedPluginSupport)
|
||||||
|
protected hostedPluginSupport: HostedPluginSupport;
|
||||||
|
|
||||||
|
@inject(ExecutableService)
|
||||||
|
protected executableService: ExecutableService;
|
||||||
|
|
||||||
|
@inject(OutputService)
|
||||||
|
protected readonly outputService: OutputService;
|
||||||
|
|
||||||
|
@inject(ArduinoPreferences)
|
||||||
|
protected readonly arduinoPreferences: ArduinoPreferences;
|
||||||
|
|
||||||
|
@inject(SketchesServiceClientImpl)
|
||||||
|
protected readonly sketchServiceClient: SketchesServiceClientImpl;
|
||||||
|
|
||||||
|
protected invalidConfigPopup: Promise<void | 'No' | 'Yes' | undefined> | undefined;
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected async init(): Promise<void> {
|
protected async init(): Promise<void> {
|
||||||
if (!window.navigator.onLine) {
|
if (!window.navigator.onLine) {
|
||||||
@@ -177,6 +197,61 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
|||||||
viewContribution.initializeLayout(app);
|
viewContribution.initializeLayout(app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const start = async ({ selectedBoard }: BoardsConfig.Config) => {
|
||||||
|
if (selectedBoard) {
|
||||||
|
const { name, fqbn } = selectedBoard;
|
||||||
|
if (fqbn) {
|
||||||
|
await this.hostedPluginSupport.didStart;
|
||||||
|
this.startLanguageServer(fqbn, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.boardsServiceClientImpl.onBoardsConfigChanged(start);
|
||||||
|
this.arduinoPreferences.onPreferenceChanged(event => {
|
||||||
|
if (event.preferenceName === 'arduino.language.log' && event.newValue !== event.oldValue) {
|
||||||
|
start(this.boardsServiceClientImpl.boardsConfig);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.arduinoPreferences.ready.then(() => {
|
||||||
|
const webContents = remote.getCurrentWebContents();
|
||||||
|
const zoomLevel = this.arduinoPreferences.get('arduino.window.zoomLevel');
|
||||||
|
webContents.setZoomLevel(zoomLevel);
|
||||||
|
});
|
||||||
|
this.arduinoPreferences.onPreferenceChanged(event => {
|
||||||
|
if (event.preferenceName === 'arduino.window.zoomLevel' && typeof event.newValue === 'number' && event.newValue !== event.oldValue) {
|
||||||
|
const webContents = remote.getCurrentWebContents();
|
||||||
|
webContents.setZoomLevel(event.newValue || 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected startLanguageServer = debounce((fqbn: string, name: string | undefined) => this.doStartLanguageServer(fqbn, name));
|
||||||
|
protected async doStartLanguageServer(fqbn: string, name: string | undefined): Promise<void> {
|
||||||
|
this.logger.info(`Starting language server: ${fqbn}`);
|
||||||
|
const log = this.arduinoPreferences.get('arduino.language.log');
|
||||||
|
let currentSketchPath: string | undefined = undefined;
|
||||||
|
if (log) {
|
||||||
|
const currentSketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (currentSketch) {
|
||||||
|
currentSketchPath = await this.fileSystem.fsPath(new URI(currentSketch.uri));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { clangdUri, cliUri, lsUri } = await this.executableService.list();
|
||||||
|
const [clangdPath, cliPath, lsPath] = await Promise.all([
|
||||||
|
this.fileSystem.fsPath(new URI(clangdUri)),
|
||||||
|
this.fileSystem.fsPath(new URI(cliUri)),
|
||||||
|
this.fileSystem.fsPath(new URI(lsUri)),
|
||||||
|
]);
|
||||||
|
this.commandRegistry.executeCommand('arduino.languageserver.start', {
|
||||||
|
lsPath,
|
||||||
|
cliPath,
|
||||||
|
clangdPath,
|
||||||
|
log: currentSketchPath ? currentSketchPath : log,
|
||||||
|
board: {
|
||||||
|
fqbn,
|
||||||
|
name: name ? `"${name}"` : undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||||
@@ -194,12 +269,6 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
|||||||
command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR,
|
command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR,
|
||||||
tooltip: 'Serial Monitor'
|
tooltip: 'Serial Monitor'
|
||||||
});
|
});
|
||||||
registry.registerItem({
|
|
||||||
id: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
|
||||||
command: ArduinoCommands.TOGGLE_ADVANCED_MODE_TOOLBAR.id,
|
|
||||||
tooltip: this.editorMode.proMode ? 'Switch to Classic Mode' : 'Switch to Advanced Mode',
|
|
||||||
text: this.editorMode.proMode ? '$(toggle-on)' : '$(toggle-off)'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCommands(registry: CommandRegistry): void {
|
registerCommands(registry: CommandRegistry): void {
|
||||||
@@ -208,62 +277,56 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
|||||||
isToggled: () => this.editorMode.compileForDebug
|
isToggled: () => this.editorMode.compileForDebug
|
||||||
});
|
});
|
||||||
registry.registerCommand(ArduinoCommands.OPEN_SKETCH_FILES, {
|
registry.registerCommand(ArduinoCommands.OPEN_SKETCH_FILES, {
|
||||||
execute: async (uri: string) => {
|
execute: async (uri: URI) => {
|
||||||
this.openSketchFiles(uri);
|
this.openSketchFiles(uri);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, {
|
registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, {
|
||||||
execute: async () => {
|
execute: async (query?: string | undefined) => {
|
||||||
const boardsConfig = await this.boardsConfigDialog.open();
|
const boardsConfig = await this.boardsConfigDialog.open(query);
|
||||||
if (boardsConfig) {
|
if (boardsConfig) {
|
||||||
this.boardsServiceClientImpl.boardsConfig = boardsConfig;
|
this.boardsServiceClientImpl.boardsConfig = boardsConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE, {
|
|
||||||
isToggled: () => this.editorMode.proMode,
|
|
||||||
execute: () => this.editorMode.toggleProMode()
|
|
||||||
});
|
|
||||||
registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE_TOOLBAR, {
|
|
||||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right',
|
|
||||||
isToggled: () => this.editorMode.proMode,
|
|
||||||
execute: () => this.editorMode.toggleProMode()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMenus(registry: MenuModelRegistry) {
|
registerMenus(registry: MenuModelRegistry) {
|
||||||
if (!this.editorMode.proMode) {
|
const menuId = (menuPath: string[]): string => {
|
||||||
const menuId = (menuPath: string[]): string => {
|
const index = menuPath.length - 1;
|
||||||
const index = menuPath.length - 1;
|
const menuId = menuPath[index];
|
||||||
const menuId = menuPath[index];
|
return menuId;
|
||||||
return menuId;
|
|
||||||
}
|
|
||||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(MonacoMenus.SELECTION));
|
|
||||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(EditorMainMenu.GO));
|
|
||||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(TerminalMenus.TERMINAL));
|
|
||||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(CommonMenus.VIEW));
|
|
||||||
}
|
}
|
||||||
|
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(MonacoMenus.SELECTION));
|
||||||
|
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(EditorMainMenu.GO));
|
||||||
|
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(TerminalMenus.TERMINAL));
|
||||||
|
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(CommonMenus.VIEW));
|
||||||
|
|
||||||
registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch');
|
registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch');
|
||||||
registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools');
|
registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools');
|
||||||
registry.registerMenuAction(ArduinoMenus.SKETCH, {
|
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||||
commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id,
|
commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id,
|
||||||
label: 'Optimize for Debugging',
|
label: 'Optimize for Debugging',
|
||||||
order: '1'
|
order: '4'
|
||||||
});
|
|
||||||
registry.registerMenuAction(CommonMenus.HELP, {
|
|
||||||
commandId: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
|
||||||
label: 'Advanced Mode'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async openSketchFiles(uri: string): Promise<void> {
|
protected async openSketchFiles(uri: URI): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const sketch = await this.sketchService.loadSketch(uri);
|
const sketch = await this.sketchService.loadSketch(uri.toString());
|
||||||
const { mainFileUri, otherSketchFileUris, additionalFileUris } = sketch;
|
const { mainFileUri, rootFolderFileUris } = sketch;
|
||||||
for (const uri of [mainFileUri, ...otherSketchFileUris, ...additionalFileUris]) {
|
for (const uri of [mainFileUri, ...rootFolderFileUris]) {
|
||||||
await this.ensureOpened(uri);
|
await this.ensureOpened(uri);
|
||||||
}
|
}
|
||||||
await this.ensureOpened(mainFileUri, true);
|
await this.ensureOpened(mainFileUri, true);
|
||||||
|
if (mainFileUri.endsWith('.pde')) {
|
||||||
|
const message = `The '${sketch.name}' still uses the old \`.pde\` format. Do you want to switch to the new \`.ino\` extension?`;
|
||||||
|
this.messageService.info(message, 'Later', 'Yes').then(async answer => {
|
||||||
|
if (answer === 'Yes') {
|
||||||
|
this.commandRegistry.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, { execOnlyIfTemp: false, openAfterMove: true, wipeOriginal: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
const message = e instanceof Error ? e.message : JSON.stringify(e);
|
const message = e instanceof Error ? e.message : JSON.stringify(e);
|
||||||
@@ -303,7 +366,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
|||||||
light: 'editorWidget.background',
|
light: 'editorWidget.background',
|
||||||
hc: 'editorWidget.background'
|
hc: 'editorWidget.background'
|
||||||
},
|
},
|
||||||
description: 'Color of the Arduino Pro IDE foreground which is used for dialogs, such as the Select Board dialog.'
|
description: 'Color of the Arduino IDE foreground which is used for dialogs, such as the Select Board dialog.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'arduino.toolbar.background',
|
id: 'arduino.toolbar.background',
|
||||||
|
@@ -6,23 +6,16 @@ import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contrib
|
|||||||
import { TabBarToolbarContribution, TabBarToolbarFactory } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
import { TabBarToolbarContribution, TabBarToolbarFactory } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||||
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
|
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
|
||||||
import { FrontendApplicationContribution, FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application'
|
import { FrontendApplicationContribution, FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application'
|
||||||
import { LanguageGrammarDefinitionContribution } from '@theia/monaco/lib/browser/textmate';
|
|
||||||
import { LanguageClientContribution } from '@theia/languages/lib/browser';
|
|
||||||
import { ArduinoLanguageClientContribution } from './language/arduino-language-client-contribution';
|
|
||||||
import { LibraryListWidget } from './library/library-list-widget';
|
import { LibraryListWidget } from './library/library-list-widget';
|
||||||
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
|
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
|
||||||
import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution';
|
|
||||||
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
|
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
|
||||||
import { BoardsService, BoardsServicePath, BoardsServiceClient } from '../common/protocol/boards-service';
|
import { BoardsService, BoardsServicePath } from '../common/protocol/boards-service';
|
||||||
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
|
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
|
||||||
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
|
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
|
||||||
import { CoreService, CoreServicePath, CoreServiceClient } from '../common/protocol/core-service';
|
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
|
||||||
import { BoardsListWidget } from './boards/boards-list-widget';
|
import { BoardsListWidget } from './boards/boards-list-widget';
|
||||||
import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution';
|
import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution';
|
||||||
import { ToolOutputServiceClient } from '../common/protocol/tool-output-service';
|
import { BoardsServiceProvider } from './boards/boards-service-provider';
|
||||||
import { ToolOutputService } from '../common/protocol/tool-output-service';
|
|
||||||
import { ToolOutputServiceClientImpl } from './tool-output/client-service-impl';
|
|
||||||
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
|
|
||||||
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||||
import { WorkspaceService } from './theia/workspace/workspace-service';
|
import { WorkspaceService } from './theia/workspace/workspace-service';
|
||||||
import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||||
@@ -40,7 +33,9 @@ import {
|
|||||||
ApplicationShell as TheiaApplicationShell,
|
ApplicationShell as TheiaApplicationShell,
|
||||||
ShellLayoutRestorer as TheiaShellLayoutRestorer,
|
ShellLayoutRestorer as TheiaShellLayoutRestorer,
|
||||||
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
||||||
KeybindingRegistry as TheiaKeybindingRegistry
|
KeybindingRegistry as TheiaKeybindingRegistry,
|
||||||
|
TabBarRendererFactory,
|
||||||
|
ContextMenuRenderer
|
||||||
} from '@theia/core/lib/browser';
|
} from '@theia/core/lib/browser';
|
||||||
import { MenuContribution } from '@theia/core/lib/common/menu';
|
import { MenuContribution } from '@theia/core/lib/common/menu';
|
||||||
import { ApplicationShell } from './theia/core/application-shell';
|
import { ApplicationShell } from './theia/core/application-shell';
|
||||||
@@ -54,7 +49,7 @@ import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspa
|
|||||||
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
|
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
|
||||||
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
|
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
|
||||||
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
|
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
|
||||||
import { ConfigService, ConfigServicePath, ConfigServiceClient } from '../common/protocol/config-service';
|
import { ConfigService, ConfigServicePath } from '../common/protocol/config-service';
|
||||||
import { MonitorWidget } from './monitor/monitor-widget';
|
import { MonitorWidget } from './monitor/monitor-widget';
|
||||||
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
||||||
import { MonitorConnection } from './monitor/monitor-connection';
|
import { MonitorConnection } from './monitor/monitor-connection';
|
||||||
@@ -64,24 +59,19 @@ import { TabBarDecoratorService } from './theia/core/tab-bar-decorator';
|
|||||||
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser';
|
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser';
|
||||||
import { ProblemManager } from './theia/markers/problem-manager';
|
import { ProblemManager } from './theia/markers/problem-manager';
|
||||||
import { BoardsAutoInstaller } from './boards/boards-auto-installer';
|
import { BoardsAutoInstaller } from './boards/boards-auto-installer';
|
||||||
import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog';
|
|
||||||
import { AboutDialog } from './theia/core/about-dialog';
|
|
||||||
import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer';
|
import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer';
|
||||||
import { EditorMode } from './editor-mode';
|
import { EditorMode } from './editor-mode';
|
||||||
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
|
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
|
||||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||||
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
||||||
import { ArduinoDaemonClientImpl } from './arduino-daemon-client-impl';
|
import { ArduinoDaemonPath, ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
||||||
import { ArduinoDaemonClient, ArduinoDaemonPath, ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
import { EditorManager as TheiaEditorManager, EditorCommandContribution as TheiaEditorCommandContribution } from '@theia/editor/lib/browser';
|
||||||
import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser';
|
|
||||||
import { EditorManager } from './theia/editor/editor-manager';
|
import { EditorManager } from './theia/editor/editor-manager';
|
||||||
import { FrontendConnectionStatusService, ApplicationConnectionStatusContribution } from './theia/core/connection-status-service';
|
import { FrontendConnectionStatusService, ApplicationConnectionStatusContribution } from './theia/core/connection-status-service';
|
||||||
import {
|
import {
|
||||||
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
||||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution
|
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution
|
||||||
} from '@theia/core/lib/browser/connection-status-service';
|
} from '@theia/core/lib/browser/connection-status-service';
|
||||||
import { ConfigServiceClientImpl } from './config-service-client-impl';
|
|
||||||
import { CoreServiceClientImpl } from './core-service-client-impl';
|
|
||||||
import { BoardsDataMenuUpdater } from './boards/boards-data-menu-updater';
|
import { BoardsDataMenuUpdater } from './boards/boards-data-menu-updater';
|
||||||
import { BoardsDataStore } from './boards/boards-data-store';
|
import { BoardsDataStore } from './boards/boards-data-store';
|
||||||
import { ILogger } from '@theia/core';
|
import { ILogger } from '@theia/core';
|
||||||
@@ -95,7 +85,7 @@ import { WorkspaceFrontendContribution, ArduinoFileMenuContribution } from './th
|
|||||||
import { Contribution } from './contributions/contribution';
|
import { Contribution } from './contributions/contribution';
|
||||||
import { NewSketch } from './contributions/new-sketch';
|
import { NewSketch } from './contributions/new-sketch';
|
||||||
import { OpenSketch } from './contributions/open-sketch';
|
import { OpenSketch } from './contributions/open-sketch';
|
||||||
import { CloseSketch } from './contributions/close-sketch';
|
import { Close } from './contributions/close';
|
||||||
import { SaveAsSketch } from './contributions/save-as-sketch';
|
import { SaveAsSketch } from './contributions/save-as-sketch';
|
||||||
import { SaveSketch } from './contributions/save-sketch';
|
import { SaveSketch } from './contributions/save-sketch';
|
||||||
import { VerifySketch } from './contributions/verify-sketch';
|
import { VerifySketch } from './contributions/verify-sketch';
|
||||||
@@ -117,11 +107,46 @@ import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/l
|
|||||||
import { EditorWidgetFactory } from './theia/editor/editor-widget-factory';
|
import { EditorWidgetFactory } from './theia/editor/editor-widget-factory';
|
||||||
import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/output-widget';
|
import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||||
import { OutputWidget } from './theia/output/output-widget';
|
import { OutputWidget } from './theia/output/output-widget';
|
||||||
|
import { BurnBootloader } from './contributions/burn-bootloader';
|
||||||
|
import { ExamplesServicePath, ExamplesService } from '../common/protocol/examples-service';
|
||||||
|
import { BuiltInExamples, LibraryExamples } from './contributions/examples';
|
||||||
|
import { IncludeLibrary } from './contributions/include-library';
|
||||||
|
import { OutputChannelManager as TheiaOutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||||
|
import { OutputChannelManager } from './theia/output/output-channel';
|
||||||
|
import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, OutputChannelRegistryMainImpl } from './theia/plugin-ext/output-channel-registry-main';
|
||||||
|
import { ExecutableService, ExecutableServicePath } from '../common/protocol';
|
||||||
|
import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
|
||||||
|
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
|
||||||
|
import { OutputServiceImpl } from './output-service-impl';
|
||||||
|
import { OutputServicePath, OutputService } from '../common/protocol/output-service';
|
||||||
|
import { NotificationCenter } from './notification-center';
|
||||||
|
import { NotificationServicePath, NotificationServiceServer } from '../common/protocol';
|
||||||
|
import { About } from './contributions/about';
|
||||||
|
import { IconThemeService } from '@theia/core/lib/browser/icon-theme-service';
|
||||||
|
import { TabBarRenderer } from './theia/core/tab-bars';
|
||||||
|
import { EditorCommandContribution } from './theia/editor/editor-command';
|
||||||
|
import { NavigatorTabBarDecorator as TheiaNavigatorTabBarDecorator } from '@theia/navigator/lib/browser/navigator-tab-bar-decorator';
|
||||||
|
import { NavigatorTabBarDecorator } from './theia/navigator/navigator-tab-bar-decorator';
|
||||||
|
import { Debug } from './contributions/debug';
|
||||||
|
import { DebugSessionManager } from './theia/debug/debug-session-manager';
|
||||||
|
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||||
|
import { Sketchbook } from './contributions/sketchbook';
|
||||||
|
import { DebugFrontendApplicationContribution } from './theia/debug/debug-frontend-application-contribution';
|
||||||
|
import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||||
|
import { BoardSelection } from './contributions/board-selection';
|
||||||
|
import { OpenRecentSketch } from './contributions/open-recent-sketch';
|
||||||
|
import { Help } from './contributions/help';
|
||||||
|
import { bindArduinoPreferences } from './arduino-preferences'
|
||||||
|
import { SettingsService, SettingsDialog, SettingsWidget, SettingsDialogProps } from './settings';
|
||||||
|
import { AddFile } from './contributions/add-file';
|
||||||
|
import { ArchiveSketch } from './contributions/archive-sketch';
|
||||||
|
import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||||
|
import { OutputToolbarContribution } from './theia/output/output-toolbar-contribution';
|
||||||
|
|
||||||
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||||
|
|
||||||
MonacoThemingService.register({
|
MonacoThemingService.register({
|
||||||
id: 'arduinoTheme',
|
id: 'arduino-theme',
|
||||||
label: 'Light (Arduino)',
|
label: 'Light (Arduino)',
|
||||||
uiTheme: 'vs',
|
uiTheme: 'vs',
|
||||||
json: require('../../src/browser/data/arduino.color-theme.json')
|
json: require('../../src/browser/data/arduino.color-theme.json')
|
||||||
@@ -142,15 +167,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
bind(ArduinoToolbarContribution).toSelf().inSingletonScope();
|
bind(ArduinoToolbarContribution).toSelf().inSingletonScope();
|
||||||
bind(FrontendApplicationContribution).toService(ArduinoToolbarContribution);
|
bind(FrontendApplicationContribution).toService(ArduinoToolbarContribution);
|
||||||
|
|
||||||
// `ino` TextMate grammar and language client
|
|
||||||
bind(LanguageGrammarDefinitionContribution).to(ArduinoLanguageGrammarContribution).inSingletonScope();
|
|
||||||
bind(LanguageClientContribution).to(ArduinoLanguageClientContribution).inSingletonScope();
|
|
||||||
|
|
||||||
// Renderer for both the library and the core widgets.
|
// Renderer for both the library and the core widgets.
|
||||||
bind(ListItemRenderer).toSelf().inSingletonScope();
|
bind(ListItemRenderer).toSelf().inSingletonScope();
|
||||||
|
|
||||||
// Library service
|
// Library service
|
||||||
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
|
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
|
||||||
|
|
||||||
// Library list widget
|
// Library list widget
|
||||||
bind(LibraryListWidget).toSelf();
|
bind(LibraryListWidget).toSelf();
|
||||||
bindViewContribution(bind, LibraryListWidgetFrontendContribution);
|
bindViewContribution(bind, LibraryListWidgetFrontendContribution);
|
||||||
@@ -163,36 +185,16 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
// Sketch list service
|
// Sketch list service
|
||||||
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
|
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
|
||||||
bind(SketchesServiceClientImpl).toSelf().inSingletonScope();
|
bind(SketchesServiceClientImpl).toSelf().inSingletonScope();
|
||||||
|
bind(FrontendApplicationContribution).toService(SketchesServiceClientImpl);
|
||||||
|
|
||||||
// Config service
|
// Config service
|
||||||
bind(ConfigService).toDynamicValue(context => {
|
bind(ConfigService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ConfigServicePath)).inSingletonScope();
|
||||||
const connection = context.container.get(WebSocketConnectionProvider);
|
|
||||||
const client = context.container.get(ConfigServiceClientImpl);
|
|
||||||
return connection.createProxy(ConfigServicePath, client);
|
|
||||||
}).inSingletonScope();
|
|
||||||
bind(ConfigServiceClientImpl).toSelf().inSingletonScope();
|
|
||||||
bind(ConfigServiceClient).toDynamicValue(context => {
|
|
||||||
const client = context.container.get(ConfigServiceClientImpl);
|
|
||||||
WebSocketConnectionProvider.createProxy(context.container, ConfigServicePath, client);
|
|
||||||
return client;
|
|
||||||
}).inSingletonScope();
|
|
||||||
|
|
||||||
// Boards service
|
// Boards service
|
||||||
bind(BoardsService).toDynamicValue(context => {
|
bind(BoardsService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath)).inSingletonScope();
|
||||||
const connection = context.container.get(WebSocketConnectionProvider);
|
|
||||||
const client = context.container.get(BoardsServiceClientImpl);
|
|
||||||
return connection.createProxy(BoardsServicePath, client);
|
|
||||||
}).inSingletonScope();
|
|
||||||
// Boards service client to receive and delegate notifications from the backend.
|
// Boards service client to receive and delegate notifications from the backend.
|
||||||
bind(BoardsServiceClientImpl).toSelf().inSingletonScope();
|
bind(BoardsServiceProvider).toSelf().inSingletonScope();
|
||||||
bind(FrontendApplicationContribution).toService(BoardsServiceClientImpl);
|
bind(FrontendApplicationContribution).toService(BoardsServiceProvider);
|
||||||
bind(BoardsServiceClient).toDynamicValue(async context => {
|
|
||||||
const client = context.container.get(BoardsServiceClientImpl);
|
|
||||||
const service = context.container.get<BoardsService>(BoardsService);
|
|
||||||
await client.init(service);
|
|
||||||
WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath, client);
|
|
||||||
return client;
|
|
||||||
}).inSingletonScope();
|
|
||||||
|
|
||||||
// To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board.
|
// To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board.
|
||||||
bind(FrontendApplicationContribution).to(BoardsDataMenuUpdater).inSingletonScope();
|
bind(FrontendApplicationContribution).to(BoardsDataMenuUpdater).inSingletonScope();
|
||||||
@@ -225,26 +227,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Core service
|
// Core service
|
||||||
bind(CoreService).toDynamicValue(context => {
|
bind(CoreService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, CoreServicePath)).inSingletonScope();
|
||||||
const connection = context.container.get(WebSocketConnectionProvider);
|
|
||||||
const client = context.container.get(CoreServiceClientImpl);
|
|
||||||
return connection.createProxy(CoreServicePath, client);
|
|
||||||
}).inSingletonScope();
|
|
||||||
// Core service client to receive and delegate notifications when the index or the library index has been updated.
|
|
||||||
bind(CoreServiceClientImpl).toSelf().inSingletonScope();
|
|
||||||
bind(CoreServiceClient).toDynamicValue(context => {
|
|
||||||
const client = context.container.get(CoreServiceClientImpl);
|
|
||||||
WebSocketConnectionProvider.createProxy(context.container, CoreServicePath, client);
|
|
||||||
return client;
|
|
||||||
}).inSingletonScope();
|
|
||||||
|
|
||||||
// Tool output service client
|
|
||||||
bind(ToolOutputServiceClientImpl).toSelf().inSingletonScope();
|
|
||||||
bind(ToolOutputServiceClient).toDynamicValue(context => {
|
|
||||||
const client = context.container.get(ToolOutputServiceClientImpl);
|
|
||||||
WebSocketConnectionProvider.createProxy(context.container, ToolOutputService.SERVICE_PATH, client);
|
|
||||||
return client;
|
|
||||||
}).inSingletonScope();
|
|
||||||
|
|
||||||
// Serial monitor
|
// Serial monitor
|
||||||
bind(MonitorModel).toSelf().inSingletonScope();
|
bind(MonitorModel).toSelf().inSingletonScope();
|
||||||
@@ -303,6 +286,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
});
|
});
|
||||||
bind(OutputWidget).toSelf().inSingletonScope();
|
bind(OutputWidget).toSelf().inSingletonScope();
|
||||||
rebind(TheiaOutputWidget).toService(OutputWidget);
|
rebind(TheiaOutputWidget).toService(OutputWidget);
|
||||||
|
bind(OutputChannelManager).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaOutputChannelManager).toService(OutputChannelManager);
|
||||||
|
bind(OutputChannelRegistryMainImpl).toSelf().inTransientScope();
|
||||||
|
rebind(TheiaOutputChannelRegistryMainImpl).toService(OutputChannelRegistryMainImpl);
|
||||||
|
bind(MonacoTextModelService).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaMonacoTextModelService).toService(MonacoTextModelService);
|
||||||
|
|
||||||
// Show a disconnected status bar, when the daemon is not available
|
// Show a disconnected status bar, when the daemon is not available
|
||||||
bind(ApplicationConnectionStatusContribution).toSelf().inSingletonScope();
|
bind(ApplicationConnectionStatusContribution).toSelf().inSingletonScope();
|
||||||
@@ -322,33 +311,28 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
bind(ProblemManager).toSelf().inSingletonScope();
|
bind(ProblemManager).toSelf().inSingletonScope();
|
||||||
rebind(TheiaProblemManager).toService(ProblemManager);
|
rebind(TheiaProblemManager).toService(ProblemManager);
|
||||||
|
|
||||||
// About dialog to show the CLI version
|
|
||||||
bind(AboutDialog).toSelf().inSingletonScope();
|
|
||||||
rebind(TheiaAboutDialog).toService(AboutDialog);
|
|
||||||
|
|
||||||
// Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579
|
// Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579
|
||||||
bind(ShellLayoutRestorer).toSelf().inSingletonScope();
|
bind(ShellLayoutRestorer).toSelf().inSingletonScope();
|
||||||
rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer);
|
rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer);
|
||||||
|
|
||||||
// Arduino daemon client. Receives notifications from the backend if the CLI daemon process has been restarted.
|
// No dropdown for the _Output_ view.
|
||||||
bind(ArduinoDaemon).toDynamicValue(context => {
|
bind(OutputToolbarContribution).toSelf().inSingletonScope();
|
||||||
const connection = context.container.get(WebSocketConnectionProvider);
|
rebind(TheiaOutputToolbarContribution).toService(OutputToolbarContribution);
|
||||||
const client = context.container.get(ArduinoDaemonClientImpl);
|
|
||||||
return connection.createProxy(ArduinoDaemonPath, client);
|
bind(ArduinoDaemon).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ArduinoDaemonPath)).inSingletonScope();
|
||||||
}).inSingletonScope();
|
|
||||||
bind(ArduinoDaemonClientImpl).toSelf().inSingletonScope();
|
|
||||||
bind(ArduinoDaemonClient).toDynamicValue(context => {
|
|
||||||
const client = context.container.get(ArduinoDaemonClientImpl);
|
|
||||||
WebSocketConnectionProvider.createProxy(context.container, ArduinoDaemonPath, client);
|
|
||||||
return client;
|
|
||||||
}).inSingletonScope();
|
|
||||||
|
|
||||||
// File-system extension
|
// File-system extension
|
||||||
bind(FileSystemExt).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, FileSystemExtPath)).inSingletonScope();
|
bind(FileSystemExt).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, FileSystemExtPath)).inSingletonScope();
|
||||||
|
|
||||||
|
// Examples service@
|
||||||
|
bind(ExamplesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ExamplesServicePath)).inSingletonScope();
|
||||||
|
|
||||||
|
// Executable URIs known by the backend
|
||||||
|
bind(ExecutableService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ExecutableServicePath)).inSingletonScope();
|
||||||
|
|
||||||
Contribution.configure(bind, NewSketch);
|
Contribution.configure(bind, NewSketch);
|
||||||
Contribution.configure(bind, OpenSketch);
|
Contribution.configure(bind, OpenSketch);
|
||||||
Contribution.configure(bind, CloseSketch);
|
Contribution.configure(bind, Close);
|
||||||
Contribution.configure(bind, SaveSketch);
|
Contribution.configure(bind, SaveSketch);
|
||||||
Contribution.configure(bind, SaveAsSketch);
|
Contribution.configure(bind, SaveAsSketch);
|
||||||
Contribution.configure(bind, VerifySketch);
|
Contribution.configure(bind, VerifySketch);
|
||||||
@@ -358,4 +342,62 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
Contribution.configure(bind, QuitApp);
|
Contribution.configure(bind, QuitApp);
|
||||||
Contribution.configure(bind, SketchControl);
|
Contribution.configure(bind, SketchControl);
|
||||||
Contribution.configure(bind, Settings);
|
Contribution.configure(bind, Settings);
|
||||||
|
Contribution.configure(bind, BurnBootloader);
|
||||||
|
Contribution.configure(bind, BuiltInExamples);
|
||||||
|
Contribution.configure(bind, LibraryExamples);
|
||||||
|
Contribution.configure(bind, IncludeLibrary);
|
||||||
|
Contribution.configure(bind, About);
|
||||||
|
Contribution.configure(bind, Debug);
|
||||||
|
Contribution.configure(bind, Sketchbook);
|
||||||
|
Contribution.configure(bind, BoardSelection);
|
||||||
|
Contribution.configure(bind, OpenRecentSketch);
|
||||||
|
Contribution.configure(bind, Help);
|
||||||
|
Contribution.configure(bind, AddFile);
|
||||||
|
Contribution.configure(bind, ArchiveSketch);
|
||||||
|
|
||||||
|
bind(OutputServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, outputService) => {
|
||||||
|
WebSocketConnectionProvider.createProxy(container, OutputServicePath, outputService);
|
||||||
|
return outputService;
|
||||||
|
});
|
||||||
|
bind(OutputService).toService(OutputServiceImpl);
|
||||||
|
|
||||||
|
bind(NotificationCenter).toSelf().inSingletonScope();
|
||||||
|
bind(FrontendApplicationContribution).toService(NotificationCenter);
|
||||||
|
bind(NotificationServiceServer).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, NotificationServicePath)).inSingletonScope();
|
||||||
|
|
||||||
|
// Enable the dirty indicator on uncloseable widgets.
|
||||||
|
rebind(TabBarRendererFactory).toFactory(context => () => {
|
||||||
|
const contextMenuRenderer = context.container.get<ContextMenuRenderer>(ContextMenuRenderer);
|
||||||
|
const decoratorService = context.container.get<TabBarDecoratorService>(TabBarDecoratorService);
|
||||||
|
const iconThemeService = context.container.get<IconThemeService>(IconThemeService);
|
||||||
|
return new TabBarRenderer(contextMenuRenderer, decoratorService, iconThemeService);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Workaround for https://github.com/eclipse-theia/theia/issues/8722
|
||||||
|
// Do not trigger a save on IDE startup if `"editor.autoSave": "on"` was set as a preference.
|
||||||
|
bind(EditorCommandContribution).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaEditorCommandContribution).toService(EditorCommandContribution);
|
||||||
|
|
||||||
|
// Silent the badge decoration in the Explorer view.
|
||||||
|
bind(NavigatorTabBarDecorator).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaNavigatorTabBarDecorator).toService(NavigatorTabBarDecorator);
|
||||||
|
|
||||||
|
// To avoid running `Save All` when there are no dirty editors before starting the debug session.
|
||||||
|
bind(DebugSessionManager).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaDebugSessionManager).toService(DebugSessionManager);
|
||||||
|
// To remove the `Run` menu item from the application menu.
|
||||||
|
bind(DebugFrontendApplicationContribution).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaDebugFrontendApplicationContribution).toService(DebugFrontendApplicationContribution);
|
||||||
|
|
||||||
|
// Preferences
|
||||||
|
bindArduinoPreferences(bind);
|
||||||
|
|
||||||
|
// Settings wrapper for the preferences and the CLI config.
|
||||||
|
bind(SettingsService).toSelf().inSingletonScope();
|
||||||
|
// Settings dialog and widget
|
||||||
|
bind(SettingsWidget).toSelf().inSingletonScope();
|
||||||
|
bind(SettingsDialog).toSelf().inSingletonScope();
|
||||||
|
bind(SettingsDialogProps).toConstantValue({
|
||||||
|
title: 'Preferences'
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
73
arduino-ide-extension/src/browser/arduino-preferences.ts
Normal file
73
arduino-ide-extension/src/browser/arduino-preferences.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { interfaces } from 'inversify';
|
||||||
|
import {
|
||||||
|
createPreferenceProxy,
|
||||||
|
PreferenceProxy,
|
||||||
|
PreferenceService,
|
||||||
|
PreferenceContribution,
|
||||||
|
PreferenceSchema
|
||||||
|
} from '@theia/core/lib/browser/preferences';
|
||||||
|
|
||||||
|
export const ArduinoConfigSchema: PreferenceSchema = {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'arduino.language.log': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.",
|
||||||
|
'default': false
|
||||||
|
},
|
||||||
|
'arduino.compile.verbose': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'True for verbose compile output. False by default',
|
||||||
|
'default': false
|
||||||
|
},
|
||||||
|
'arduino.upload.verbose': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'True for verbose upload output. False by default.',
|
||||||
|
'default': false
|
||||||
|
},
|
||||||
|
'arduino.upload.verify': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'default': false
|
||||||
|
},
|
||||||
|
'arduino.window.autoScale': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'True if the user interface automatically scales with the font size.',
|
||||||
|
'default': true
|
||||||
|
},
|
||||||
|
'arduino.window.zoomLevel': {
|
||||||
|
'type': 'number',
|
||||||
|
'description': 'Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.',
|
||||||
|
'default': 0
|
||||||
|
},
|
||||||
|
'arduino.ide.autoUpdate': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'True to enable automatic update checks. The IDE will check for updates automatically and periodically.',
|
||||||
|
'default': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ArduinoConfiguration {
|
||||||
|
'arduino.language.log': boolean;
|
||||||
|
'arduino.compile.verbose': boolean;
|
||||||
|
'arduino.upload.verbose': boolean;
|
||||||
|
'arduino.upload.verify': boolean;
|
||||||
|
'arduino.window.autoScale': boolean;
|
||||||
|
'arduino.window.zoomLevel': number;
|
||||||
|
'arduino.ide.autoUpdate': boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ArduinoPreferences = Symbol('ArduinoPreferences');
|
||||||
|
export type ArduinoPreferences = PreferenceProxy<ArduinoConfiguration>;
|
||||||
|
|
||||||
|
export function createArduinoPreferences(preferences: PreferenceService): ArduinoPreferences {
|
||||||
|
return createPreferenceProxy(preferences, ArduinoConfigSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bindArduinoPreferences(bind: interfaces.Bind): void {
|
||||||
|
bind(ArduinoPreferences).toDynamicValue(ctx => {
|
||||||
|
const preferences = ctx.container.get<PreferenceService>(PreferenceService);
|
||||||
|
return createArduinoPreferences(preferences);
|
||||||
|
});
|
||||||
|
bind(PreferenceContribution).toConstantValue({ schema: ArduinoConfigSchema });
|
||||||
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
import { injectable, inject } from 'inversify';
|
import { injectable, inject } from 'inversify';
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||||
import { BoardsService, Board } from '../../common/protocol/boards-service';
|
import { BoardsService, BoardsPackage } from '../../common/protocol/boards-service';
|
||||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
import { BoardsServiceProvider } from './boards-service-provider';
|
||||||
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||||
import { InstallationProgressDialog } from '../widgets/progress-dialog';
|
import { InstallationProgressDialog } from '../widgets/progress-dialog';
|
||||||
import { BoardsConfig } from './boards-config';
|
import { BoardsConfig } from './boards-config';
|
||||||
@@ -20,8 +20,8 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
|||||||
@inject(BoardsService)
|
@inject(BoardsService)
|
||||||
protected readonly boardsService: BoardsService;
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||||
|
|
||||||
@inject(BoardsListWidgetFrontendContribution)
|
@inject(BoardsListWidgetFrontendContribution)
|
||||||
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;
|
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;
|
||||||
@@ -36,7 +36,7 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
|||||||
if (selectedBoard) {
|
if (selectedBoard) {
|
||||||
this.boardsService.search({}).then(packages => {
|
this.boardsService.search({}).then(packages => {
|
||||||
const candidates = packages
|
const candidates = packages
|
||||||
.filter(pkg => pkg.boards.some(board => Board.sameAs(board, selectedBoard)))
|
.filter(pkg => BoardsPackage.contains(selectedBoard, pkg))
|
||||||
.filter(({ installable, installedVersion }) => installable && !installedVersion);
|
.filter(({ installable, installedVersion }) => installable && !installedVersion);
|
||||||
for (const candidate of candidates) {
|
for (const candidate of candidates) {
|
||||||
// tslint:disable-next-line:max-line-length
|
// tslint:disable-next-line:max-line-length
|
||||||
|
@@ -4,9 +4,8 @@ import { Emitter } from '@theia/core/lib/common/event';
|
|||||||
import { ReactWidget, Message } from '@theia/core/lib/browser';
|
import { ReactWidget, Message } from '@theia/core/lib/browser';
|
||||||
import { BoardsService } from '../../common/protocol/boards-service';
|
import { BoardsService } from '../../common/protocol/boards-service';
|
||||||
import { BoardsConfig } from './boards-config';
|
import { BoardsConfig } from './boards-config';
|
||||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
import { BoardsServiceProvider } from './boards-service-provider';
|
||||||
import { CoreServiceClientImpl } from '../core-service-client-impl';
|
import { NotificationCenter } from '../notification-center';
|
||||||
import { ArduinoDaemonClientImpl } from '../arduino-daemon-client-impl';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsConfigDialogWidget extends ReactWidget {
|
export class BoardsConfigDialogWidget extends ReactWidget {
|
||||||
@@ -14,15 +13,13 @@ export class BoardsConfigDialogWidget extends ReactWidget {
|
|||||||
@inject(BoardsService)
|
@inject(BoardsService)
|
||||||
protected readonly boardsService: BoardsService;
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||||
|
|
||||||
@inject(CoreServiceClientImpl)
|
@inject(NotificationCenter)
|
||||||
protected readonly coreServiceClient: CoreServiceClientImpl;
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
@inject(ArduinoDaemonClientImpl)
|
|
||||||
protected readonly daemonClient: ArduinoDaemonClientImpl;
|
|
||||||
|
|
||||||
|
protected readonly onFilterTextDidChangeEmitter = new Emitter<string>();
|
||||||
protected readonly onBoardConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
protected readonly onBoardConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
||||||
readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event;
|
readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event;
|
||||||
|
|
||||||
@@ -31,6 +28,14 @@ export class BoardsConfigDialogWidget extends ReactWidget {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.id = 'select-board-dialog';
|
this.id = 'select-board-dialog';
|
||||||
|
this.toDispose.pushAll([
|
||||||
|
this.onBoardConfigChangedEmitter,
|
||||||
|
this.onFilterTextDidChangeEmitter
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(query: string): void {
|
||||||
|
this.onFilterTextDidChangeEmitter.fire(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fireConfigChanged = (config: BoardsConfig.Config) => {
|
protected fireConfigChanged = (config: BoardsConfig.Config) => {
|
||||||
@@ -44,12 +49,11 @@ export class BoardsConfigDialogWidget extends ReactWidget {
|
|||||||
protected render(): React.ReactNode {
|
protected render(): React.ReactNode {
|
||||||
return <div className='selectBoardContainer'>
|
return <div className='selectBoardContainer'>
|
||||||
<BoardsConfig
|
<BoardsConfig
|
||||||
boardsService={this.boardsService}
|
boardsServiceProvider={this.boardsServiceClient}
|
||||||
boardsServiceClient={this.boardsServiceClient}
|
notificationCenter={this.notificationCenter}
|
||||||
coreServiceClient={this.coreServiceClient}
|
|
||||||
daemonClient={this.daemonClient}
|
|
||||||
onConfigChange={this.fireConfigChanged}
|
onConfigChange={this.fireConfigChanged}
|
||||||
onFocusNodeSet={this.setFocusNode} />
|
onFocusNodeSet={this.setFocusNode}
|
||||||
|
onFilteredTextDidChangeEvent={this.onFilterTextDidChangeEmitter.event} />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { injectable, inject, postConstruct } from 'inversify';
|
import { injectable, inject, postConstruct } from 'inversify';
|
||||||
import { Message } from '@phosphor/messaging';
|
import { Message } from '@phosphor/messaging';
|
||||||
import { AbstractDialog, DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
|
import { AbstractDialog, DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
|
||||||
import { BoardsService } from '../../common/protocol/boards-service';
|
|
||||||
import { BoardsConfig } from './boards-config';
|
import { BoardsConfig } from './boards-config';
|
||||||
|
import { BoardsService } from '../../common/protocol/boards-service';
|
||||||
|
import { BoardsServiceProvider } from './boards-service-provider';
|
||||||
import { BoardsConfigDialogWidget } from './boards-config-dialog-widget';
|
import { BoardsConfigDialogWidget } from './boards-config-dialog-widget';
|
||||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsConfigDialogProps extends DialogProps {
|
export class BoardsConfigDialogProps extends DialogProps {
|
||||||
@@ -19,8 +19,8 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
|||||||
@inject(BoardsService)
|
@inject(BoardsService)
|
||||||
protected readonly boardService: BoardsService;
|
protected readonly boardService: BoardsService;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||||
|
|
||||||
protected config: BoardsConfig.Config = {};
|
protected config: BoardsConfig.Config = {};
|
||||||
|
|
||||||
@@ -42,6 +42,16 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass in an empty string if you want to reset the search term. Using `undefined` has no effect.
|
||||||
|
*/
|
||||||
|
async open(query: string | undefined = undefined): Promise<BoardsConfig.Config | undefined> {
|
||||||
|
if (typeof query === 'string') {
|
||||||
|
this.widget.search(query);
|
||||||
|
}
|
||||||
|
return super.open();
|
||||||
|
}
|
||||||
|
|
||||||
protected createDescription(): HTMLElement {
|
protected createDescription(): HTMLElement {
|
||||||
const head = document.createElement('div');
|
const head = document.createElement('div');
|
||||||
head.classList.add('head');
|
head.classList.add('head');
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { DisposableCollection } from '@theia/core';
|
import { Event } from '@theia/core/lib/common/event';
|
||||||
import { BoardsService, Board, Port, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
import { notEmpty } from '@theia/core/lib/common/objects';
|
||||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
import { CoreServiceClientImpl } from '../core-service-client-impl';
|
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
import { ArduinoDaemonClientImpl } from '../arduino-daemon-client-impl';
|
import { Board, Port, AttachedBoardsChangeEvent, BoardWithPackage } from '../../common/protocol/boards-service';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
import { BoardsServiceProvider } from './boards-service-provider';
|
||||||
|
|
||||||
export namespace BoardsConfig {
|
export namespace BoardsConfig {
|
||||||
|
|
||||||
@@ -13,16 +15,15 @@ export namespace BoardsConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly boardsService: BoardsService;
|
readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
readonly boardsServiceClient: BoardsServiceClientImpl;
|
readonly notificationCenter: NotificationCenter;
|
||||||
readonly coreServiceClient: CoreServiceClientImpl;
|
|
||||||
readonly daemonClient: ArduinoDaemonClientImpl;
|
|
||||||
readonly onConfigChange: (config: Config) => void;
|
readonly onConfigChange: (config: Config) => void;
|
||||||
readonly onFocusNodeSet: (element: HTMLElement | undefined) => void;
|
readonly onFocusNodeSet: (element: HTMLElement | undefined) => void;
|
||||||
|
readonly onFilteredTextDidChangeEvent: Event<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State extends Config {
|
export interface State extends Config {
|
||||||
searchResults: Array<Board & { packageName: string }>;
|
searchResults: Array<BoardWithPackage>;
|
||||||
knownPorts: Port[];
|
knownPorts: Port[];
|
||||||
showAllPorts: boolean;
|
showAllPorts: boolean;
|
||||||
query: string;
|
query: string;
|
||||||
@@ -70,7 +71,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
|||||||
constructor(props: BoardsConfig.Props) {
|
constructor(props: BoardsConfig.Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const { boardsConfig } = props.boardsServiceClient;
|
const { boardsConfig } = props.boardsServiceProvider;
|
||||||
this.state = {
|
this.state = {
|
||||||
searchResults: [],
|
searchResults: [],
|
||||||
knownPorts: [],
|
knownPorts: [],
|
||||||
@@ -82,18 +83,18 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.updateBoards();
|
this.updateBoards();
|
||||||
this.props.boardsService.getAvailablePorts().then(ports => this.updatePorts(ports));
|
this.updatePorts(this.props.boardsServiceProvider.availableBoards.map(({ port }) => port).filter(notEmpty));
|
||||||
const { boardsServiceClient, coreServiceClient, daemonClient } = this.props;
|
|
||||||
this.toDispose.pushAll([
|
this.toDispose.pushAll([
|
||||||
boardsServiceClient.onAttachedBoardsChanged(event => this.updatePorts(event.newState.ports, AttachedBoardsChangeEvent.diff(event).detached.ports)),
|
this.props.notificationCenter.onAttachedBoardsChanged(event => this.updatePorts(event.newState.ports, AttachedBoardsChangeEvent.diff(event).detached.ports)),
|
||||||
boardsServiceClient.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => {
|
this.props.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => {
|
||||||
this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged());
|
this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged());
|
||||||
}),
|
}),
|
||||||
boardsServiceClient.onBoardsPackageInstalled(() => this.updateBoards(this.state.query)),
|
this.props.notificationCenter.onPlatformInstalled(() => this.updateBoards(this.state.query)),
|
||||||
boardsServiceClient.onBoardsPackageUninstalled(() => this.updateBoards(this.state.query)),
|
this.props.notificationCenter.onPlatformUninstalled(() => this.updateBoards(this.state.query)),
|
||||||
coreServiceClient.onIndexUpdated(() => this.updateBoards(this.state.query)),
|
this.props.notificationCenter.onIndexUpdated(() => this.updateBoards(this.state.query)),
|
||||||
daemonClient.onDaemonStarted(() => this.updateBoards(this.state.query)),
|
this.props.notificationCenter.onDaemonStarted(() => this.updateBoards(this.state.query)),
|
||||||
daemonClient.onDaemonStopped(() => this.setState({ searchResults: [] }))
|
this.props.notificationCenter.onDaemonStopped(() => this.setState({ searchResults: [] })),
|
||||||
|
this.props.onFilteredTextDidChangeEvent(query => this.setState({ query }, () => this.updateBoards(this.state.query)))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,10 +108,9 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected updateBoards = (eventOrQuery: React.ChangeEvent<HTMLInputElement> | string = '') => {
|
protected updateBoards = (eventOrQuery: React.ChangeEvent<HTMLInputElement> | string = '') => {
|
||||||
const query = (typeof eventOrQuery === 'string'
|
const query = typeof eventOrQuery === 'string'
|
||||||
? eventOrQuery
|
? eventOrQuery
|
||||||
: eventOrQuery.target.value.toLowerCase()
|
: eventOrQuery.target.value.toLowerCase();
|
||||||
).trim();
|
|
||||||
this.setState({ query });
|
this.setState({ query });
|
||||||
this.queryBoards({ query }).then(searchResults => this.setState({ searchResults }));
|
this.queryBoards({ query }).then(searchResults => this.setState({ searchResults }));
|
||||||
}
|
}
|
||||||
@@ -126,15 +126,15 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected queryBoards = (options: { query?: string } = {}): Promise<Array<Board & { packageName: string }>> => {
|
protected queryBoards = (options: { query?: string } = {}): Promise<Array<BoardWithPackage>> => {
|
||||||
return this.props.boardsServiceClient.searchBoards(options);
|
return this.props.boardsServiceProvider.searchBoards(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get availablePorts(): Promise<Port[]> {
|
protected get availablePorts(): MaybePromise<Port[]> {
|
||||||
return this.props.boardsService.getAvailablePorts();
|
return this.props.boardsServiceProvider.availableBoards.map(({ port }) => port).filter(notEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected queryPorts = async (availablePorts: Promise<Port[]> = this.availablePorts) => {
|
protected queryPorts = async (availablePorts: MaybePromise<Port[]> = this.availablePorts) => {
|
||||||
const ports = await availablePorts;
|
const ports = await availablePorts;
|
||||||
return { knownPorts: ports.sort(Port.compare) };
|
return { knownPorts: ports.sort(Port.compare) };
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
|||||||
this.setState({ selectedPort }, () => this.fireConfigChanged());
|
this.setState({ selectedPort }, () => this.fireConfigChanged());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected selectBoard = (selectedBoard: Board & { packageName: string } | undefined) => {
|
protected selectBoard = (selectedBoard: BoardWithPackage | undefined) => {
|
||||||
this.setState({ selectedBoard }, () => this.fireConfigChanged());
|
this.setState({ selectedBoard }, () => this.fireConfigChanged());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected renderBoards(): React.ReactNode {
|
protected renderBoards(): React.ReactNode {
|
||||||
const { selectedBoard, searchResults } = this.state;
|
const { selectedBoard, searchResults, query } = this.state;
|
||||||
// Board names are not unique per core https://github.com/arduino/arduino-pro-ide/issues/262#issuecomment-661019560
|
// Board names are not unique per core https://github.com/arduino/arduino-pro-ide/issues/262#issuecomment-661019560
|
||||||
// It is tricky when the core is not yet installed, no FQBNs are available.
|
// It is tricky when the core is not yet installed, no FQBNs are available.
|
||||||
const distinctBoards = new Map<string, Board.Detailed>();
|
const distinctBoards = new Map<string, Board.Detailed>();
|
||||||
@@ -191,11 +191,18 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
|||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<div className='search'>
|
<div className='search'>
|
||||||
<input type='search' className='theia-input' placeholder='SEARCH BOARD' onChange={this.updateBoards} ref={this.focusNodeSet} />
|
<input
|
||||||
|
type='search'
|
||||||
|
value={query}
|
||||||
|
className='theia-input'
|
||||||
|
placeholder='SEARCH BOARD'
|
||||||
|
onChange={this.updateBoards}
|
||||||
|
ref={this.focusNodeSet}
|
||||||
|
/>
|
||||||
<i className='fa fa-search'></i>
|
<i className='fa fa-search'></i>
|
||||||
</div>
|
</div>
|
||||||
<div className='boards list'>
|
<div className='boards list'>
|
||||||
{Array.from(distinctBoards.values()).map(board => <Item<Board & { packageName: string }>
|
{Array.from(distinctBoards.values()).map(board => <Item<BoardWithPackage>
|
||||||
key={`${board.name}-${board.packageName}`}
|
key={`${board.name}-${board.packageName}`}
|
||||||
item={board}
|
item={board}
|
||||||
label={board.name}
|
label={board.name}
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import * as PQueue from 'p-queue';
|
import * as PQueue from 'p-queue';
|
||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||||
import { MenuModelRegistry, MenuNode } from '@theia/core/lib/common/menu';
|
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
import { BoardsServiceProvider } from './boards-service-provider';
|
||||||
import { Board, ConfigOption, Programmer } from '../../common/protocol';
|
import { Board, ConfigOption, Programmer } from '../../common/protocol';
|
||||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||||
import { BoardsDataStore } from './boards-data-store';
|
import { BoardsDataStore } from './boards-data-store';
|
||||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
||||||
@@ -25,8 +25,8 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
|||||||
@inject(BoardsDataStore)
|
@inject(BoardsDataStore)
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
protected readonly boardsDataStore: BoardsDataStore;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||||
|
|
||||||
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
|
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
|
||||||
protected readonly toDisposeOnBoardChange = new DisposableCollection();
|
protected readonly toDisposeOnBoardChange = new DisposableCollection();
|
||||||
@@ -63,7 +63,7 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
|||||||
this.menuRegistry.registerSubmenu(menuPath, label);
|
this.menuRegistry.registerSubmenu(menuPath, label);
|
||||||
this.toDisposeOnBoardChange.pushAll([
|
this.toDisposeOnBoardChange.pushAll([
|
||||||
...commands.values(),
|
...commands.values(),
|
||||||
Disposable.create(() => this.unregisterSubmenu(menuPath)), // We cannot dispose submenu entries: https://github.com/eclipse-theia/theia/issues/7299
|
Disposable.create(() => unregisterSubmenu(menuPath, this.menuRegistry)),
|
||||||
...Array.from(commands.keys()).map((commandId, i) => {
|
...Array.from(commands.keys()).map((commandId, i) => {
|
||||||
const { label } = commands.get(commandId)!;
|
const { label } = commands.get(commandId)!;
|
||||||
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: `${i}`, label });
|
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: `${i}`, label });
|
||||||
@@ -76,7 +76,7 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
|||||||
const programmersMenuPath = [...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, 'z02_programmers'];
|
const programmersMenuPath = [...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, 'z02_programmers'];
|
||||||
const label = selectedProgrammer ? `Programmer: "${selectedProgrammer.name}"` : 'Programmer'
|
const label = selectedProgrammer ? `Programmer: "${selectedProgrammer.name}"` : 'Programmer'
|
||||||
this.menuRegistry.registerSubmenu(programmersMenuPath, label);
|
this.menuRegistry.registerSubmenu(programmersMenuPath, label);
|
||||||
this.toDisposeOnBoardChange.push(Disposable.create(() => this.unregisterSubmenu(programmersMenuPath)));
|
this.toDisposeOnBoardChange.push(Disposable.create(() => unregisterSubmenu(programmersMenuPath, this.menuRegistry)));
|
||||||
for (const programmer of programmers) {
|
for (const programmer of programmers) {
|
||||||
const { id, name } = programmer;
|
const { id, name } = programmer;
|
||||||
const command = { id: `${fqbn}-programmer--${id}` };
|
const command = { id: `${fqbn}-programmer--${id}` };
|
||||||
@@ -98,20 +98,4 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected unregisterSubmenu(menuPath: string[]): void {
|
|
||||||
if (menuPath.length < 2) {
|
|
||||||
throw new Error(`Expected at least two item as a menu-path. Got ${JSON.stringify(menuPath)} instead.`);
|
|
||||||
}
|
|
||||||
const toRemove = menuPath[menuPath.length - 1];
|
|
||||||
const parentMenuPath = menuPath.slice(0, menuPath.length - 1);
|
|
||||||
// This is unsafe. Calling `getMenu` with a non-existing menu-path will result in a new menu creation.
|
|
||||||
// https://github.com/eclipse-theia/theia/issues/7300
|
|
||||||
const parent = this.menuRegistry.getMenu(parentMenuPath);
|
|
||||||
const index = parent.children.findIndex(({ id }) => id === toRemove);
|
|
||||||
if (index === -1) {
|
|
||||||
throw new Error(`Could not find menu with menu-path: ${JSON.stringify(menuPath)}.`);
|
|
||||||
}
|
|
||||||
(parent.children as Array<MenuNode>).splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -5,8 +5,8 @@ import { MaybePromise } from '@theia/core/lib/common/types';
|
|||||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||||
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
||||||
import { notEmpty } from '../../common/utils';
|
import { notEmpty } from '../../common/utils';
|
||||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
|
||||||
import { BoardsService, ConfigOption, Installable, BoardDetails, Programmer } from '../../common/protocol';
|
import { BoardsService, ConfigOption, Installable, BoardDetails, Programmer } from '../../common/protocol';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsDataStore implements FrontendApplicationContribution {
|
export class BoardsDataStore implements FrontendApplicationContribution {
|
||||||
@@ -18,8 +18,8 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
|||||||
@inject(BoardsService)
|
@inject(BoardsService)
|
||||||
protected readonly boardsService: BoardsService;
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(NotificationCenter)
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
@inject(LocalStorageService)
|
@inject(LocalStorageService)
|
||||||
protected readonly storageService: LocalStorageService;
|
protected readonly storageService: LocalStorageService;
|
||||||
@@ -27,13 +27,13 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
|||||||
protected readonly onChangedEmitter = new Emitter<void>();
|
protected readonly onChangedEmitter = new Emitter<void>();
|
||||||
|
|
||||||
onStart(): void {
|
onStart(): void {
|
||||||
this.boardsServiceClient.onBoardsPackageInstalled(async ({ pkg }) => {
|
this.notificationCenter.onPlatformInstalled(async ({ item }) => {
|
||||||
const { installedVersion: version } = pkg;
|
const { installedVersion: version } = item;
|
||||||
if (!version) {
|
if (!version) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let shouldFireChanged = false;
|
let shouldFireChanged = false;
|
||||||
for (const fqbn of pkg.boards.map(({ fqbn }) => fqbn).filter(notEmpty).filter(fqbn => !!fqbn)) {
|
for (const fqbn of item.boards.map(({ fqbn }) => fqbn).filter(notEmpty).filter(fqbn => !!fqbn)) {
|
||||||
const key = this.getStorageKey(fqbn, version);
|
const key = this.getStorageKey(fqbn, version);
|
||||||
let data = await this.storageService.getData<ConfigOption[] | undefined>(key);
|
let data = await this.storageService.getData<ConfigOption[] | undefined>(key);
|
||||||
if (!data || !data.length) {
|
if (!data || !data.length) {
|
||||||
@@ -58,27 +58,33 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async appendConfigToFqbn(
|
async appendConfigToFqbn(
|
||||||
fqbn: string,
|
fqbn: string | undefined,
|
||||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<string> {
|
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<string | undefined> {
|
||||||
|
|
||||||
|
if (!fqbn) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const { configOptions } = await this.getData(fqbn, boardsPackageVersion);
|
const { configOptions } = await this.getData(fqbn, boardsPackageVersion);
|
||||||
return ConfigOption.decorate(fqbn, configOptions);
|
return ConfigOption.decorate(fqbn, configOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(
|
async getData(
|
||||||
fqbn: string,
|
fqbn: string | undefined,
|
||||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<BoardsDataStore.Data> {
|
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<BoardsDataStore.Data> {
|
||||||
|
|
||||||
|
if (!fqbn) {
|
||||||
|
return BoardsDataStore.Data.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
const version = await boardsPackageVersion;
|
const version = await boardsPackageVersion;
|
||||||
if (!version) {
|
if (!version) {
|
||||||
return BoardsDataStore.Data.EMPTY;
|
return BoardsDataStore.Data.EMPTY;
|
||||||
}
|
}
|
||||||
const key = this.getStorageKey(fqbn, version);
|
const key = this.getStorageKey(fqbn, version);
|
||||||
let data = await this.storageService.getData<BoardsDataStore.Data | undefined>(key, undefined);
|
let data = await this.storageService.getData<BoardsDataStore.Data | undefined>(key, undefined);
|
||||||
if (data) {
|
if (BoardsDataStore.Data.is(data)) {
|
||||||
if (data.programmers !== undefined) { // to be backward compatible. We did not save the `programmers` into the `localStorage`.
|
return data;
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const boardDetails = await this.getBoardDetailsSafe(fqbn);
|
const boardDetails = await this.getBoardDetailsSafe(fqbn);
|
||||||
@@ -151,7 +157,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected getStorageKey(fqbn: string, version: Installable.Version): string {
|
protected getStorageKey(fqbn: string, version: Installable.Version): string {
|
||||||
return `.arduinoProIDE-configOptions-${version}-${fqbn}`;
|
return `.arduinoIDE-configOptions-${version}-${fqbn}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getBoardDetailsSafe(fqbn: string): Promise<BoardDetails | undefined> {
|
protected async getBoardDetailsSafe(fqbn: string): Promise<BoardDetails | undefined> {
|
||||||
@@ -172,7 +178,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
|||||||
this.onChangedEmitter.fire();
|
this.onChangedEmitter.fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getBoardsPackageVersion(fqbn: string): Promise<Installable.Version | undefined> {
|
protected async getBoardsPackageVersion(fqbn: string | undefined): Promise<Installable.Version | undefined> {
|
||||||
if (!fqbn) {
|
if (!fqbn) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -196,5 +202,10 @@ export namespace BoardsDataStore {
|
|||||||
configOptions: [],
|
configOptions: [],
|
||||||
programmers: []
|
programmers: []
|
||||||
};
|
};
|
||||||
|
export function is(arg: any): arg is Data {
|
||||||
|
return !!arg
|
||||||
|
&& 'configOptions' in arg && Array.isArray(arg['configOptions'])
|
||||||
|
&& 'programmers' in arg && Array.isArray(arg['programmers'])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable, postConstruct } from 'inversify';
|
||||||
import { BoardsPackage, BoardsService } from '../../common/protocol/boards-service';
|
import { BoardsPackage, BoardsService } from '../../common/protocol/boards-service';
|
||||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||||
@@ -24,4 +24,13 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
super.init();
|
||||||
|
this.toDispose.pushAll([
|
||||||
|
this.notificationCenter.onPlatformInstalled(() => this.refresh(undefined)),
|
||||||
|
this.notificationCenter.onPlatformUninstalled(() => this.refresh(undefined)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,36 +1,51 @@
|
|||||||
import { injectable, inject, optional } from 'inversify';
|
import { injectable, inject } from 'inversify';
|
||||||
import { Emitter } from '@theia/core/lib/common/event';
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
import { ILogger } from '@theia/core/lib/common/logger';
|
import { ILogger } from '@theia/core/lib/common/logger';
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { StorageService } from '@theia/core/lib/browser/storage-service';
|
import { StorageService } from '@theia/core/lib/browser/storage-service';
|
||||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||||
import { RecursiveRequired } from '../../common/types';
|
import { RecursiveRequired } from '../../common/types';
|
||||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, Board, Port, BoardUninstalledEvent, BoardsService } from '../../common/protocol';
|
import {
|
||||||
|
Port,
|
||||||
|
Board,
|
||||||
|
BoardsService,
|
||||||
|
BoardsPackage,
|
||||||
|
AttachedBoardsChangeEvent,
|
||||||
|
BoardWithPackage
|
||||||
|
} from '../../common/protocol';
|
||||||
import { BoardsConfig } from './boards-config';
|
import { BoardsConfig } from './boards-config';
|
||||||
import { naturalCompare } from '../../common/utils';
|
import { naturalCompare } from '../../common/utils';
|
||||||
import { compareAnything } from '../theia/monaco/comparers';
|
import { compareAnything } from '../theia/monaco/comparers';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
import { CommandService } from '@theia/core';
|
||||||
|
import { ArduinoCommands } from '../arduino-commands';
|
||||||
|
|
||||||
interface BoardMatch {
|
interface BoardMatch {
|
||||||
readonly board: Board & Readonly<{ packageName: string }>;
|
readonly board: BoardWithPackage;
|
||||||
readonly matches: monaco.filters.IMatch[] | undefined;
|
readonly matches: monaco.filters.IMatch[] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApplicationContribution {
|
export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||||
|
|
||||||
@inject(ILogger)
|
@inject(ILogger)
|
||||||
protected logger: ILogger;
|
protected logger: ILogger;
|
||||||
|
|
||||||
@optional()
|
|
||||||
@inject(MessageService)
|
@inject(MessageService)
|
||||||
protected messageService: MessageService;
|
protected messageService: MessageService;
|
||||||
|
|
||||||
@inject(StorageService)
|
@inject(StorageService)
|
||||||
protected storageService: StorageService;
|
protected storageService: StorageService;
|
||||||
|
|
||||||
protected readonly onBoardsPackageInstalledEmitter = new Emitter<BoardInstalledEvent>();
|
@inject(BoardsService)
|
||||||
protected readonly onBoardsPackageUninstalledEmitter = new Emitter<BoardUninstalledEvent>();
|
protected boardsService: BoardsService;
|
||||||
protected readonly onAttachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
|
|
||||||
|
@inject(CommandService)
|
||||||
|
protected commandService: CommandService;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
protected readonly onBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
protected readonly onBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
||||||
protected readonly onAvailableBoardsChangedEmitter = new Emitter<AvailableBoard[]>();
|
protected readonly onAvailableBoardsChangedEmitter = new Emitter<AvailableBoard[]>();
|
||||||
|
|
||||||
@@ -42,18 +57,12 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
* See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ
|
* See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ
|
||||||
*/
|
*/
|
||||||
protected latestValidBoardsConfig: RecursiveRequired<BoardsConfig.Config> | undefined = undefined;
|
protected latestValidBoardsConfig: RecursiveRequired<BoardsConfig.Config> | undefined = undefined;
|
||||||
|
protected latestBoardsConfig: BoardsConfig.Config | undefined = undefined;
|
||||||
protected _boardsConfig: BoardsConfig.Config = {};
|
protected _boardsConfig: BoardsConfig.Config = {};
|
||||||
protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only.
|
protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only.
|
||||||
protected _availablePorts: Port[] = [];
|
protected _availablePorts: Port[] = [];
|
||||||
protected _availableBoards: AvailableBoard[] = [];
|
protected _availableBoards: AvailableBoard[] = [];
|
||||||
protected boardsService: BoardsService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when the state of the attached/detached boards has changed. For instance, the user have detached a physical board.
|
|
||||||
*/
|
|
||||||
readonly onAttachedBoardsChanged = this.onAttachedBoardsChangedEmitter.event;
|
|
||||||
readonly onBoardsPackageInstalled = this.onBoardsPackageInstalledEmitter.event;
|
|
||||||
readonly onBoardsPackageUninstalled = this.onBoardsPackageUninstalledEmitter.event;
|
|
||||||
/**
|
/**
|
||||||
* Unlike `onAttachedBoardsChanged` this even fires when the user modifies the selected board in the IDE.\
|
* Unlike `onAttachedBoardsChanged` this even fires when the user modifies the selected board in the IDE.\
|
||||||
* This even also fires, when the boards package was not available for the currently selected board,
|
* This even also fires, when the boards package was not available for the currently selected board,
|
||||||
@@ -64,33 +73,85 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
|
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
|
||||||
readonly onAvailableBoardsChanged = this.onAvailableBoardsChangedEmitter.event;
|
readonly onAvailableBoardsChanged = this.onAvailableBoardsChangedEmitter.event;
|
||||||
|
|
||||||
async onStart(): Promise<void> {
|
onStart(): void {
|
||||||
return this.loadState();
|
this.notificationCenter.onAttachedBoardsChanged(this.notifyAttachedBoardsChanged.bind(this));
|
||||||
}
|
this.notificationCenter.onPlatformInstalled(this.notifyPlatformInstalled.bind(this));
|
||||||
|
this.notificationCenter.onPlatformUninstalled(this.notifyPlatformUninstalled.bind(this));
|
||||||
|
|
||||||
/**
|
Promise.all([
|
||||||
* When the FE connects to the BE, the BE stets the known boards and ports.\
|
|
||||||
* This is a DI workaround for not being able to inject the service into the client.
|
|
||||||
*/
|
|
||||||
async init(boardsService: BoardsService): Promise<void> {
|
|
||||||
this.boardsService = boardsService;
|
|
||||||
const [attachedBoards, availablePorts] = await Promise.all([
|
|
||||||
this.boardsService.getAttachedBoards(),
|
this.boardsService.getAttachedBoards(),
|
||||||
this.boardsService.getAvailablePorts()
|
this.boardsService.getAvailablePorts(),
|
||||||
]);
|
this.loadState()
|
||||||
this._attachedBoards = attachedBoards;
|
]).then(([attachedBoards, availablePorts]) => {
|
||||||
this._availablePorts = availablePorts;
|
this._attachedBoards = attachedBoards;
|
||||||
this.reconcileAvailableBoards().then(() => this.tryReconnect());
|
this._availablePorts = availablePorts;
|
||||||
|
this.reconcileAvailableBoards().then(() => this.tryReconnect());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
|
protected notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
|
||||||
this.logger.info('Attached boards and available ports changed: ', JSON.stringify(event));
|
if (!AttachedBoardsChangeEvent.isEmpty(event)) {
|
||||||
|
this.logger.info('Attached boards and available ports changed:');
|
||||||
|
this.logger.info(AttachedBoardsChangeEvent.toString(event));
|
||||||
|
this.logger.info(`------------------------------------------`);
|
||||||
|
}
|
||||||
this._attachedBoards = event.newState.boards;
|
this._attachedBoards = event.newState.boards;
|
||||||
this.onAttachedBoardsChangedEmitter.fire(event);
|
|
||||||
this._availablePorts = event.newState.ports;
|
this._availablePorts = event.newState.ports;
|
||||||
this.reconcileAvailableBoards().then(() => this.tryReconnect());
|
this.reconcileAvailableBoards().then(() => this.tryReconnect());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected notifyPlatformInstalled(event: { item: BoardsPackage }): void {
|
||||||
|
this.logger.info('Boards package installed: ', JSON.stringify(event));
|
||||||
|
const { selectedBoard } = this.boardsConfig;
|
||||||
|
const { installedVersion, id } = event.item;
|
||||||
|
if (selectedBoard) {
|
||||||
|
const installedBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
|
||||||
|
if (installedBoard && (!selectedBoard.fqbn || selectedBoard.fqbn === installedBoard.fqbn)) {
|
||||||
|
this.logger.info(`Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]`);
|
||||||
|
this.boardsConfig = {
|
||||||
|
...this.boardsConfig,
|
||||||
|
selectedBoard: installedBoard
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The board name can change after install.
|
||||||
|
// This logic handles it "gracefully" by unselecting the board, so that we can avoid no FQBN is set error.
|
||||||
|
// https://github.com/arduino/arduino-cli/issues/620
|
||||||
|
// https://github.com/arduino/arduino-pro-ide/issues/374
|
||||||
|
if (BoardWithPackage.is(selectedBoard) && selectedBoard.packageId === event.item.id && !installedBoard) {
|
||||||
|
this.messageService.warn(`Could not find previously selected board '${selectedBoard.name}' in installed platform '${event.item.name}'. Please manually reselect the board you want to use. Do you want to reselect it now?`, 'Reselect later', 'Yes').then(async answer => {
|
||||||
|
if (answer === 'Yes') {
|
||||||
|
this.commandService.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id, selectedBoard.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.boardsConfig = {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Trigger a board re-set. See: https://github.com/arduino/arduino-cli/issues/954
|
||||||
|
// E.g: install `adafruit:avr`, then select `adafruit:avr:adafruit32u4` board, and finally install the required `arduino:avr`
|
||||||
|
this.boardsConfig = this.boardsConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected notifyPlatformUninstalled(event: { item: BoardsPackage }): void {
|
||||||
|
this.logger.info('Boards package uninstalled: ', JSON.stringify(event));
|
||||||
|
const { selectedBoard } = this.boardsConfig;
|
||||||
|
if (selectedBoard && selectedBoard.fqbn) {
|
||||||
|
const uninstalledBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
|
||||||
|
if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) {
|
||||||
|
this.logger.info(`Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`);
|
||||||
|
const selectedBoardWithoutFqbn = {
|
||||||
|
name: selectedBoard.name
|
||||||
|
// No FQBN
|
||||||
|
};
|
||||||
|
this.boardsConfig = {
|
||||||
|
...this.boardsConfig,
|
||||||
|
selectedBoard: selectedBoardWithoutFqbn
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected async tryReconnect(): Promise<boolean> {
|
protected async tryReconnect(): Promise<boolean> {
|
||||||
if (this.latestValidBoardsConfig && !this.canUploadTo(this.boardsConfig)) {
|
if (this.latestValidBoardsConfig && !this.canUploadTo(this.boardsConfig)) {
|
||||||
for (const board of this.availableBoards.filter(({ state }) => state !== AvailableBoard.State.incomplete)) {
|
for (const board of this.availableBoards.filter(({ state }) => state !== AvailableBoard.State.incomplete)) {
|
||||||
@@ -119,43 +180,6 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyBoardInstalled(event: BoardInstalledEvent): void {
|
|
||||||
this.logger.info('Board installed: ', JSON.stringify(event));
|
|
||||||
this.onBoardsPackageInstalledEmitter.fire(event);
|
|
||||||
const { selectedBoard } = this.boardsConfig;
|
|
||||||
const { installedVersion, id } = event.pkg;
|
|
||||||
if (selectedBoard) {
|
|
||||||
const installedBoard = event.pkg.boards.find(({ name }) => name === selectedBoard.name);
|
|
||||||
if (installedBoard && (!selectedBoard.fqbn || selectedBoard.fqbn === installedBoard.fqbn)) {
|
|
||||||
this.logger.info(`Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]`);
|
|
||||||
this.boardsConfig = {
|
|
||||||
...this.boardsConfig,
|
|
||||||
selectedBoard: installedBoard
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyBoardUninstalled(event: BoardUninstalledEvent): void {
|
|
||||||
this.logger.info('Board uninstalled: ', JSON.stringify(event));
|
|
||||||
this.onBoardsPackageUninstalledEmitter.fire(event);
|
|
||||||
const { selectedBoard } = this.boardsConfig;
|
|
||||||
if (selectedBoard && selectedBoard.fqbn) {
|
|
||||||
const uninstalledBoard = event.pkg.boards.find(({ name }) => name === selectedBoard.name);
|
|
||||||
if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) {
|
|
||||||
this.logger.info(`Board package ${event.pkg.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`);
|
|
||||||
const selectedBoardWithoutFqbn = {
|
|
||||||
name: selectedBoard.name
|
|
||||||
// No FQBN
|
|
||||||
};
|
|
||||||
this.boardsConfig = {
|
|
||||||
...this.boardsConfig,
|
|
||||||
selectedBoard: selectedBoardWithoutFqbn
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set boardsConfig(config: BoardsConfig.Config) {
|
set boardsConfig(config: BoardsConfig.Config) {
|
||||||
this.doSetBoardsConfig(config);
|
this.doSetBoardsConfig(config);
|
||||||
this.saveState().finally(() => this.reconcileAvailableBoards().finally(() => this.onBoardsConfigChangedEmitter.fire(this._boardsConfig)));
|
this.saveState().finally(() => this.reconcileAvailableBoards().finally(() => this.onBoardsConfigChangedEmitter.fire(this._boardsConfig)));
|
||||||
@@ -164,20 +188,21 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
protected doSetBoardsConfig(config: BoardsConfig.Config): void {
|
protected doSetBoardsConfig(config: BoardsConfig.Config): void {
|
||||||
this.logger.info('Board config changed: ', JSON.stringify(config));
|
this.logger.info('Board config changed: ', JSON.stringify(config));
|
||||||
this._boardsConfig = config;
|
this._boardsConfig = config;
|
||||||
|
this.latestBoardsConfig = this._boardsConfig;
|
||||||
if (this.canUploadTo(this._boardsConfig)) {
|
if (this.canUploadTo(this._boardsConfig)) {
|
||||||
this.latestValidBoardsConfig = this._boardsConfig;
|
this.latestValidBoardsConfig = this._boardsConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchBoards({ query, cores }: { query?: string, cores?: string[] }): Promise<Array<Board & { packageName: string }>> {
|
async searchBoards({ query, cores }: { query?: string, cores?: string[] }): Promise<Array<BoardWithPackage>> {
|
||||||
const boards = await this.boardsService.allBoards({});
|
const boards = await this.boardsService.allBoards({});
|
||||||
const coresFilter = !!cores && cores.length
|
const coresFilter = !!cores && cores.length
|
||||||
? ((toFilter: { packageName: string }) => cores.some(core => core === toFilter.packageName))
|
? ((toFilter: BoardWithPackage) => cores.some(core => core === toFilter.packageName || core === toFilter.packageId))
|
||||||
: () => true;
|
: () => true;
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return boards.filter(coresFilter).sort(Board.compare);
|
return boards.filter(coresFilter).sort(Board.compare);
|
||||||
}
|
}
|
||||||
const toMatch = ((toFilter: Board & { packageName: string }) => (({ board: toFilter, matches: monaco.filters.matchesFuzzy(query, toFilter.name, true) })));
|
const toMatch = ((toFilter: BoardWithPackage) => (({ board: toFilter, matches: monaco.filters.matchesFuzzy(query, toFilter.name, true) })));
|
||||||
const compareEntries = (left: BoardMatch, right: BoardMatch, lookFor: string) => {
|
const compareEntries = (left: BoardMatch, right: BoardMatch, lookFor: string) => {
|
||||||
const leftMatches = left.matches || [];
|
const leftMatches = left.matches || [];
|
||||||
const rightMatches = right.matches || [];
|
const rightMatches = right.matches || [];
|
||||||
@@ -219,7 +244,7 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!config.selectedBoard) {
|
if (!config.selectedBoard) {
|
||||||
if (!options.silent && this.messageService) {
|
if (!options.silent) {
|
||||||
this.messageService.warn('No boards selected.', { timeout: 3000 });
|
this.messageService.warn('No boards selected.', { timeout: 3000 });
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -241,14 +266,14 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
|
|
||||||
const { name } = config.selectedBoard;
|
const { name } = config.selectedBoard;
|
||||||
if (!config.selectedPort) {
|
if (!config.selectedPort) {
|
||||||
if (!options.silent && this.messageService) {
|
if (!options.silent) {
|
||||||
this.messageService.warn(`No ports selected for board: '${name}'.`, { timeout: 3000 });
|
this.messageService.warn(`No ports selected for board: '${name}'.`, { timeout: 3000 });
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.selectedBoard.fqbn) {
|
if (!config.selectedBoard.fqbn) {
|
||||||
if (!options.silent && this.messageService) {
|
if (!options.silent) {
|
||||||
this.messageService.warn(`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, { timeout: 3000 });
|
this.messageService.warn(`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, { timeout: 3000 });
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -261,6 +286,29 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
return this._availableBoards;
|
return this._availableBoards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async waitUntilAvailable(what: Board & { port: Port }, timeout?: number): Promise<void> {
|
||||||
|
const find = (needle: Board & { port: Port }, haystack: AvailableBoard[]) =>
|
||||||
|
haystack.find(board => Board.equals(needle, board) && Port.equals(needle.port, board.port));
|
||||||
|
const timeoutTask = !!timeout && timeout > 0
|
||||||
|
? new Promise<void>((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${timeout} ms.`)), timeout))
|
||||||
|
: new Promise<void>(() => { /* never */ });
|
||||||
|
const waitUntilTask = new Promise<void>(resolve => {
|
||||||
|
let candidate = find(what, this.availableBoards);
|
||||||
|
if (candidate) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const disposable = this.onAvailableBoardsChanged(availableBoards => {
|
||||||
|
candidate = find(what, availableBoards);
|
||||||
|
if (candidate) {
|
||||||
|
disposable.dispose();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return await Promise.race([waitUntilTask, timeoutTask]);
|
||||||
|
}
|
||||||
|
|
||||||
protected async reconcileAvailableBoards(): Promise<void> {
|
protected async reconcileAvailableBoards(): Promise<void> {
|
||||||
const attachedBoards = this._attachedBoards;
|
const attachedBoards = this._attachedBoards;
|
||||||
const availablePorts = this._availablePorts;
|
const availablePorts = this._availablePorts;
|
||||||
@@ -338,7 +386,10 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
const key = this.getLastSelectedBoardOnPortKey(selectedPort);
|
const key = this.getLastSelectedBoardOnPortKey(selectedPort);
|
||||||
await this.storageService.setData(key, selectedBoard);
|
await this.storageService.setData(key, selectedBoard);
|
||||||
}
|
}
|
||||||
await this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig);
|
await Promise.all([
|
||||||
|
this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig),
|
||||||
|
this.storageService.setData('latest-boards-config', this.latestBoardsConfig)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getLastSelectedBoardOnPortKey(port: Port | string): string {
|
protected getLastSelectedBoardOnPortKey(port: Port | string): string {
|
||||||
@@ -347,15 +398,21 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async loadState(): Promise<void> {
|
protected async loadState(): Promise<void> {
|
||||||
const storedValidBoardsConfig = await this.storageService.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
|
const storedLatestValidBoardsConfig = await this.storageService.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
|
||||||
if (storedValidBoardsConfig) {
|
if (storedLatestValidBoardsConfig) {
|
||||||
this.latestValidBoardsConfig = storedValidBoardsConfig;
|
this.latestValidBoardsConfig = storedLatestValidBoardsConfig;
|
||||||
if (this.canUploadTo(this.latestValidBoardsConfig)) {
|
if (this.canUploadTo(this.latestValidBoardsConfig)) {
|
||||||
this.boardsConfig = this.latestValidBoardsConfig;
|
this.boardsConfig = this.latestValidBoardsConfig;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If we could not restore the latest valid config, try to restore something, the board at least.
|
||||||
|
const storedLatestBoardsConfig = await this.storageService.getData<BoardsConfig.Config | undefined>('latest-boards-config');
|
||||||
|
if (storedLatestBoardsConfig) {
|
||||||
|
this.latestBoardsConfig = storedLatestBoardsConfig;
|
||||||
|
this.boardsConfig = this.latestBoardsConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@@ -5,7 +5,7 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
|||||||
import { Port } from '../../common/protocol';
|
import { Port } from '../../common/protocol';
|
||||||
import { BoardsConfig } from './boards-config';
|
import { BoardsConfig } from './boards-config';
|
||||||
import { ArduinoCommands } from '../arduino-commands';
|
import { ArduinoCommands } from '../arduino-commands';
|
||||||
import { BoardsServiceClientImpl, AvailableBoard } from './boards-service-client-impl';
|
import { BoardsServiceProvider, AvailableBoard } from './boards-service-provider';
|
||||||
|
|
||||||
export interface BoardsDropDownListCoords {
|
export interface BoardsDropDownListCoords {
|
||||||
readonly top: number;
|
readonly top: number;
|
||||||
@@ -181,7 +181,7 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
|||||||
export namespace BoardsToolBarItem {
|
export namespace BoardsToolBarItem {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly boardsServiceClient: BoardsServiceClientImpl;
|
readonly boardsServiceClient: BoardsServiceProvider;
|
||||||
readonly commands: CommandRegistry;
|
readonly commands: CommandRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,15 +1,11 @@
|
|||||||
import { injectable } from 'inversify';
|
import { injectable } from 'inversify';
|
||||||
import { MenuModelRegistry } from '@theia/core';
|
|
||||||
import { BoardsListWidget } from './boards-list-widget';
|
import { BoardsListWidget } from './boards-list-widget';
|
||||||
import { BoardsPackage } from '../../common/protocol/boards-service';
|
import { BoardsPackage } from '../../common/protocol/boards-service';
|
||||||
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
|
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
|
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
|
||||||
|
|
||||||
static readonly OPEN_MANAGER = `${BoardsListWidget.WIDGET_ID}:toggle`;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
widgetId: BoardsListWidget.WIDGET_ID,
|
widgetId: BoardsListWidget.WIDGET_ID,
|
||||||
@@ -18,7 +14,7 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
|
|||||||
area: 'left',
|
area: 'left',
|
||||||
rank: 600
|
rank: 600
|
||||||
},
|
},
|
||||||
toggleCommandId: BoardsListWidgetFrontendContribution.OPEN_MANAGER,
|
toggleCommandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
|
||||||
toggleKeybinding: 'CtrlCmd+Shift+B'
|
toggleKeybinding: 'CtrlCmd+Shift+B'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -27,14 +23,4 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
|
|||||||
this.openView();
|
this.openView();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMenus(menus: MenuModelRegistry): void {
|
|
||||||
if (this.toggleCommand) {
|
|
||||||
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
|
||||||
commandId: this.toggleCommand.id,
|
|
||||||
label: 'Boards Manager...',
|
|
||||||
order: '4'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -16,9 +16,9 @@ import {
|
|||||||
} from '@theia/core/lib/browser/quick-open';
|
} from '@theia/core/lib/browser/quick-open';
|
||||||
import { naturalCompare } from '../../../common/utils';
|
import { naturalCompare } from '../../../common/utils';
|
||||||
import { BoardsService, Port, Board, ConfigOption, ConfigValue } from '../../../common/protocol';
|
import { BoardsService, Port, Board, ConfigOption, ConfigValue } from '../../../common/protocol';
|
||||||
import { CoreServiceClientImpl } from '../../core-service-client-impl';
|
|
||||||
import { BoardsDataStore } from '../boards-data-store';
|
import { BoardsDataStore } from '../boards-data-store';
|
||||||
import { BoardsServiceClientImpl, AvailableBoard } from '../boards-service-client-impl';
|
import { BoardsServiceProvider, AvailableBoard } from '../boards-service-provider';
|
||||||
|
import { NotificationCenter } from '../../notification-center';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenModel, QuickOpenHandler, CommandContribution, KeybindingContribution, Command {
|
export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenModel, QuickOpenHandler, CommandContribution, KeybindingContribution, Command {
|
||||||
@@ -38,14 +38,14 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
|
|||||||
@inject(BoardsService)
|
@inject(BoardsService)
|
||||||
protected readonly boardsService: BoardsService;
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||||
|
|
||||||
@inject(BoardsDataStore)
|
@inject(BoardsDataStore)
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
protected readonly boardsDataStore: BoardsDataStore;
|
||||||
|
|
||||||
@inject(CoreServiceClientImpl)
|
@inject(NotificationCenter)
|
||||||
protected coreServiceClient: CoreServiceClientImpl;
|
protected notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
protected isOpen: boolean = false;
|
protected isOpen: boolean = false;
|
||||||
protected currentQuery: string = '';
|
protected currentQuery: string = '';
|
||||||
@@ -59,7 +59,7 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
|
|||||||
// `init` name is used by the `QuickOpenHandler`.
|
// `init` name is used by the `QuickOpenHandler`.
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected postConstruct(): void {
|
protected postConstruct(): void {
|
||||||
this.coreServiceClient.onIndexUpdated(() => this.update(this.availableBoards));
|
this.notificationCenter.onIndexUpdated(() => this.update(this.availableBoards));
|
||||||
this.boardsServiceClient.onAvailableBoardsChanged(availableBoards => this.update(availableBoards));
|
this.boardsServiceClient.onAvailableBoardsChanged(availableBoards => this.update(availableBoards));
|
||||||
this.update(this.boardsServiceClient.availableBoards);
|
this.update(this.boardsServiceClient.availableBoards);
|
||||||
}
|
}
|
||||||
|
@@ -1,52 +0,0 @@
|
|||||||
import { injectable, inject } from 'inversify';
|
|
||||||
import { ILogger } from '@theia/core/lib/common/logger';
|
|
||||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
|
||||||
import { CommandService } from '@theia/core/lib/common/command';
|
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
|
||||||
import { ConfigServiceClient, Config } from '../common/protocol';
|
|
||||||
import { Settings } from './contributions/settings';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ConfigServiceClientImpl implements ConfigServiceClient {
|
|
||||||
|
|
||||||
@inject(CommandService)
|
|
||||||
protected readonly commandService: CommandService;
|
|
||||||
|
|
||||||
@inject(ILogger)
|
|
||||||
protected readonly logger: ILogger;
|
|
||||||
|
|
||||||
@inject(MessageService)
|
|
||||||
protected readonly messageService: MessageService;
|
|
||||||
|
|
||||||
protected readonly onConfigChangedEmitter = new Emitter<Config>();
|
|
||||||
protected invalidConfigPopup: Promise<void | 'No' | 'Yes' | undefined> | undefined;
|
|
||||||
|
|
||||||
notifyConfigChanged(config: Config): void {
|
|
||||||
this.invalidConfigPopup = undefined;
|
|
||||||
this.info(`The CLI configuration has been successfully reloaded.`);
|
|
||||||
this.onConfigChangedEmitter.fire(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyInvalidConfig(): void {
|
|
||||||
if (!this.invalidConfigPopup) {
|
|
||||||
this.invalidConfigPopup = this.messageService.error(`Your CLI configuration is invalid. Do you want to correct it now?`, 'No', 'Yes')
|
|
||||||
.then(answer => {
|
|
||||||
if (answer === 'Yes') {
|
|
||||||
this.commandService.executeCommand(Settings.Commands.OPEN_CLI_CONFIG.id)
|
|
||||||
}
|
|
||||||
this.invalidConfigPopup = undefined;
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get onConfigChanged(): Event<Config> {
|
|
||||||
return this.onConfigChangedEmitter.event;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected info(message: string): void {
|
|
||||||
this.messageService.info(message, { timeout: 3000 });
|
|
||||||
this.logger.info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
105
arduino-ide-extension/src/browser/contributions/about.ts
Normal file
105
arduino-ide-extension/src/browser/contributions/about.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { remote } from 'electron';
|
||||||
|
import { isOSX, isWindows } from '@theia/core/lib/common/os';
|
||||||
|
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||||
|
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
|
||||||
|
import { Contribution, Command, MenuModelRegistry, CommandRegistry } from './contribution';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { ConfigService } from '../../common/protocol';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class About extends Contribution {
|
||||||
|
|
||||||
|
@inject(ClipboardService)
|
||||||
|
protected readonly clipboardService: ClipboardService;
|
||||||
|
|
||||||
|
@inject(ConfigService)
|
||||||
|
protected readonly configService: ConfigService;
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(About.Commands.ABOUT_APP, {
|
||||||
|
execute: () => this.showAbout()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, {
|
||||||
|
commandId: About.Commands.ABOUT_APP.id,
|
||||||
|
label: `About ${this.applicationName}`,
|
||||||
|
order: '0'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async showAbout(): Promise<void> {
|
||||||
|
const { version, commit, status: cliStatus } = await this.configService.getVersion();
|
||||||
|
const buildDate = this.buildDate;
|
||||||
|
const detail = (showAll: boolean) => `Version: ${remote.app.getVersion()}
|
||||||
|
Date: ${buildDate ? buildDate : 'dev build'}${buildDate && showAll ? ` (${this.ago(buildDate)})` : ''}
|
||||||
|
CLI Version: ${version}${cliStatus ? ` ${cliStatus}` : ''} [${commit}]
|
||||||
|
|
||||||
|
${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''}
|
||||||
|
`;
|
||||||
|
const ok = 'OK';
|
||||||
|
const copy = 'Copy';
|
||||||
|
const buttons = !isWindows && !isOSX ? [copy, ok] : [ok, copy];
|
||||||
|
const { response } = await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
message: `${this.applicationName}`,
|
||||||
|
title: `${this.applicationName}`,
|
||||||
|
type: 'info',
|
||||||
|
detail: detail(true),
|
||||||
|
buttons,
|
||||||
|
noLink: true,
|
||||||
|
defaultId: buttons.indexOf(ok),
|
||||||
|
cancelId: buttons.indexOf(ok)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (buttons[response] === copy) {
|
||||||
|
await this.clipboardService.writeText(detail(false).trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get applicationName(): string {
|
||||||
|
return FrontendApplicationConfigProvider.get().applicationName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get buildDate(): string | undefined {
|
||||||
|
return FrontendApplicationConfigProvider.get().buildDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ago(isoTime: string): string {
|
||||||
|
const now = moment(Date.now());
|
||||||
|
const other = moment(isoTime);
|
||||||
|
let result = now.diff(other, 'minute');
|
||||||
|
if (result < 60) {
|
||||||
|
return result === 1 ? `${result} minute ago` : `${result} minute ago`;
|
||||||
|
}
|
||||||
|
result = now.diff(other, 'hour');
|
||||||
|
if (result < 25) {
|
||||||
|
return result === 1 ? `${result} hour ago` : `${result} hours ago`;
|
||||||
|
}
|
||||||
|
result = now.diff(other, 'day');
|
||||||
|
if (result < 8) {
|
||||||
|
return result === 1 ? `${result} day ago` : `${result} days ago`;
|
||||||
|
}
|
||||||
|
result = now.diff(other, 'week');
|
||||||
|
if (result < 5) {
|
||||||
|
return result === 1 ? `${result} week ago` : `${result} weeks ago`;
|
||||||
|
}
|
||||||
|
result = now.diff(other, 'month');
|
||||||
|
if (result < 13) {
|
||||||
|
return result === 1 ? `${result} month ago` : `${result} months ago`;
|
||||||
|
}
|
||||||
|
result = now.diff(other, 'year');
|
||||||
|
return result === 1 ? `${result} year ago` : `${result} years ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace About {
|
||||||
|
export namespace Commands {
|
||||||
|
export const ABOUT_APP: Command = {
|
||||||
|
id: 'arduino-about'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
68
arduino-ide-extension/src/browser/contributions/add-file.ts
Normal file
68
arduino-ide-extension/src/browser/contributions/add-file.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { remote } from 'electron';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, URI } from './contribution';
|
||||||
|
import { FileDialogService } from '@theia/filesystem/lib/browser';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class AddFile extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(FileDialogService)
|
||||||
|
protected readonly fileDialogService: FileDialogService;
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(AddFile.Commands.ADD_FILE, {
|
||||||
|
execute: () => this.addFile()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
|
||||||
|
commandId: AddFile.Commands.ADD_FILE.id,
|
||||||
|
label: 'Add File...',
|
||||||
|
order: '2'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async addFile(): Promise<void> {
|
||||||
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (!sketch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const toAddUri = await this.fileDialogService.showOpenDialog({
|
||||||
|
title: 'Add File',
|
||||||
|
canSelectFiles: true,
|
||||||
|
canSelectFolders: false,
|
||||||
|
canSelectMany: false
|
||||||
|
});
|
||||||
|
if (!toAddUri) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sketchUri = new URI(sketch.uri);
|
||||||
|
const filename = toAddUri.path.base;
|
||||||
|
const targetUri = sketchUri.resolve('data').resolve(filename);
|
||||||
|
const exists = await this.fileService.exists(targetUri);
|
||||||
|
if (exists) {
|
||||||
|
const { response } = await remote.dialog.showMessageBox({
|
||||||
|
type: 'question',
|
||||||
|
title: 'Replace',
|
||||||
|
buttons: ['Cancel', 'OK'],
|
||||||
|
message: `Replace the existing version of ${filename}?`
|
||||||
|
});
|
||||||
|
if (response === 0) { // Cancel
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.fileService.copy(toAddUri, targetUri, { overwrite: true });
|
||||||
|
this.messageService.info('One file added to the sketch.', { timeout: 2000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace AddFile {
|
||||||
|
export namespace Commands {
|
||||||
|
export const ADD_FILE: Command = {
|
||||||
|
id: 'arduino-add-file'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
import { injectable } from 'inversify';
|
||||||
|
import { remote } from 'electron';
|
||||||
|
import * as dateFormat from 'dateformat';
|
||||||
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArchiveSketch extends SketchContribution {
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, {
|
||||||
|
execute: () => this.archiveSketch()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||||
|
commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id,
|
||||||
|
label: 'Archive Sketch',
|
||||||
|
order: '1'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async archiveSketch(): Promise<void> {
|
||||||
|
const [sketch, config] = await Promise.all([
|
||||||
|
this.sketchServiceClient.currentSketch(),
|
||||||
|
this.configService.getConfiguration()
|
||||||
|
]);
|
||||||
|
if (!sketch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const archiveBasename = `${sketch.name}-${dateFormat(new Date(), 'yymmdd')}a.zip`;
|
||||||
|
const defaultPath = await this.fileService.fsPath(new URI(config.sketchDirUri).resolve(archiveBasename));
|
||||||
|
const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath });
|
||||||
|
if (!filePath || canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const destinationUri = await this.fileSystemExt.getUri(filePath);
|
||||||
|
if (!destinationUri) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.sketchService.archive(sketch, destinationUri.toString());
|
||||||
|
this.messageService.info(`Created archive '${archiveBasename}'.`, { timeout: 2000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ArchiveSketch {
|
||||||
|
export namespace Commands {
|
||||||
|
export const ARCHIVE_SKETCH: Command = {
|
||||||
|
id: 'arduino-archive-sketch'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,222 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { remote } from 'electron';
|
||||||
|
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||||
|
import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable';
|
||||||
|
import { firstToUpperCase } from '../../common/utils';
|
||||||
|
import { BoardsConfig } from '../boards/boards-config';
|
||||||
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||||
|
import { BoardsListWidget } from '../boards/boards-list-widget';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
import { ArduinoMenus, PlaceholderMenuNode, unregisterSubmenu } from '../menu/arduino-menus';
|
||||||
|
import { BoardsService, InstalledBoardWithPackage, AvailablePorts, Port } from '../../common/protocol';
|
||||||
|
import { SketchContribution, Command, CommandRegistry } from './contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BoardSelection extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(CommandRegistry)
|
||||||
|
protected readonly commandRegistry: CommandRegistry;
|
||||||
|
|
||||||
|
@inject(MainMenuManager)
|
||||||
|
protected readonly mainMenuManager: MainMenuManager;
|
||||||
|
|
||||||
|
@inject(MenuModelRegistry)
|
||||||
|
protected readonly menuModelRegistry: MenuModelRegistry;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
@inject(BoardsService)
|
||||||
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
|
protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection();
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, {
|
||||||
|
execute: async () => {
|
||||||
|
const { selectedBoard, selectedPort } = this.boardsServiceProvider.boardsConfig;
|
||||||
|
if (!selectedBoard) {
|
||||||
|
this.messageService.info('Please select a board to obtain board info.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!selectedBoard.fqbn) {
|
||||||
|
this.messageService.info(`The platform for the selected '${selectedBoard.name}' board is not installed.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!selectedPort) {
|
||||||
|
this.messageService.info('Please select a port to obtain board info.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const boardDetails = await this.boardsService.getBoardDetails({ fqbn: selectedBoard.fqbn });
|
||||||
|
if (boardDetails) {
|
||||||
|
const { VID, PID } = boardDetails;
|
||||||
|
const detail = `BN: ${selectedBoard.name}
|
||||||
|
VID: ${VID}
|
||||||
|
PID: ${PID}`;
|
||||||
|
await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
message: 'Board Info',
|
||||||
|
title: 'Board Info',
|
||||||
|
type: 'info',
|
||||||
|
detail,
|
||||||
|
buttons: ['OK']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.updateMenus();
|
||||||
|
this.notificationCenter.onPlatformInstalled(this.updateMenus.bind(this));
|
||||||
|
this.notificationCenter.onPlatformUninstalled(this.updateMenus.bind(this));
|
||||||
|
this.boardsServiceProvider.onBoardsConfigChanged(this.updateMenus.bind(this));
|
||||||
|
this.boardsServiceProvider.onAvailableBoardsChanged(this.updateMenus.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async updateMenus(): Promise<void> {
|
||||||
|
const [installedBoards, availablePorts, config] = await Promise.all([
|
||||||
|
this.installedBoards(),
|
||||||
|
this.boardsService.getState(),
|
||||||
|
this.boardsServiceProvider.boardsConfig
|
||||||
|
]);
|
||||||
|
this.rebuildMenus(installedBoards, availablePorts, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected rebuildMenus(installedBoards: InstalledBoardWithPackage[], availablePorts: AvailablePorts, config: BoardsConfig.Config): void {
|
||||||
|
this.toDisposeBeforeMenuRebuild.dispose();
|
||||||
|
|
||||||
|
// Boards submenu
|
||||||
|
const boardsSubmenuPath = [...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, '1_boards'];
|
||||||
|
const boardsSubmenuLabel = config.selectedBoard?.name;
|
||||||
|
// Note: The submenu order starts from `100` because `Auto Format`, `Serial Monitor`, etc starts from `0` index.
|
||||||
|
// The board specific items, and the rest, have order with `z`. We needed something between `0` and `z` with natural-order.
|
||||||
|
this.menuModelRegistry.registerSubmenu(boardsSubmenuPath, `Board${!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''}`, { order: '100' });
|
||||||
|
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => unregisterSubmenu(boardsSubmenuPath, this.menuModelRegistry)));
|
||||||
|
|
||||||
|
// Ports submenu
|
||||||
|
const portsSubmenuPath = [...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, '2_ports'];
|
||||||
|
const portsSubmenuLabel = config.selectedPort?.address;
|
||||||
|
this.menuModelRegistry.registerSubmenu(portsSubmenuPath, `Port${!!portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''}`, { order: '101' });
|
||||||
|
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => unregisterSubmenu(portsSubmenuPath, this.menuModelRegistry)));
|
||||||
|
|
||||||
|
const getBoardInfo = { commandId: BoardSelection.Commands.GET_BOARD_INFO.id, label: 'Get Board Info', order: '103' };
|
||||||
|
this.menuModelRegistry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, getBoardInfo);
|
||||||
|
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.menuModelRegistry.unregisterMenuAction(getBoardInfo)));
|
||||||
|
|
||||||
|
const boardsManagerGroup = [...boardsSubmenuPath, '0_manager'];
|
||||||
|
const boardsPackagesGroup = [...boardsSubmenuPath, '1_packages'];
|
||||||
|
|
||||||
|
this.menuModelRegistry.registerMenuAction(boardsManagerGroup, {
|
||||||
|
commandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
|
||||||
|
label: 'Boards Manager...'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Installed boards
|
||||||
|
for (const board of installedBoards) {
|
||||||
|
const { packageId, packageName, fqbn, name } = board;
|
||||||
|
|
||||||
|
// Platform submenu
|
||||||
|
const platformMenuPath = [...boardsPackagesGroup, packageId];
|
||||||
|
// Note: Registering the same submenu twice is a noop. No need to group the boards per platform.
|
||||||
|
this.menuModelRegistry.registerSubmenu(platformMenuPath, packageName);
|
||||||
|
|
||||||
|
const id = `arduino-select-board--${fqbn}`;
|
||||||
|
const command = { id };
|
||||||
|
const handler = {
|
||||||
|
execute: () => {
|
||||||
|
if (fqbn !== this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn) {
|
||||||
|
this.boardsServiceProvider.boardsConfig = {
|
||||||
|
selectedBoard: {
|
||||||
|
name,
|
||||||
|
fqbn,
|
||||||
|
port: this.boardsServiceProvider.boardsConfig.selectedBoard?.port // TODO: verify!
|
||||||
|
},
|
||||||
|
selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isToggled: () => fqbn === this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn
|
||||||
|
};
|
||||||
|
|
||||||
|
// Board menu
|
||||||
|
const menuAction = { commandId: id, label: name };
|
||||||
|
this.commandRegistry.registerCommand(command, handler);
|
||||||
|
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.commandRegistry.unregisterCommand(command)));
|
||||||
|
this.menuModelRegistry.registerMenuAction(platformMenuPath, menuAction);
|
||||||
|
// Note: we do not dispose the menu actions individually. Calling `unregisterSubmenu` on the parent will wipe the children menu nodes recursively.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installed ports
|
||||||
|
const registerPorts = (ports: AvailablePorts) => {
|
||||||
|
const addresses = Object.keys(ports);
|
||||||
|
if (!addresses.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register placeholder for protocol
|
||||||
|
const [port] = ports[addresses[0]];
|
||||||
|
const protocol = port.protocol;
|
||||||
|
const menuPath = [...portsSubmenuPath, protocol];
|
||||||
|
const placeholder = new PlaceholderMenuNode(menuPath, `${firstToUpperCase(port.protocol)} ports`);
|
||||||
|
this.menuModelRegistry.registerMenuNode(menuPath, placeholder);
|
||||||
|
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.menuModelRegistry.unregisterMenuNode(placeholder.id)));
|
||||||
|
|
||||||
|
for (const address of addresses) {
|
||||||
|
if (!!ports[address]) {
|
||||||
|
const [port, boards] = ports[address];
|
||||||
|
if (!boards.length) {
|
||||||
|
boards.push({
|
||||||
|
name: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const { name, fqbn } of boards) {
|
||||||
|
const id = `arduino-select-port--${address}${fqbn ? `--${fqbn}` : ''}`;
|
||||||
|
const command = { id };
|
||||||
|
const handler = {
|
||||||
|
execute: () => {
|
||||||
|
if (!Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)) {
|
||||||
|
this.boardsServiceProvider.boardsConfig = {
|
||||||
|
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
|
||||||
|
selectedPort: port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isToggled: () => Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)
|
||||||
|
};
|
||||||
|
const label = `${address}${name ? ` (${name})` : ''}`;
|
||||||
|
const menuAction = {
|
||||||
|
commandId: id,
|
||||||
|
label,
|
||||||
|
order: `1${label}` // `1` comes after the placeholder which has order `0`
|
||||||
|
};
|
||||||
|
this.commandRegistry.registerCommand(command, handler);
|
||||||
|
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.commandRegistry.unregisterCommand(command)));
|
||||||
|
this.menuModelRegistry.registerMenuAction(menuPath, menuAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { serial, network, unknown } = AvailablePorts.groupByProtocol(availablePorts);
|
||||||
|
registerPorts(serial);
|
||||||
|
registerPorts(network);
|
||||||
|
registerPorts(unknown);
|
||||||
|
|
||||||
|
this.mainMenuManager.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async installedBoards(): Promise<InstalledBoardWithPackage[]> {
|
||||||
|
const allBoards = await this.boardsService.allBoards({});
|
||||||
|
return allBoards.filter(InstalledBoardWithPackage.is);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export namespace BoardSelection {
|
||||||
|
export namespace Commands {
|
||||||
|
export const GET_BOARD_INFO: Command = { id: 'arduino-get-board-info' };
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,82 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||||
|
import { CoreService } from '../../common/protocol';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||||
|
import { MonitorConnection } from '../monitor/monitor-connection';
|
||||||
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BurnBootloader extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(CoreService)
|
||||||
|
protected readonly coreService: CoreService;
|
||||||
|
|
||||||
|
@inject(MonitorConnection)
|
||||||
|
protected readonly monitorConnection: MonitorConnection;
|
||||||
|
|
||||||
|
@inject(BoardsDataStore)
|
||||||
|
protected readonly boardsDataStore: BoardsDataStore;
|
||||||
|
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
||||||
|
|
||||||
|
@inject(OutputChannelManager)
|
||||||
|
protected readonly outputChannelManager: OutputChannelManager;
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, {
|
||||||
|
execute: () => this.burnBootloader()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, {
|
||||||
|
commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id,
|
||||||
|
label: 'Burn Bootloader',
|
||||||
|
order: 'z99'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async burnBootloader(): Promise<void> {
|
||||||
|
const monitorConfig = this.monitorConnection.monitorConfig;
|
||||||
|
if (monitorConfig) {
|
||||||
|
await this.monitorConnection.disconnect();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||||
|
const port = boardsConfig.selectedPort?.address;
|
||||||
|
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] = await Promise.all([
|
||||||
|
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||||
|
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||||
|
this.preferences.get('arduino.upload.verify'),
|
||||||
|
this.preferences.get('arduino.upload.verbose')
|
||||||
|
]);
|
||||||
|
this.outputChannelManager.getChannel('Arduino').clear();
|
||||||
|
await this.coreService.burnBootloader({
|
||||||
|
fqbn,
|
||||||
|
programmer,
|
||||||
|
port,
|
||||||
|
verify,
|
||||||
|
verbose
|
||||||
|
});
|
||||||
|
this.messageService.info('Done burning bootloader.', { timeout: 1000 });
|
||||||
|
} catch (e) {
|
||||||
|
this.messageService.error(e.toString());
|
||||||
|
} finally {
|
||||||
|
if (monitorConfig) {
|
||||||
|
await this.monitorConnection.connect(monitorConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace BurnBootloader {
|
||||||
|
export namespace Commands {
|
||||||
|
export const BURN_BOOTLOADER: Command = {
|
||||||
|
id: 'arduino-burn-bootloader'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -1,20 +1,50 @@
|
|||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { toArray } from '@phosphor/algorithm';
|
||||||
import { remote } from 'electron';
|
import { remote } from 'electron';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
|
||||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, URI } from './contribution';
|
|
||||||
import { SaveAsSketch } from './save-as-sketch';
|
|
||||||
import { EditorManager } from '@theia/editor/lib/browser';
|
|
||||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||||
|
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||||
|
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
||||||
|
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { SaveAsSketch } from './save-as-sketch';
|
||||||
|
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, URI } from './contribution';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window.
|
||||||
|
*/
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CloseSketch extends SketchContribution {
|
export class Close extends SketchContribution {
|
||||||
|
|
||||||
@inject(EditorManager)
|
@inject(EditorManager)
|
||||||
protected readonly editorManager: EditorManager;
|
protected readonly editorManager: EditorManager;
|
||||||
|
|
||||||
|
protected shell: ApplicationShell;
|
||||||
|
|
||||||
|
onStart(app: FrontendApplication): void {
|
||||||
|
this.shell = app.shell;
|
||||||
|
}
|
||||||
|
|
||||||
registerCommands(registry: CommandRegistry): void {
|
registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(CloseSketch.Commands.CLOSE_SKETCH, {
|
registry.registerCommand(Close.Commands.CLOSE, {
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
|
|
||||||
|
// Close current editor if closeable.
|
||||||
|
const { currentEditor } = this.editorManager;
|
||||||
|
if (currentEditor && currentEditor.title.closable) {
|
||||||
|
currentEditor.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close current widget from the main area if possible.
|
||||||
|
const { currentWidget } = this.shell;
|
||||||
|
if (currentWidget) {
|
||||||
|
const currentWidgetInMain = toArray(this.shell.mainPanel.widgets()).find(widget => widget === currentWidget);
|
||||||
|
if (currentWidgetInMain && currentWidgetInMain.title.closable) {
|
||||||
|
return currentWidgetInMain.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the sketch (window).
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
if (!sketch) {
|
if (!sketch) {
|
||||||
return;
|
return;
|
||||||
@@ -48,7 +78,7 @@ export class CloseSketch extends SketchContribution {
|
|||||||
|
|
||||||
registerMenus(registry: MenuModelRegistry): void {
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||||
commandId: CloseSketch.Commands.CLOSE_SKETCH.id,
|
commandId: Close.Commands.CLOSE.id,
|
||||||
label: 'Close',
|
label: 'Close',
|
||||||
order: '5'
|
order: '5'
|
||||||
});
|
});
|
||||||
@@ -56,7 +86,7 @@ export class CloseSketch extends SketchContribution {
|
|||||||
|
|
||||||
registerKeybindings(registry: KeybindingRegistry): void {
|
registerKeybindings(registry: KeybindingRegistry): void {
|
||||||
registry.registerKeybinding({
|
registry.registerKeybinding({
|
||||||
command: CloseSketch.Commands.CLOSE_SKETCH.id,
|
command: Close.Commands.CLOSE.id,
|
||||||
keybinding: 'CtrlCmd+W'
|
keybinding: 'CtrlCmd+W'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -80,10 +110,10 @@ export class CloseSketch extends SketchContribution {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace CloseSketch {
|
export namespace Close {
|
||||||
export namespace Commands {
|
export namespace Commands {
|
||||||
export const CLOSE_SKETCH: Command = {
|
export const CLOSE: Command = {
|
||||||
id: 'arduino-close-sketch'
|
id: 'arduino-close'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,23 +1,29 @@
|
|||||||
import { inject, injectable, interfaces } from 'inversify';
|
import { inject, injectable, interfaces } from 'inversify';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { ILogger } from '@theia/core/lib/common/logger';
|
import { ILogger } from '@theia/core/lib/common/logger';
|
||||||
import { FileSystem } from '@theia/filesystem/lib/common';
|
import { Saveable } from '@theia/core/lib/browser/saveable';
|
||||||
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||||
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||||
|
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||||
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
|
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
|
||||||
import { MenuModelRegistry, MenuContribution } from '@theia/core/lib/common/menu';
|
import { MenuModelRegistry, MenuContribution } from '@theia/core/lib/common/menu';
|
||||||
import { KeybindingRegistry, KeybindingContribution } from '@theia/core/lib/browser/keybinding';
|
import { KeybindingRegistry, KeybindingContribution } from '@theia/core/lib/browser/keybinding';
|
||||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||||
|
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
import { Command, CommandRegistry, CommandContribution, CommandService } from '@theia/core/lib/common/command';
|
import { Command, CommandRegistry, CommandContribution, CommandService } from '@theia/core/lib/common/command';
|
||||||
import { EditorMode } from '../editor-mode';
|
import { EditorMode } from '../editor-mode';
|
||||||
|
import { SettingsService } from '../settings';
|
||||||
import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl';
|
import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl';
|
||||||
import { SketchesService, ConfigService, FileSystemExt, Sketch } from '../../common/protocol';
|
import { SketchesService, ConfigService, FileSystemExt, Sketch } from '../../common/protocol';
|
||||||
|
import { ArduinoPreferences } from '../arduino-preferences';
|
||||||
|
|
||||||
export { Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry, URI, Sketch, open };
|
export { Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry, URI, Sketch, open };
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export abstract class Contribution implements CommandContribution, MenuContribution, KeybindingContribution, TabBarToolbarContribution {
|
export abstract class Contribution implements CommandContribution, MenuContribution, KeybindingContribution, TabBarToolbarContribution, FrontendApplicationContribution {
|
||||||
|
|
||||||
@inject(ILogger)
|
@inject(ILogger)
|
||||||
protected readonly logger: ILogger;
|
protected readonly logger: ILogger;
|
||||||
@@ -37,6 +43,12 @@ export abstract class Contribution implements CommandContribution, MenuContribut
|
|||||||
@inject(LabelProvider)
|
@inject(LabelProvider)
|
||||||
protected readonly labelProvider: LabelProvider;
|
protected readonly labelProvider: LabelProvider;
|
||||||
|
|
||||||
|
@inject(SettingsService)
|
||||||
|
protected readonly settingsService: SettingsService;
|
||||||
|
|
||||||
|
onStart(app: FrontendApplication): MaybePromise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
registerCommands(registry: CommandRegistry): void {
|
registerCommands(registry: CommandRegistry): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +66,8 @@ export abstract class Contribution implements CommandContribution, MenuContribut
|
|||||||
@injectable()
|
@injectable()
|
||||||
export abstract class SketchContribution extends Contribution {
|
export abstract class SketchContribution extends Contribution {
|
||||||
|
|
||||||
@inject(FileSystem)
|
@inject(FileService)
|
||||||
protected readonly fileSystem: FileSystem;
|
protected readonly fileService: FileService;
|
||||||
|
|
||||||
@inject(FileSystemExt)
|
@inject(FileSystemExt)
|
||||||
protected readonly fileSystemExt: FileSystemExt;
|
protected readonly fileSystemExt: FileSystemExt;
|
||||||
@@ -72,14 +84,35 @@ export abstract class SketchContribution extends Contribution {
|
|||||||
@inject(SketchesServiceClientImpl)
|
@inject(SketchesServiceClientImpl)
|
||||||
protected readonly sketchServiceClient: SketchesServiceClientImpl;
|
protected readonly sketchServiceClient: SketchesServiceClientImpl;
|
||||||
|
|
||||||
|
@inject(ArduinoPreferences)
|
||||||
|
protected readonly preferences: ArduinoPreferences;
|
||||||
|
|
||||||
|
@inject(EditorManager)
|
||||||
|
protected readonly editorManager: EditorManager;
|
||||||
|
|
||||||
|
protected async sourceOverride(): Promise<Record<string, string>> {
|
||||||
|
const override: Record<string, string> = {};
|
||||||
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (sketch) {
|
||||||
|
for (const editor of this.editorManager.all) {
|
||||||
|
const uri = editor.editor.uri;
|
||||||
|
if (Saveable.isDirty(editor) && Sketch.isInSketch(uri, sketch)) {
|
||||||
|
override[uri.toString()] = editor.editor.document.getText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return override;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Contribution {
|
export namespace Contribution {
|
||||||
export function configure<T>(bind: interfaces.Bind, serviceIdentifier: interfaces.ServiceIdentifier<T>): void {
|
export function configure<T>(bind: interfaces.Bind, serviceIdentifier: typeof Contribution): void {
|
||||||
bind(serviceIdentifier).toSelf().inSingletonScope();
|
bind(serviceIdentifier).toSelf().inSingletonScope();
|
||||||
bind(CommandContribution).toService(serviceIdentifier);
|
bind(CommandContribution).toService(serviceIdentifier);
|
||||||
bind(MenuContribution).toService(serviceIdentifier);
|
bind(MenuContribution).toService(serviceIdentifier);
|
||||||
bind(KeybindingContribution).toService(serviceIdentifier);
|
bind(KeybindingContribution).toService(serviceIdentifier);
|
||||||
bind(TabBarToolbarContribution).toService(serviceIdentifier);
|
bind(TabBarToolbarContribution).toService(serviceIdentifier);
|
||||||
|
bind(FrontendApplicationContribution).toService(serviceIdentifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
134
arduino-ide-extension/src/browser/contributions/debug.ts
Normal file
134
arduino-ide-extension/src/browser/contributions/debug.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import { inject, injectable } from '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 { Board, BoardsService, ExecutableService } from '../../common/protocol';
|
||||||
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
import { URI, Command, CommandRegistry, SketchContribution, TabBarToolbarRegistry } from './contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class Debug extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(HostedPluginSupport)
|
||||||
|
protected hostedPluginSupport: HostedPluginSupport;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
@inject(ExecutableService)
|
||||||
|
protected readonly executableService: ExecutableService;
|
||||||
|
|
||||||
|
@inject(BoardsService)
|
||||||
|
protected readonly boardService: BoardsService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `undefined`, debugging is enabled. Otherwise, the reason why it's disabled.
|
||||||
|
*/
|
||||||
|
protected _disabledMessages?: string = 'No board selected'; // Initial pessimism.
|
||||||
|
protected disabledMessageDidChangeEmitter = new Emitter<string | undefined>();
|
||||||
|
protected onDisabledMessageDidChange = this.disabledMessageDidChangeEmitter.event;
|
||||||
|
|
||||||
|
protected get disabledMessage(): string | undefined {
|
||||||
|
return this._disabledMessages;
|
||||||
|
}
|
||||||
|
protected set disabledMessage(message: string | undefined) {
|
||||||
|
this._disabledMessages = message;
|
||||||
|
this.disabledMessageDidChangeEmitter.fire(this._disabledMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly debugToolbarItem = {
|
||||||
|
id: Debug.Commands.START_DEBUGGING.id,
|
||||||
|
command: Debug.Commands.START_DEBUGGING.id,
|
||||||
|
tooltip: `${this.disabledMessage ? `Debug - ${this.disabledMessage}` : 'Start Debugging'}`,
|
||||||
|
priority: 3,
|
||||||
|
onDidChange: this.onDisabledMessageDidChange as Event<void>
|
||||||
|
};
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.onDisabledMessageDidChange(() => this.debugToolbarItem.tooltip = `${this.disabledMessage ? `Debug - ${this.disabledMessage}` : 'Start Debugging'}`);
|
||||||
|
const refreshState = async (board: Board | undefined = this.boardsServiceProvider.boardsConfig.selectedBoard) => {
|
||||||
|
if (!board) {
|
||||||
|
this.disabledMessage = 'No board selected';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fqbn = board.fqbn;
|
||||||
|
if (!fqbn) {
|
||||||
|
this.disabledMessage = `Platform is not installed for '${board.name}'`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const details = await this.boardService.getBoardDetails({ fqbn });
|
||||||
|
if (!details) {
|
||||||
|
this.disabledMessage = `Platform is not installed for '${board.name}'`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { debuggingSupported } = details;
|
||||||
|
if (!debuggingSupported) {
|
||||||
|
this.disabledMessage = `Debugging is not supported by '${board.name}'`;
|
||||||
|
} else {
|
||||||
|
this.disabledMessage = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => refreshState(selectedBoard));
|
||||||
|
this.notificationCenter.onPlatformInstalled(() => refreshState());
|
||||||
|
this.notificationCenter.onPlatformUninstalled(() => refreshState());
|
||||||
|
refreshState();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(Debug.Commands.START_DEBUGGING, {
|
||||||
|
execute: () => this.startDebug(),
|
||||||
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
|
isEnabled: () => !this.disabledMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||||
|
registry.registerItem(this.debugToolbarItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async startDebug(board: Board | undefined = this.boardsServiceProvider.boardsConfig.selectedBoard): Promise<void> {
|
||||||
|
if (!board) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { name, fqbn } = board;
|
||||||
|
if (!fqbn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.hostedPluginSupport.didStart;
|
||||||
|
const [sketch, executables] = await Promise.all([
|
||||||
|
this.sketchServiceClient.currentSketch(),
|
||||||
|
this.executableService.list()
|
||||||
|
]);
|
||||||
|
if (!sketch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [cliPath, sketchPath] = await Promise.all([
|
||||||
|
this.fileService.fsPath(new URI(executables.cliUri)),
|
||||||
|
this.fileService.fsPath(new URI(sketch.uri))
|
||||||
|
])
|
||||||
|
const config = {
|
||||||
|
cliPath,
|
||||||
|
board: {
|
||||||
|
fqbn,
|
||||||
|
name
|
||||||
|
},
|
||||||
|
sketchPath
|
||||||
|
};
|
||||||
|
return this.commandService.executeCommand('arduino.debug.start', config);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Debug {
|
||||||
|
export namespace Commands {
|
||||||
|
export const START_DEBUGGING: Command = {
|
||||||
|
id: 'arduino-start-debug',
|
||||||
|
label: 'Start Debugging',
|
||||||
|
category: 'Arduino'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,7 +3,6 @@ import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribu
|
|||||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||||
import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service';
|
import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service';
|
||||||
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
|
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
|
||||||
import { EDITOR_FONT_DEFAULTS } from '@theia/editor/lib/browser/editor-preferences';
|
|
||||||
import { Contribution, Command, MenuModelRegistry, KeybindingRegistry, CommandRegistry } from './contribution';
|
import { Contribution, Command, MenuModelRegistry, KeybindingRegistry, CommandRegistry } from './contribution';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
|
||||||
@@ -31,10 +30,28 @@ export class EditContributions extends Contribution {
|
|||||||
registry.registerCommand(EditContributions.Commands.FIND_PREVIOUS, { execute: () => this.run('editor.action.nextMatchFindAction') });
|
registry.registerCommand(EditContributions.Commands.FIND_PREVIOUS, { execute: () => this.run('editor.action.nextMatchFindAction') });
|
||||||
registry.registerCommand(EditContributions.Commands.USE_FOR_FIND, { execute: () => this.run('editor.action.previousSelectionMatchFindAction') });
|
registry.registerCommand(EditContributions.Commands.USE_FOR_FIND, { execute: () => this.run('editor.action.previousSelectionMatchFindAction') });
|
||||||
registry.registerCommand(EditContributions.Commands.INCREASE_FONT_SIZE, {
|
registry.registerCommand(EditContributions.Commands.INCREASE_FONT_SIZE, {
|
||||||
execute: () => this.preferences.set('editor.fontSize', this.preferences.get('editor.fontSize', EDITOR_FONT_DEFAULTS.fontSize) + 1)
|
execute: async () => {
|
||||||
|
const settings = await this.settingsService.settings();
|
||||||
|
if (settings.autoScaleInterface) {
|
||||||
|
settings.interfaceScale = settings.interfaceScale + 1;
|
||||||
|
} else {
|
||||||
|
settings.editorFontSize = settings.editorFontSize + 1;
|
||||||
|
}
|
||||||
|
await this.settingsService.update(settings);
|
||||||
|
await this.settingsService.save();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
registry.registerCommand(EditContributions.Commands.DECREASE_FONT_SIZE, {
|
registry.registerCommand(EditContributions.Commands.DECREASE_FONT_SIZE, {
|
||||||
execute: () => this.preferences.set('editor.fontSize', this.preferences.get('editor.fontSize', EDITOR_FONT_DEFAULTS.fontSize) - 1)
|
execute: async () => {
|
||||||
|
const settings = await this.settingsService.settings();
|
||||||
|
if (settings.autoScaleInterface) {
|
||||||
|
settings.interfaceScale = settings.interfaceScale - 1;
|
||||||
|
} else {
|
||||||
|
settings.editorFontSize = settings.editorFontSize - 1;
|
||||||
|
}
|
||||||
|
await this.settingsService.update(settings);
|
||||||
|
await this.settingsService.save();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
/* Tools */registry.registerCommand(EditContributions.Commands.AUTO_FORMAT, { execute: () => this.run('editor.action.formatDocument') });
|
/* Tools */registry.registerCommand(EditContributions.Commands.AUTO_FORMAT, { execute: () => this.run('editor.action.formatDocument') });
|
||||||
registry.registerCommand(EditContributions.Commands.COPY_FOR_FORUM, {
|
registry.registerCommand(EditContributions.Commands.COPY_FOR_FORUM, {
|
||||||
|
169
arduino-ide-extension/src/browser/contributions/examples.ts
Normal file
169
arduino-ide-extension/src/browser/contributions/examples.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import * as PQueue from 'p-queue';
|
||||||
|
import { inject, injectable, postConstruct } from 'inversify';
|
||||||
|
import { MenuPath, CompositeMenuNode } from '@theia/core/lib/common/menu';
|
||||||
|
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { OpenSketch } from './open-sketch';
|
||||||
|
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||||
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||||
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
import { ExamplesService, ExampleContainer } from '../../common/protocol/examples-service';
|
||||||
|
import { SketchContribution, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
import { Board } from '../../common/protocol';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export abstract class Examples extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(CommandRegistry)
|
||||||
|
protected readonly commandRegistry: CommandRegistry;
|
||||||
|
|
||||||
|
@inject(MenuModelRegistry)
|
||||||
|
protected readonly menuRegistry: MenuModelRegistry;
|
||||||
|
|
||||||
|
@inject(MainMenuManager)
|
||||||
|
protected readonly menuManager: MainMenuManager;
|
||||||
|
|
||||||
|
@inject(ExamplesService)
|
||||||
|
protected readonly examplesService: ExamplesService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||||
|
|
||||||
|
protected readonly toDispose = new DisposableCollection();
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
init(): void {
|
||||||
|
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.handleBoardChanged(selectedBoard));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleBoardChanged(board: Board | undefined): void {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
try {
|
||||||
|
// This is a hack the ensures the desired menu ordering! We cannot use https://github.com/eclipse-theia/theia/pull/8377 due to ATL-222.
|
||||||
|
const index = ArduinoMenus.FILE__EXAMPLES_SUBMENU.length - 1;
|
||||||
|
const menuId = ArduinoMenus.FILE__EXAMPLES_SUBMENU[index];
|
||||||
|
const groupPath = index === 0 ? [] : ArduinoMenus.FILE__EXAMPLES_SUBMENU.slice(0, index);
|
||||||
|
const parent: CompositeMenuNode = (registry as any).findGroup(groupPath);
|
||||||
|
const examples = new CompositeMenuNode(menuId, '', { order: '4' });
|
||||||
|
parent.addNode(examples);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
console.warn('Could not patch menu ordering.');
|
||||||
|
}
|
||||||
|
// Registering the same submenu multiple times has no side-effect.
|
||||||
|
// TODO: unregister submenu? https://github.com/eclipse-theia/theia/issues/7300
|
||||||
|
registry.registerSubmenu(ArduinoMenus.FILE__EXAMPLES_SUBMENU, 'Examples', { order: '4' });
|
||||||
|
}
|
||||||
|
|
||||||
|
registerRecursively(
|
||||||
|
exampleContainerOrPlaceholder: ExampleContainer | string,
|
||||||
|
menuPath: MenuPath,
|
||||||
|
pushToDispose: DisposableCollection = new DisposableCollection()): void {
|
||||||
|
|
||||||
|
if (typeof exampleContainerOrPlaceholder === 'string') {
|
||||||
|
const placeholder = new PlaceholderMenuNode(menuPath, exampleContainerOrPlaceholder);
|
||||||
|
this.menuRegistry.registerMenuNode(menuPath, placeholder);
|
||||||
|
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id)));
|
||||||
|
} else {
|
||||||
|
const { label, sketches, children } = exampleContainerOrPlaceholder;
|
||||||
|
const submenuPath = [...menuPath, label];
|
||||||
|
this.menuRegistry.registerSubmenu(submenuPath, label);
|
||||||
|
children.forEach(child => this.registerRecursively(child, submenuPath, pushToDispose));
|
||||||
|
for (const sketch of sketches) {
|
||||||
|
const { uri } = sketch;
|
||||||
|
const commandId = `arduino-open-example-${submenuPath.join(':')}--${uri}`;
|
||||||
|
const command = { id: commandId };
|
||||||
|
const handler = {
|
||||||
|
execute: async () => {
|
||||||
|
const sketch = await this.sketchService.cloneExample(uri);
|
||||||
|
this.commandService.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pushToDispose.push(this.commandRegistry.registerCommand(command, handler));
|
||||||
|
this.menuRegistry.registerMenuAction(submenuPath, { commandId, label: sketch.name });
|
||||||
|
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BuiltInExamples extends Examples {
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.register(); // no `await`
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async register() {
|
||||||
|
let exampleContainers: ExampleContainer[] | undefined;
|
||||||
|
try {
|
||||||
|
exampleContainers = await this.examplesService.builtIns();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Could not initialize built-in examples.', e);
|
||||||
|
this.messageService.error('Could not initialize built-in examples.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.toDispose.dispose();
|
||||||
|
for (const container of ['Built-in examples', ...exampleContainers]) {
|
||||||
|
this.registerRecursively(container, ArduinoMenus.EXAMPLES__BUILT_IN_GROUP, this.toDispose);
|
||||||
|
}
|
||||||
|
this.menuManager.update();
|
||||||
|
// TODO: remove
|
||||||
|
console.log(typeof this.menuRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class LibraryExamples extends Examples {
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.register(); // no `await`
|
||||||
|
this.notificationCenter.onLibraryInstalled(() => this.register());
|
||||||
|
this.notificationCenter.onLibraryUninstalled(() => this.register());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleBoardChanged(board: Board | undefined): void {
|
||||||
|
this.register(board);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async register(board: Board | undefined = this.boardsServiceClient.boardsConfig.selectedBoard) {
|
||||||
|
return this.queue.add(async () => {
|
||||||
|
this.toDispose.dispose();
|
||||||
|
if (!board || !board.fqbn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { fqbn, name } = board;
|
||||||
|
const { user, current, any } = await this.examplesService.installed({ fqbn });
|
||||||
|
if (user.length) {
|
||||||
|
(user as any).unshift('Examples from Custom Libraries');
|
||||||
|
}
|
||||||
|
if (current.length) {
|
||||||
|
(current as any).unshift(`Examples for ${name}`);
|
||||||
|
}
|
||||||
|
if (any.length) {
|
||||||
|
(any as any).unshift('Examples for any board');
|
||||||
|
}
|
||||||
|
for (const container of user) {
|
||||||
|
this.registerRecursively(container, ArduinoMenus.EXAMPLES__USER_LIBS_GROUP, this.toDispose);
|
||||||
|
}
|
||||||
|
for (const container of current) {
|
||||||
|
this.registerRecursively(container, ArduinoMenus.EXAMPLES__CURRENT_BOARD_GROUP, this.toDispose);
|
||||||
|
}
|
||||||
|
for (const container of any) {
|
||||||
|
this.registerRecursively(container, ArduinoMenus.EXAMPLES__ANY_BOARD_GROUP, this.toDispose);
|
||||||
|
}
|
||||||
|
this.menuManager.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
137
arduino-ide-extension/src/browser/contributions/help.ts
Normal file
137
arduino-ide-extension/src/browser/contributions/help.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||||
|
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||||
|
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||||
|
import { CommandHandler } from '@theia/core/lib/common/command';
|
||||||
|
import { QuickInputService } from '@theia/core/lib/browser/quick-open/quick-input-service';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { Contribution, Command, MenuModelRegistry, CommandRegistry, KeybindingRegistry } from './contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class Help extends Contribution {
|
||||||
|
|
||||||
|
@inject(EditorManager)
|
||||||
|
protected readonly editorManager: EditorManager;
|
||||||
|
|
||||||
|
@inject(WindowService)
|
||||||
|
protected readonly windowService: WindowService;
|
||||||
|
|
||||||
|
@inject(QuickInputService)
|
||||||
|
protected readonly quickInputService: QuickInputService;
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
const open = (url: string) => this.windowService.openNewWindow(url, { external: true });
|
||||||
|
const createOpenHandler = (url: string) => <CommandHandler>{
|
||||||
|
execute: () => open(url)
|
||||||
|
};
|
||||||
|
registry.registerCommand(Help.Commands.GETTING_STARTED, createOpenHandler('https://www.arduino.cc/en/Guide'));
|
||||||
|
registry.registerCommand(Help.Commands.ENVIRONMENT, createOpenHandler('https://www.arduino.cc/en/Guide/Environment'));
|
||||||
|
registry.registerCommand(Help.Commands.TROUBLESHOOTING, createOpenHandler('https://support.arduino.cc/hc/en-us'));
|
||||||
|
registry.registerCommand(Help.Commands.REFERENCE, createOpenHandler('https://www.arduino.cc/reference/en/'));
|
||||||
|
registry.registerCommand(Help.Commands.FIND_IN_REFERENCE, {
|
||||||
|
execute: async () => {
|
||||||
|
let searchFor: string | undefined = undefined;
|
||||||
|
const { currentEditor } = this.editorManager;
|
||||||
|
if (currentEditor && currentEditor.editor instanceof MonacoEditor) {
|
||||||
|
const codeEditor = currentEditor.editor.getControl();
|
||||||
|
const selection = codeEditor.getSelection();
|
||||||
|
const model = codeEditor.getModel();
|
||||||
|
if (model && selection && !monaco.Range.isEmpty(selection)) {
|
||||||
|
searchFor = model.getValueInRange(selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!searchFor) {
|
||||||
|
searchFor = await this.quickInputService.open({
|
||||||
|
prompt: 'Search on Arduino.cc',
|
||||||
|
placeHolder: 'Type a keyword'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (searchFor) {
|
||||||
|
return open(`https://www.arduino.cc/search?q=${encodeURIComponent(searchFor)}&tab=reference`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registry.registerCommand(Help.Commands.FAQ, createOpenHandler('https://support.arduino.cc/hc/en-us'));
|
||||||
|
registry.registerCommand(Help.Commands.VISIT_ARDUINO, createOpenHandler('https://www.arduino.cc/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||||
|
commandId: Help.Commands.GETTING_STARTED.id,
|
||||||
|
order: '0'
|
||||||
|
});
|
||||||
|
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||||
|
commandId: Help.Commands.ENVIRONMENT.id,
|
||||||
|
order: '1'
|
||||||
|
});
|
||||||
|
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||||
|
commandId: Help.Commands.TROUBLESHOOTING.id,
|
||||||
|
order: '2'
|
||||||
|
});
|
||||||
|
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||||
|
commandId: Help.Commands.REFERENCE.id,
|
||||||
|
order: '3'
|
||||||
|
});
|
||||||
|
|
||||||
|
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
|
||||||
|
commandId: Help.Commands.FIND_IN_REFERENCE.id,
|
||||||
|
order: '4'
|
||||||
|
});
|
||||||
|
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
|
||||||
|
commandId: Help.Commands.FAQ.id,
|
||||||
|
order: '5'
|
||||||
|
});
|
||||||
|
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
|
||||||
|
commandId: Help.Commands.VISIT_ARDUINO.id,
|
||||||
|
order: '6'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerKeybindings(registry: KeybindingRegistry): void {
|
||||||
|
registry.registerKeybinding({
|
||||||
|
command: Help.Commands.FIND_IN_REFERENCE.id,
|
||||||
|
keybinding: 'CtrlCmd+Shift+F'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Help {
|
||||||
|
export namespace Commands {
|
||||||
|
export const GETTING_STARTED: Command = {
|
||||||
|
id: 'arduino-getting-started',
|
||||||
|
label: 'Getting Started',
|
||||||
|
category: 'Arduino'
|
||||||
|
};
|
||||||
|
export const ENVIRONMENT: Command = {
|
||||||
|
id: 'arduino-environment',
|
||||||
|
label: 'Environment',
|
||||||
|
category: 'Arduino'
|
||||||
|
};
|
||||||
|
export const TROUBLESHOOTING: Command = {
|
||||||
|
id: 'arduino-troubleshooting',
|
||||||
|
label: 'Troubleshooting',
|
||||||
|
category: 'Arduino'
|
||||||
|
};
|
||||||
|
export const REFERENCE: Command = {
|
||||||
|
id: 'arduino-reference',
|
||||||
|
label: 'Reference',
|
||||||
|
category: 'Arduino'
|
||||||
|
};
|
||||||
|
export const FIND_IN_REFERENCE: Command = {
|
||||||
|
id: 'arduino-find-in-reference',
|
||||||
|
label: 'Find in Reference',
|
||||||
|
category: 'Arduino'
|
||||||
|
};
|
||||||
|
export const FAQ: Command = {
|
||||||
|
id: 'arduino-faq',
|
||||||
|
label: 'Frequently Asked Questions',
|
||||||
|
category: 'Arduino'
|
||||||
|
};
|
||||||
|
export const VISIT_ARDUINO: Command = {
|
||||||
|
id: 'arduino-visit-arduino',
|
||||||
|
label: 'Visit Arduino.cc',
|
||||||
|
category: 'Arduino'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,175 @@
|
|||||||
|
import * as PQueue from 'p-queue';
|
||||||
|
import { inject, injectable } from '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, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||||
|
import { LibraryPackage, LibraryService } from '../../common/protocol';
|
||||||
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||||
|
import { LibraryListWidget } from '../library/library-list-widget';
|
||||||
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
import { SketchContribution, Command, CommandRegistry } from './contribution';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class IncludeLibrary extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(CommandRegistry)
|
||||||
|
protected readonly commandRegistry: CommandRegistry;
|
||||||
|
|
||||||
|
@inject(MenuModelRegistry)
|
||||||
|
protected readonly menuRegistry: MenuModelRegistry;
|
||||||
|
|
||||||
|
@inject(MainMenuManager)
|
||||||
|
protected readonly mainMenuManager: MainMenuManager;
|
||||||
|
|
||||||
|
@inject(EditorManager)
|
||||||
|
protected readonly editorManager: EditorManager;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||||
|
|
||||||
|
@inject(LibraryService)
|
||||||
|
protected readonly libraryService: LibraryService;
|
||||||
|
|
||||||
|
protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 });
|
||||||
|
protected readonly toDispose = new DisposableCollection();
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.updateMenuActions();
|
||||||
|
this.boardsServiceClient.onBoardsConfigChanged(() => this.updateMenuActions())
|
||||||
|
this.notificationCenter.onLibraryInstalled(() => this.updateMenuActions());
|
||||||
|
this.notificationCenter.onLibraryUninstalled(() => this.updateMenuActions());
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, {
|
||||||
|
execute: async arg => {
|
||||||
|
if (LibraryPackage.is(arg)) {
|
||||||
|
this.includeLibrary(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async updateMenuActions(): Promise<void> {
|
||||||
|
return this.queue.add(async () => {
|
||||||
|
this.toDispose.dispose();
|
||||||
|
this.mainMenuManager.update();
|
||||||
|
const libraries: LibraryPackage[] = []
|
||||||
|
const fqbn = this.boardsServiceClient.boardsConfig.selectedBoard?.fqbn;
|
||||||
|
// Do not show board specific examples, when no board is selected.
|
||||||
|
if (fqbn) {
|
||||||
|
libraries.push(...await this.libraryService.list({ fqbn }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Include Library` submenu
|
||||||
|
const includeLibMenuPath = [...ArduinoMenus.SKETCH__UTILS_GROUP, '0_include'];
|
||||||
|
this.menuRegistry.registerSubmenu(includeLibMenuPath, 'Include Library', { order: '1' });
|
||||||
|
// `Manage Libraries...` group.
|
||||||
|
this.menuRegistry.registerMenuAction([...includeLibMenuPath, '0_manage'], {
|
||||||
|
commandId: `${LibraryListWidget.WIDGET_ID}:toggle`,
|
||||||
|
label: 'Manage Libraries...'
|
||||||
|
});
|
||||||
|
this.toDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction({ commandId: `${LibraryListWidget.WIDGET_ID}:toggle` })));
|
||||||
|
|
||||||
|
// `Add .ZIP Library...`
|
||||||
|
// TODO: implement it
|
||||||
|
|
||||||
|
// `Arduino libraries`
|
||||||
|
const packageMenuPath = [...includeLibMenuPath, '2_arduino'];
|
||||||
|
const userMenuPath = [...includeLibMenuPath, '3_contributed'];
|
||||||
|
const { user, rest } = LibraryPackage.groupByLocation(libraries);
|
||||||
|
if (rest.length) {
|
||||||
|
(rest as any).unshift('Arduino libraries');
|
||||||
|
}
|
||||||
|
if (user.length) {
|
||||||
|
(user as any).unshift('Contributed libraries');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const library of user) {
|
||||||
|
this.toDispose.push(this.registerLibrary(library, userMenuPath));
|
||||||
|
}
|
||||||
|
for (const library of rest) {
|
||||||
|
this.toDispose.push(this.registerLibrary(library, packageMenuPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mainMenuManager.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected registerLibrary(libraryOrPlaceholder: LibraryPackage | string, menuPath: MenuPath): Disposable {
|
||||||
|
if (typeof libraryOrPlaceholder === 'string') {
|
||||||
|
const placeholder = new PlaceholderMenuNode(menuPath, libraryOrPlaceholder);
|
||||||
|
this.menuRegistry.registerMenuNode(menuPath, placeholder);
|
||||||
|
return Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id));
|
||||||
|
}
|
||||||
|
const commandId = `arduino-include-library--${libraryOrPlaceholder.name}:${libraryOrPlaceholder.author}`;
|
||||||
|
const command = { id: commandId };
|
||||||
|
const handler = { execute: () => this.commandRegistry.executeCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY.id, libraryOrPlaceholder) };
|
||||||
|
const menuAction = { commandId, label: libraryOrPlaceholder.name };
|
||||||
|
this.menuRegistry.registerMenuAction(menuPath, menuAction);
|
||||||
|
return new DisposableCollection(
|
||||||
|
this.commandRegistry.registerCommand(command, handler),
|
||||||
|
Disposable.create(() => this.menuRegistry.unregisterMenuAction(menuAction)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async includeLibrary(library: LibraryPackage): Promise<void> {
|
||||||
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (!sketch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If the current editor is one of the additional files from the sketch, we use that.
|
||||||
|
// Otherwise, we pick the editor of the main sketch file.
|
||||||
|
let codeEditor: monaco.editor.IStandaloneCodeEditor | undefined;
|
||||||
|
const editor = this.editorManager.currentEditor?.editor;
|
||||||
|
if (editor instanceof MonacoEditor) {
|
||||||
|
if (sketch.additionalFileUris.some(uri => uri === editor.uri.toString())) {
|
||||||
|
codeEditor = editor.getControl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!codeEditor) {
|
||||||
|
const widget = await this.editorManager.open(new URI(sketch.mainFileUri));
|
||||||
|
if (widget.editor instanceof MonacoEditor) {
|
||||||
|
codeEditor = widget.editor.getControl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!codeEditor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textModel = codeEditor.getModel();
|
||||||
|
if (!textModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cursorState = codeEditor.getSelections() || [];
|
||||||
|
const eol = textModel.getEOL();
|
||||||
|
const includes = library.includes.slice();
|
||||||
|
includes.push(''); // For the trailing new line.
|
||||||
|
const text = includes.map(include => include ? `#include <${include}>` : eol).join(eol);
|
||||||
|
textModel.pushStackElement(); // Start a fresh operation.
|
||||||
|
textModel.pushEditOperations(cursorState, [{
|
||||||
|
range: new monaco.Range(1, 1, 1, 1),
|
||||||
|
text,
|
||||||
|
forceMoveMarkers: true
|
||||||
|
}], () => cursorState);
|
||||||
|
textModel.pushStackElement(); // Make it undoable.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace IncludeLibrary {
|
||||||
|
export namespace Commands {
|
||||||
|
export const INCLUDE_LIBRARY: Command = {
|
||||||
|
id: 'arduino-include-library'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,62 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { WorkspaceServer } from '@theia/workspace/lib/common/workspace-protocol';
|
||||||
|
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { SketchContribution, CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||||
|
import { OpenSketch } from './open-sketch';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class OpenRecentSketch extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(CommandRegistry)
|
||||||
|
protected readonly commandRegistry: CommandRegistry;
|
||||||
|
|
||||||
|
@inject(MenuModelRegistry)
|
||||||
|
protected readonly menuRegistry: MenuModelRegistry;
|
||||||
|
|
||||||
|
@inject(MainMenuManager)
|
||||||
|
protected readonly mainMenuManager: MainMenuManager;
|
||||||
|
|
||||||
|
@inject(WorkspaceServer)
|
||||||
|
protected readonly workspaceServer: WorkspaceServer;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
protected toDisposeBeforeRegister = new Map<string, DisposableCollection>();
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
const refreshMenu = (sketches: Sketch[]) => {
|
||||||
|
this.register(sketches);
|
||||||
|
this.mainMenuManager.update();
|
||||||
|
};
|
||||||
|
this.notificationCenter.onRecentSketchesChanged(({ sketches }) => refreshMenu(sketches));
|
||||||
|
this.sketchService.recentlyOpenedSketches().then(refreshMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
registry.registerSubmenu(ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, 'Open Recent', { order: '2' });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected register(sketches: Sketch[]): void {
|
||||||
|
let order = 0;
|
||||||
|
for (const sketch of sketches) {
|
||||||
|
const { uri } = sketch;
|
||||||
|
const toDispose = this.toDisposeBeforeRegister.get(uri);
|
||||||
|
if (toDispose) {
|
||||||
|
toDispose.dispose();
|
||||||
|
}
|
||||||
|
const command = { id: `arduino-open-recent--${uri}` };
|
||||||
|
const handler = { execute: () => this.commandRegistry.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch) };
|
||||||
|
this.commandRegistry.registerCommand(command, handler);
|
||||||
|
this.menuRegistry.registerMenuAction(ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, { commandId: command.id, label: sketch.name, order: String(order) });
|
||||||
|
this.toDisposeBeforeRegister.set(sketch.uri, new DisposableCollection(
|
||||||
|
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
|
||||||
|
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
import { injectable } from 'inversify';
|
import { injectable } from 'inversify';
|
||||||
import { remote } from 'electron';
|
import { remote } from 'electron';
|
||||||
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry } from './contribution';
|
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry } from './contribution';
|
||||||
|
|
||||||
@@ -30,9 +31,9 @@ export class OpenSketchExternal extends SketchContribution {
|
|||||||
protected async openExternal(): Promise<void> {
|
protected async openExternal(): Promise<void> {
|
||||||
const uri = await this.sketchServiceClient.currentSketchFile();
|
const uri = await this.sketchServiceClient.currentSketchFile();
|
||||||
if (uri) {
|
if (uri) {
|
||||||
const exists = this.fileSystem.exists(uri);
|
const exists = this.fileService.exists(new URI(uri));
|
||||||
if (exists) {
|
if (exists) {
|
||||||
const fsPath = await this.fileSystem.getFsPath(uri);
|
const fsPath = await this.fileService.fsPath(new URI(uri));
|
||||||
if (fsPath) {
|
if (fsPath) {
|
||||||
remote.shell.showItemInFolder(fsPath);
|
remote.shell.showItemInFolder(fsPath);
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,8 @@ import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposa
|
|||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||||
import { SketchContribution, Sketch, URI, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
import { SketchContribution, Sketch, URI, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||||
|
import { ExamplesService } from '../../common/protocol/examples-service';
|
||||||
|
import { BuiltInExamples } from './examples';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class OpenSketch extends SketchContribution {
|
export class OpenSketch extends SketchContribution {
|
||||||
@@ -16,6 +18,12 @@ export class OpenSketch extends SketchContribution {
|
|||||||
@inject(ContextMenuRenderer)
|
@inject(ContextMenuRenderer)
|
||||||
protected readonly contextMenuRenderer: ContextMenuRenderer;
|
protected readonly contextMenuRenderer: ContextMenuRenderer;
|
||||||
|
|
||||||
|
@inject(BuiltInExamples)
|
||||||
|
protected readonly builtInExamples: BuiltInExamples;
|
||||||
|
|
||||||
|
@inject(ExamplesService)
|
||||||
|
protected readonly examplesService: ExamplesService;
|
||||||
|
|
||||||
protected readonly toDisposeBeforeCreateNewContextMenu = new DisposableCollection();
|
protected readonly toDisposeBeforeCreateNewContextMenu = new DisposableCollection();
|
||||||
|
|
||||||
registerCommands(registry: CommandRegistry): void {
|
registerCommands(registry: CommandRegistry): void {
|
||||||
@@ -53,6 +61,14 @@ export class OpenSketch extends SketchContribution {
|
|||||||
});
|
});
|
||||||
this.toDisposeBeforeCreateNewContextMenu.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
this.toDisposeBeforeCreateNewContextMenu.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
const containers = await this.examplesService.builtIns();
|
||||||
|
for (const container of containers) {
|
||||||
|
this.builtInExamples.registerRecursively(container, ArduinoMenus.OPEN_SKETCH__CONTEXT__EXAMPLES_GROUP, this.toDisposeBeforeCreateNewContextMenu);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error when collecting built-in examples.', e);
|
||||||
|
}
|
||||||
const options = {
|
const options = {
|
||||||
menuPath: ArduinoMenus.OPEN_SKETCH__CONTEXT,
|
menuPath: ArduinoMenus.OPEN_SKETCH__CONTEXT,
|
||||||
anchor: {
|
anchor: {
|
||||||
@@ -99,14 +115,14 @@ export class OpenSketch extends SketchContribution {
|
|||||||
|
|
||||||
protected async selectSketch(): Promise<Sketch | undefined> {
|
protected async selectSketch(): Promise<Sketch | undefined> {
|
||||||
const config = await this.configService.getConfiguration();
|
const config = await this.configService.getConfiguration();
|
||||||
const defaultPath = await this.fileSystem.getFsPath(config.sketchDirUri);
|
const defaultPath = await this.fileService.fsPath(new URI(config.sketchDirUri));
|
||||||
const { filePaths } = await remote.dialog.showOpenDialog({
|
const { filePaths } = await remote.dialog.showOpenDialog({
|
||||||
defaultPath,
|
defaultPath,
|
||||||
properties: ['createDirectory', 'openFile'],
|
properties: ['createDirectory', 'openFile'],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
name: 'Sketch',
|
name: 'Sketch',
|
||||||
extensions: ['ino']
|
extensions: ['ino', 'pde']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -122,7 +138,7 @@ export class OpenSketch extends SketchContribution {
|
|||||||
if (sketch) {
|
if (sketch) {
|
||||||
return sketch;
|
return sketch;
|
||||||
}
|
}
|
||||||
if (sketchFileUri.endsWith('.ino')) {
|
if (Sketch.isSketchFile(sketchFileUri)) {
|
||||||
const name = new URI(sketchFileUri).path.name;
|
const name = new URI(sketchFileUri).path.name;
|
||||||
const nameWithExt = this.labelProvider.getName(new URI(sketchFileUri));
|
const nameWithExt = this.labelProvider.getName(new URI(sketchFileUri));
|
||||||
const { response } = await remote.dialog.showMessageBox({
|
const { response } = await remote.dialog.showMessageBox({
|
||||||
@@ -133,7 +149,7 @@ export class OpenSketch extends SketchContribution {
|
|||||||
});
|
});
|
||||||
if (response === 1) { // OK
|
if (response === 1) { // OK
|
||||||
const newSketchUri = new URI(sketchFileUri).parent.resolve(name);
|
const newSketchUri = new URI(sketchFileUri).parent.resolve(name);
|
||||||
const exists = await this.fileSystem.exists(newSketchUri.toString());
|
const exists = await this.fileService.exists(newSketchUri);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
await remote.dialog.showMessageBox({
|
await remote.dialog.showMessageBox({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
@@ -142,8 +158,8 @@ export class OpenSketch extends SketchContribution {
|
|||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
await this.fileSystem.createFolder(newSketchUri.toString());
|
await this.fileService.createFolder(newSketchUri);
|
||||||
await this.fileSystem.move(sketchFileUri, newSketchUri.resolve(nameWithExt).toString());
|
await this.fileService.move(new URI(sketchFileUri), new URI(newSketchUri.resolve(nameWithExt).toString()));
|
||||||
return this.sketchService.getSketchFolder(newSketchUri.toString());
|
return this.sketchService.getSketchFolder(newSketchUri.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -45,11 +45,11 @@ export class SaveAsSketch extends SketchContribution {
|
|||||||
// If target does not exist, propose a `directories.user`/${sketch.name} path
|
// If target does not exist, propose a `directories.user`/${sketch.name} path
|
||||||
// If target exists, propose `directories.user`/${sketch.name}_copy_${yyyymmddHHMMss}
|
// If target exists, propose `directories.user`/${sketch.name}_copy_${yyyymmddHHMMss}
|
||||||
const sketchDirUri = new URI((await this.configService.getConfiguration()).sketchDirUri);
|
const sketchDirUri = new URI((await this.configService.getConfiguration()).sketchDirUri);
|
||||||
const exists = await this.fileSystem.exists(sketchDirUri.resolve(sketch.name).toString());
|
const exists = await this.fileService.exists(sketchDirUri.resolve(sketch.name));
|
||||||
const defaultUri = exists
|
const defaultUri = exists
|
||||||
? sketchDirUri.resolve(sketchDirUri.resolve(`${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}`).toString())
|
? sketchDirUri.resolve(sketchDirUri.resolve(`${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}`).toString())
|
||||||
: sketchDirUri.resolve(sketch.name);
|
: sketchDirUri.resolve(sketch.name);
|
||||||
const defaultPath = await this.fileSystem.getFsPath(defaultUri.toString())!;
|
const defaultPath = await this.fileService.fsPath(defaultUri);
|
||||||
const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath });
|
const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath });
|
||||||
if (!filePath || canceled) {
|
if (!filePath || canceled) {
|
||||||
return false;
|
return false;
|
||||||
@@ -60,8 +60,10 @@ export class SaveAsSketch extends SketchContribution {
|
|||||||
}
|
}
|
||||||
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
|
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
|
||||||
if (workspaceUri && openAfterMove) {
|
if (workspaceUri && openAfterMove) {
|
||||||
if (wipeOriginal) {
|
if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) {
|
||||||
await this.fileSystem.delete(sketch.uri);
|
try {
|
||||||
|
await this.fileService.delete(new URI(sketch.uri), { recursive: true });
|
||||||
|
} catch { /* NOOP: from time to time, it's not possible to wipe the old resource from the temp dir on Windows */ }
|
||||||
}
|
}
|
||||||
this.workspaceService.open(new URI(workspaceUri), { preserveWindow: true });
|
this.workspaceService.open(new URI(workspaceUri), { preserveWindow: true });
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +1,49 @@
|
|||||||
import { injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
import { Command, MenuModelRegistry, CommandRegistry, SketchContribution, KeybindingRegistry } from './contribution';
|
||||||
import { URI, Command, MenuModelRegistry, CommandRegistry, SketchContribution, open } from './contribution';
|
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { Settings as Preferences, SettingsDialog } from '../settings';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class Settings extends SketchContribution {
|
export class Settings extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(SettingsDialog)
|
||||||
|
protected readonly settingsDialog: SettingsDialog;
|
||||||
|
|
||||||
|
protected settingsOpened = false;
|
||||||
|
|
||||||
registerCommands(registry: CommandRegistry): void {
|
registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(Settings.Commands.OPEN_CLI_CONFIG, {
|
registry.registerCommand(Settings.Commands.OPEN, {
|
||||||
execute: () => this.configService.getCliConfigFileUri().then(uri => open(this.openerService, new URI(uri)))
|
execute: async () => {
|
||||||
|
let settings: Preferences | undefined = undefined;
|
||||||
|
try {
|
||||||
|
this.settingsOpened = true;
|
||||||
|
settings = await this.settingsDialog.open();
|
||||||
|
} finally {
|
||||||
|
this.settingsOpened = false;
|
||||||
|
}
|
||||||
|
if (settings) {
|
||||||
|
await this.settingsService.update(settings);
|
||||||
|
await this.settingsService.save();
|
||||||
|
} else {
|
||||||
|
await this.settingsService.reset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isEnabled: () => !this.settingsOpened
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMenus(registry: MenuModelRegistry): void {
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
registry.registerMenuAction(ArduinoMenus.FILE__SETTINGS_GROUP, {
|
registry.registerMenuAction(ArduinoMenus.FILE__SETTINGS_GROUP, {
|
||||||
commandId: CommonCommands.OPEN_PREFERENCES.id,
|
commandId: Settings.Commands.OPEN.id,
|
||||||
label: 'Preferences...',
|
label: 'Preferences...',
|
||||||
order: '0'
|
order: '0'
|
||||||
});
|
});
|
||||||
registry.registerMenuAction(ArduinoMenus.FILE__SETTINGS_GROUP, {
|
}
|
||||||
commandId: Settings.Commands.OPEN_CLI_CONFIG.id,
|
|
||||||
label: 'Open CLI Configuration',
|
registerKeybindings(registry: KeybindingRegistry): void {
|
||||||
order: '1',
|
registry.registerKeybinding({
|
||||||
|
command: Settings.Commands.OPEN.id,
|
||||||
|
keybinding: 'CtrlCmd+,',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,9 +51,9 @@ export class Settings extends SketchContribution {
|
|||||||
|
|
||||||
export namespace Settings {
|
export namespace Settings {
|
||||||
export namespace Commands {
|
export namespace Commands {
|
||||||
export const OPEN_CLI_CONFIG: Command = {
|
export const OPEN: Command = {
|
||||||
id: 'arduino-open-cli-config',
|
id: 'arduino-settings-open',
|
||||||
label: 'Open CLI Configuration',
|
label: 'Open Preferences...',
|
||||||
category: 'Arduino'
|
category: 'Arduino'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -40,8 +40,8 @@ export class SketchControl extends SketchContribution {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mainFileUri, otherSketchFileUris, additionalFileUris } = await this.sketchService.loadSketch(sketch.uri);
|
const { mainFileUri, rootFolderFileUris } = await this.sketchService.loadSketch(sketch.uri);
|
||||||
const uris = [mainFileUri, ...otherSketchFileUris, ...additionalFileUris];
|
const uris = [mainFileUri, ...rootFolderFileUris];
|
||||||
for (let i = 0; i < uris.length; i++) {
|
for (let i = 0; i < uris.length; i++) {
|
||||||
const uri = new URI(uris[i]);
|
const uri = new URI(uris[i]);
|
||||||
const command = { id: `arduino-focus-file--${uri.toString()}` };
|
const command = { id: `arduino-focus-file--${uri.toString()}` };
|
||||||
|
@@ -0,0 +1,69 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { SketchContribution, CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
import { OpenSketch } from './open-sketch';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class Sketchbook extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(CommandRegistry)
|
||||||
|
protected readonly commandRegistry: CommandRegistry;
|
||||||
|
|
||||||
|
@inject(MenuModelRegistry)
|
||||||
|
protected readonly menuRegistry: MenuModelRegistry;
|
||||||
|
|
||||||
|
@inject(MainMenuManager)
|
||||||
|
protected readonly mainMenuManager: MainMenuManager;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
protected toDisposePerSketch = new Map<string, DisposableCollection>();
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.sketchService.getSketches().then(sketches => {
|
||||||
|
this.register(sketches);
|
||||||
|
this.mainMenuManager.update();
|
||||||
|
});
|
||||||
|
this.sketchServiceClient.onSketchbookDidChange(({ created, removed }) => {
|
||||||
|
this.unregister(removed);
|
||||||
|
this.register(created);
|
||||||
|
this.mainMenuManager.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
registry.registerSubmenu(ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, 'Sketchbook', { order: '3' });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected register(sketches: Sketch[]): void {
|
||||||
|
for (const sketch of sketches) {
|
||||||
|
const { uri } = sketch;
|
||||||
|
const toDispose = this.toDisposePerSketch.get(uri);
|
||||||
|
if (toDispose) {
|
||||||
|
toDispose.dispose();
|
||||||
|
}
|
||||||
|
const command = { id: `arduino-sketchbook-open--${uri}` };
|
||||||
|
const handler = { execute: () => this.commandRegistry.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch) };
|
||||||
|
this.commandRegistry.registerCommand(command, handler);
|
||||||
|
this.menuRegistry.registerMenuAction(ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, { commandId: command.id, label: sketch.name });
|
||||||
|
this.toDisposePerSketch.set(sketch.uri, new DisposableCollection(
|
||||||
|
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
|
||||||
|
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected unregister(sketches: Sketch[]): void {
|
||||||
|
for (const { uri } of sketches) {
|
||||||
|
const toDispose = this.toDisposePerSketch.get(uri);
|
||||||
|
if (toDispose) {
|
||||||
|
toDispose.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -5,7 +5,7 @@ import { ArduinoMenus } from '../menu/arduino-menus';
|
|||||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||||
import { MonitorConnection } from '../monitor/monitor-connection';
|
import { MonitorConnection } from '../monitor/monitor-connection';
|
||||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@@ -20,8 +20,8 @@ export class UploadSketch extends SketchContribution {
|
|||||||
@inject(BoardsDataStore)
|
@inject(BoardsDataStore)
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
protected readonly boardsDataStore: BoardsDataStore;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
|
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
||||||
|
|
||||||
@inject(OutputChannelManager)
|
@inject(OutputChannelManager)
|
||||||
protected readonly outputChannelManager: OutputChannelManager;
|
protected readonly outputChannelManager: OutputChannelManager;
|
||||||
@@ -43,12 +43,12 @@ export class UploadSketch extends SketchContribution {
|
|||||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||||
label: 'Upload',
|
label: 'Upload',
|
||||||
order: '0'
|
order: '1'
|
||||||
});
|
});
|
||||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
|
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
|
||||||
label: 'Upload Using Programmer',
|
label: 'Upload Using Programmer',
|
||||||
order: '1'
|
order: '2'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,71 +73,81 @@ export class UploadSketch extends SketchContribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async uploadSketch(usingProgrammer: boolean = false): Promise<void> {
|
async uploadSketch(usingProgrammer: boolean = false): Promise<void> {
|
||||||
const uri = await this.sketchServiceClient.currentSketchFile();
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
if (!uri) {
|
if (!sketch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let shouldAutoConnect = false;
|
||||||
const monitorConfig = this.monitorConnection.monitorConfig;
|
const monitorConfig = this.monitorConnection.monitorConfig;
|
||||||
if (monitorConfig) {
|
if (monitorConfig) {
|
||||||
await this.monitorConnection.disconnect();
|
await this.monitorConnection.disconnect();
|
||||||
|
if (this.monitorConnection.autoConnect) {
|
||||||
|
shouldAutoConnect = true;
|
||||||
|
}
|
||||||
|
this.monitorConnection.autoConnect = false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] = await Promise.all([
|
||||||
throw new Error('No boards selected. Please select a board.');
|
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||||
}
|
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||||
if (!boardsConfig.selectedBoard.fqbn) {
|
this.preferences.get('arduino.upload.verify'),
|
||||||
throw new Error(`No core is installed for the '${boardsConfig.selectedBoard.name}' board. Please install the core.`);
|
this.preferences.get('arduino.upload.verbose'),
|
||||||
}
|
this.sourceOverride()
|
||||||
|
|
||||||
const [fqbn, { selectedProgrammer }] = await Promise.all([
|
|
||||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard.fqbn),
|
|
||||||
this.boardsDataStore.getData(boardsConfig.selectedBoard.fqbn)
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let options: CoreService.Upload.Options | undefined = undefined;
|
let options: CoreService.Upload.Options | undefined = undefined;
|
||||||
const sketchUri = uri;
|
const sketchUri = sketch.uri;
|
||||||
const optimizeForDebug = this.editorMode.compileForDebug;
|
const optimizeForDebug = this.editorMode.compileForDebug;
|
||||||
const { selectedPort } = boardsConfig;
|
const { selectedPort } = boardsConfig;
|
||||||
|
const port = selectedPort?.address;
|
||||||
|
|
||||||
if (usingProgrammer) {
|
if (usingProgrammer) {
|
||||||
const programmer = selectedProgrammer;
|
const programmer = selectedProgrammer;
|
||||||
if (!programmer) {
|
|
||||||
throw new Error('Programmer is not selected. Please select a programmer.');
|
|
||||||
}
|
|
||||||
let port: undefined | string = undefined;
|
|
||||||
// If the port is set by the user, we pass it to the CLI as it might be required.
|
|
||||||
// If it is not set but the CLI requires it, we let the CLI to complain.
|
|
||||||
if (selectedPort) {
|
|
||||||
port = selectedPort.address;
|
|
||||||
}
|
|
||||||
options = {
|
options = {
|
||||||
sketchUri,
|
sketchUri,
|
||||||
fqbn,
|
fqbn,
|
||||||
optimizeForDebug,
|
optimizeForDebug,
|
||||||
programmer,
|
programmer,
|
||||||
port
|
port,
|
||||||
|
verbose,
|
||||||
|
verify,
|
||||||
|
sourceOverride
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (!selectedPort) {
|
|
||||||
throw new Error('No ports selected. Please select a port.');
|
|
||||||
}
|
|
||||||
const port = selectedPort.address;
|
|
||||||
options = {
|
options = {
|
||||||
sketchUri,
|
sketchUri,
|
||||||
fqbn,
|
fqbn,
|
||||||
optimizeForDebug,
|
optimizeForDebug,
|
||||||
port
|
port,
|
||||||
|
verbose,
|
||||||
|
verify,
|
||||||
|
sourceOverride
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.outputChannelManager.getChannel('Arduino: upload').clear();
|
this.outputChannelManager.getChannel('Arduino').clear();
|
||||||
await this.coreService.upload(options);
|
if (usingProgrammer) {
|
||||||
|
await this.coreService.uploadUsingProgrammer(options);
|
||||||
|
} else {
|
||||||
|
await this.coreService.upload(options);
|
||||||
|
}
|
||||||
this.messageService.info('Done uploading.', { timeout: 1000 });
|
this.messageService.info('Done uploading.', { timeout: 1000 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.messageService.error(e.toString());
|
this.messageService.error(e.toString());
|
||||||
} finally {
|
} finally {
|
||||||
if (monitorConfig) {
|
if (monitorConfig) {
|
||||||
await this.monitorConnection.connect(monitorConfig);
|
const { board, port } = monitorConfig;
|
||||||
|
try {
|
||||||
|
await this.boardsServiceClientImpl.waitUntilAvailable(Object.assign(board, { port }), 10_000);
|
||||||
|
if (shouldAutoConnect) {
|
||||||
|
// Enabling auto-connect will trigger a connect.
|
||||||
|
this.monitorConnection.autoConnect = true;
|
||||||
|
} else {
|
||||||
|
await this.monitorConnection.connect(monitorConfig);
|
||||||
|
}
|
||||||
|
} catch (waitError) {
|
||||||
|
this.messageService.error(`Could not reconnect to serial monitor. ${waitError.toString()}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ import { CoreService } from '../../common/protocol';
|
|||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@@ -16,8 +16,8 @@ export class VerifySketch extends SketchContribution {
|
|||||||
@inject(BoardsDataStore)
|
@inject(BoardsDataStore)
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
protected readonly boardsDataStore: BoardsDataStore;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
|
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
||||||
|
|
||||||
@inject(OutputChannelManager)
|
@inject(OutputChannelManager)
|
||||||
protected readonly outputChannelManager: OutputChannelManager;
|
protected readonly outputChannelManager: OutputChannelManager;
|
||||||
@@ -26,6 +26,9 @@ export class VerifySketch extends SketchContribution {
|
|||||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
|
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
|
||||||
execute: () => this.verifySketch()
|
execute: () => this.verifySketch()
|
||||||
});
|
});
|
||||||
|
registry.registerCommand(VerifySketch.Commands.EXPORT_BINARIES, {
|
||||||
|
execute: () => this.verifySketch(true)
|
||||||
|
});
|
||||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
||||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
execute: () => registry.executeCommand(VerifySketch.Commands.VERIFY_SKETCH.id)
|
execute: () => registry.executeCommand(VerifySketch.Commands.VERIFY_SKETCH.id)
|
||||||
@@ -36,7 +39,12 @@ export class VerifySketch extends SketchContribution {
|
|||||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||||
commandId: VerifySketch.Commands.VERIFY_SKETCH.id,
|
commandId: VerifySketch.Commands.VERIFY_SKETCH.id,
|
||||||
label: 'Verify/Compile',
|
label: 'Verify/Compile',
|
||||||
order: '2'
|
order: '0'
|
||||||
|
});
|
||||||
|
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||||
|
commandId: VerifySketch.Commands.EXPORT_BINARIES.id,
|
||||||
|
label: 'Export compiled Binary',
|
||||||
|
order: '3'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +53,10 @@ export class VerifySketch extends SketchContribution {
|
|||||||
command: VerifySketch.Commands.VERIFY_SKETCH.id,
|
command: VerifySketch.Commands.VERIFY_SKETCH.id,
|
||||||
keybinding: 'CtrlCmd+R'
|
keybinding: 'CtrlCmd+R'
|
||||||
});
|
});
|
||||||
|
registry.registerKeybinding({
|
||||||
|
command: VerifySketch.Commands.EXPORT_BINARIES.id,
|
||||||
|
keybinding: 'CtrlCmd+Alt+S'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||||
@@ -56,25 +68,26 @@ export class VerifySketch extends SketchContribution {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifySketch(): Promise<void> {
|
async verifySketch(exportBinaries?: boolean): Promise<void> {
|
||||||
const uri = await this.sketchServiceClient.currentSketchFile();
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
if (!uri) {
|
if (!sketch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
const [fqbn, sourceOverride] = await Promise.all([
|
||||||
throw new Error('No boards selected. Please select a board.');
|
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||||
}
|
this.sourceOverride()
|
||||||
if (!boardsConfig.selectedBoard.fqbn) {
|
]);
|
||||||
throw new Error(`No core is installed for the '${boardsConfig.selectedBoard.name}' board. Please install the core.`);
|
const verbose = this.preferences.get('arduino.compile.verbose');
|
||||||
}
|
this.outputChannelManager.getChannel('Arduino').clear();
|
||||||
const fqbn = await this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard.fqbn);
|
|
||||||
this.outputChannelManager.getChannel('Arduino: compile').clear();
|
|
||||||
await this.coreService.compile({
|
await this.coreService.compile({
|
||||||
sketchUri: uri,
|
sketchUri: sketch.uri,
|
||||||
fqbn,
|
fqbn,
|
||||||
optimizeForDebug: this.editorMode.compileForDebug
|
optimizeForDebug: this.editorMode.compileForDebug,
|
||||||
|
verbose,
|
||||||
|
exportBinaries,
|
||||||
|
sourceOverride
|
||||||
});
|
});
|
||||||
this.messageService.info('Done compiling.', { timeout: 1000 });
|
this.messageService.info('Done compiling.', { timeout: 1000 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -89,6 +102,9 @@ export namespace VerifySketch {
|
|||||||
export const VERIFY_SKETCH: Command = {
|
export const VERIFY_SKETCH: Command = {
|
||||||
id: 'arduino-verify-sketch'
|
id: 'arduino-verify-sketch'
|
||||||
};
|
};
|
||||||
|
export const EXPORT_BINARIES: Command = {
|
||||||
|
id: 'arduino-export-binaries'
|
||||||
|
};
|
||||||
export const VERIFY_SKETCH_TOOLBAR: Command = {
|
export const VERIFY_SKETCH_TOOLBAR: Command = {
|
||||||
id: 'arduino-verify-sketch--toolbar'
|
id: 'arduino-verify-sketch--toolbar'
|
||||||
};
|
};
|
||||||
|
@@ -1,36 +0,0 @@
|
|||||||
import { injectable, inject } from 'inversify';
|
|
||||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
|
||||||
import { ILogger } from '@theia/core/lib/common/logger';
|
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
|
||||||
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
|
||||||
import { CoreServiceClient } from '../common/protocol';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class CoreServiceClientImpl implements CoreServiceClient {
|
|
||||||
|
|
||||||
@inject(ILogger)
|
|
||||||
protected logger: ILogger;
|
|
||||||
|
|
||||||
@inject(MessageService)
|
|
||||||
protected messageService: MessageService;
|
|
||||||
|
|
||||||
@inject(LocalStorageService)
|
|
||||||
protected storageService: LocalStorageService;
|
|
||||||
|
|
||||||
protected readonly onIndexUpdatedEmitter = new Emitter<void>();
|
|
||||||
|
|
||||||
notifyIndexUpdated(): void {
|
|
||||||
this.info('Index has been updated.');
|
|
||||||
this.onIndexUpdatedEmitter.fire();
|
|
||||||
}
|
|
||||||
|
|
||||||
get onIndexUpdated(): Event<void> {
|
|
||||||
return this.onIndexUpdatedEmitter.event;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected info(message: string): void {
|
|
||||||
this.messageService.info(message, { timeout: 3000 });
|
|
||||||
this.logger.info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,10 +1,6 @@
|
|||||||
import { injectable, inject } from 'inversify';
|
import { injectable, inject } from 'inversify';
|
||||||
import { ApplicationShell, FrontendApplicationContribution, FrontendApplication, Widget } from '@theia/core/lib/browser';
|
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
|
||||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
|
||||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
|
||||||
import { MainMenuManager } from '../common/main-menu-manager';
|
import { MainMenuManager } from '../common/main-menu-manager';
|
||||||
import { BoardsListWidget } from './boards/boards-list-widget';
|
|
||||||
import { LibraryListWidget } from './library/library-list-widget';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class EditorMode implements FrontendApplicationContribution {
|
export class EditorMode implements FrontendApplicationContribution {
|
||||||
@@ -16,41 +12,6 @@ export class EditorMode implements FrontendApplicationContribution {
|
|||||||
|
|
||||||
onStart(app: FrontendApplication): void {
|
onStart(app: FrontendApplication): void {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
if (this.proMode) {
|
|
||||||
// We use this CSS class on the body to modify the visibility of the close button for the editors and views.
|
|
||||||
document.body.classList.add(EditorMode.PRO_MODE_KEY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get proMode(): boolean {
|
|
||||||
const value = window.localStorage.getItem(EditorMode.PRO_MODE_KEY);
|
|
||||||
return value === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
async toggleProMode(): Promise<void> {
|
|
||||||
const oldState = this.proMode;
|
|
||||||
const inAdvancedMode = !oldState;
|
|
||||||
window.localStorage.setItem(EditorMode.PRO_MODE_KEY, String(inAdvancedMode));
|
|
||||||
if (!inAdvancedMode) {
|
|
||||||
const { shell } = this.app;
|
|
||||||
// Close all widgets that are neither editor nor `Output` / `Boards Manager` / `Library Manager`.
|
|
||||||
for (const area of ['left', 'right', 'bottom', 'main'] as Array<ApplicationShell.Area>) {
|
|
||||||
shell.closeTabs(area, title => !this.isInSimpleMode(title.owner));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// `storeLayout` has a sync API but the implementation is async, we store the layout manually before we reload the page.
|
|
||||||
// See: https://github.com/eclipse-theia/theia/issues/6579
|
|
||||||
// XXX: hack instead of injecting the `ArduinoShellLayoutRestorer` we have to retrieve it from the application to avoid DI cycle.
|
|
||||||
const layoutRestorer = (this.app as any).layoutRestorer as { storeLayoutAsync(app: FrontendApplication): Promise<void> };
|
|
||||||
await layoutRestorer.storeLayoutAsync(this.app);
|
|
||||||
window.location.reload(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected isInSimpleMode(widget: Widget): boolean {
|
|
||||||
return widget instanceof EditorWidget
|
|
||||||
|| widget instanceof OutputWidget
|
|
||||||
|| widget instanceof BoardsListWidget
|
|
||||||
|| widget instanceof LibraryListWidget;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get compileForDebug(): boolean {
|
get compileForDebug(): boolean {
|
||||||
@@ -68,6 +29,5 @@ export class EditorMode implements FrontendApplicationContribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace EditorMode {
|
export namespace EditorMode {
|
||||||
export const PRO_MODE_KEY = 'arduino-advanced-mode';
|
|
||||||
export const COMPILE_FOR_DEBUG_KEY = 'arduino-compile-for-debug';
|
export const COMPILE_FOR_DEBUG_KEY = 'arduino-compile-for-debug';
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
10
arduino-ide-extension/src/browser/icons/monitor-tab-icon.svg
Normal file
10
arduino-ide-extension/src/browser/icons/monitor-tab-icon.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="24" height="24" viewBox="0, 0, 24, 24">
|
||||||
|
<g>
|
||||||
|
<path d="M16.2 8.1c0-4.5-3.6-8.1-8.1-8.1S0 3.6 0 8.1s3.6 8.1 8.1 8.1c1.9 0 3.7-.7 5.1-1.8l5.6 5.6 1.4-1.4-5.7-5.6c1.1-1.4 1.7-3.1 1.7-4.9zm-14.4 0c0-3.5 2.8-6.3 6.3-6.3s6.3 2.8 6.3 6.3-2.8 6.3-6.3 6.3c-3.5.1-6.3-2.8-6.3-6.3z" />
|
||||||
|
<rect x="7.1" y="7.1" width="2" height="2" />
|
||||||
|
<rect x="17.2" y="7.1" width="2" height="2" />
|
||||||
|
<rect x="20.3" y="7.1" width="2" height="2" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 711 B |
@@ -1,40 +0,0 @@
|
|||||||
import { injectable, inject, postConstruct } from 'inversify';
|
|
||||||
import { BaseLanguageClientContribution } from '@theia/languages/lib/browser';
|
|
||||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
|
||||||
import { BoardsConfig } from '../boards/boards-config';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ArduinoLanguageClientContribution extends BaseLanguageClientContribution {
|
|
||||||
|
|
||||||
readonly id = 'ino';
|
|
||||||
readonly name = 'Arduino';
|
|
||||||
|
|
||||||
protected get documentSelector(): string[] {
|
|
||||||
return ['ino'];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get globPatterns() {
|
|
||||||
return ['**/*.ino'];
|
|
||||||
}
|
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
|
||||||
|
|
||||||
protected boardConfig?: BoardsConfig.Config;
|
|
||||||
|
|
||||||
@postConstruct()
|
|
||||||
protected init() {
|
|
||||||
this.boardsServiceClient.onBoardsConfigChanged(this.selectBoard.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
selectBoard(config: BoardsConfig.Config): void {
|
|
||||||
this.boardConfig = config;
|
|
||||||
// Force a restart to send the new board config to the language server
|
|
||||||
this.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getStartParameters(): BoardsConfig.Config | undefined {
|
|
||||||
return this.boardConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,63 +0,0 @@
|
|||||||
import { injectable } from 'inversify';
|
|
||||||
import { LanguageGrammarDefinitionContribution, TextmateRegistry } from '@theia/monaco/lib/browser/textmate';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ArduinoLanguageGrammarContribution implements LanguageGrammarDefinitionContribution {
|
|
||||||
|
|
||||||
static INO_LANGUAGE_ID = 'ino';
|
|
||||||
|
|
||||||
registerTextmateLanguage(registry: TextmateRegistry) {
|
|
||||||
monaco.languages.register({
|
|
||||||
id: ArduinoLanguageGrammarContribution.INO_LANGUAGE_ID,
|
|
||||||
extensions: ['.ino'],
|
|
||||||
aliases: ['INO', 'Ino', 'ino'],
|
|
||||||
});
|
|
||||||
|
|
||||||
monaco.languages.setLanguageConfiguration(ArduinoLanguageGrammarContribution.INO_LANGUAGE_ID, this.configuration);
|
|
||||||
|
|
||||||
const inoGrammar = require('../../../data/ino.tmLanguage.json');
|
|
||||||
registry.registerTextmateGrammarScope('source.ino', {
|
|
||||||
async getGrammarDefinition() {
|
|
||||||
return {
|
|
||||||
format: 'json',
|
|
||||||
content: inoGrammar
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
registry.mapLanguageIdToTextmateGrammar(ArduinoLanguageGrammarContribution.INO_LANGUAGE_ID, 'source.ino');
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly configuration: monaco.languages.LanguageConfiguration = {
|
|
||||||
comments: {
|
|
||||||
lineComment: '//',
|
|
||||||
blockComment: ['/*', '*/'],
|
|
||||||
},
|
|
||||||
brackets: [
|
|
||||||
['{', '}'],
|
|
||||||
['[', ']'],
|
|
||||||
['(', ')']
|
|
||||||
],
|
|
||||||
autoClosingPairs: [
|
|
||||||
{ open: '[', close: ']' },
|
|
||||||
{ open: '{', close: '}' },
|
|
||||||
{ open: '(', close: ')' },
|
|
||||||
{ open: '\'', close: '\'', notIn: ['string', 'comment'] },
|
|
||||||
{ open: '"', close: '"', notIn: ['string'] },
|
|
||||||
{ open: '/*', close: ' */', notIn: ['string'] }
|
|
||||||
],
|
|
||||||
surroundingPairs: [
|
|
||||||
{ open: '{', close: '}' },
|
|
||||||
{ open: '[', close: ']' },
|
|
||||||
{ open: '(', close: ')' },
|
|
||||||
{ open: '"', close: '"' },
|
|
||||||
{ open: '\'', close: '\'' },
|
|
||||||
],
|
|
||||||
folding: {
|
|
||||||
markers: {
|
|
||||||
start: new RegExp('^\\s*#pragma\\s+region\\b'),
|
|
||||||
end: new RegExp('^\\s*#pragma\\s+endregion\\b')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@@ -1,17 +1,17 @@
|
|||||||
import { inject, injectable } from 'inversify';
|
import { injectable, postConstruct, inject } from 'inversify';
|
||||||
import { Library, LibraryService } from '../../common/protocol/library-service';
|
import { LibraryPackage, LibraryService } from '../../common/protocol/library-service';
|
||||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LibraryListWidget extends ListWidget<Library> {
|
export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||||
|
|
||||||
static WIDGET_ID = 'library-list-widget';
|
static WIDGET_ID = 'library-list-widget';
|
||||||
static WIDGET_LABEL = 'Library Manager';
|
static WIDGET_LABEL = 'Library Manager';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LibraryService) protected service: LibraryService,
|
@inject(LibraryService) protected service: LibraryService,
|
||||||
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<Library>) {
|
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<LibraryPackage>) {
|
||||||
|
|
||||||
super({
|
super({
|
||||||
id: LibraryListWidget.WIDGET_ID,
|
id: LibraryListWidget.WIDGET_ID,
|
||||||
@@ -19,9 +19,18 @@ export class LibraryListWidget extends ListWidget<Library> {
|
|||||||
iconClass: 'library-tab-icon',
|
iconClass: 'library-tab-icon',
|
||||||
searchable: service,
|
searchable: service,
|
||||||
installable: service,
|
installable: service,
|
||||||
itemLabel: (item: Library) => item.name,
|
itemLabel: (item: LibraryPackage) => item.name,
|
||||||
itemRenderer
|
itemRenderer
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
super.init();
|
||||||
|
this.toDispose.pushAll([
|
||||||
|
this.notificationCenter.onLibraryInstalled(() => this.refresh(undefined)),
|
||||||
|
this.notificationCenter.onLibraryUninstalled(() => this.refresh(undefined)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { MAIN_MENU_BAR } from '@theia/core/lib/common/menu';
|
import { isOSX } from '@theia/core/lib/common/os';
|
||||||
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||||
import { isOSX } from '@theia/core';
|
import { MAIN_MENU_BAR, MenuModelRegistry, MenuNode, MenuPath, SubMenuOptions } from '@theia/core/lib/common/menu';
|
||||||
|
|
||||||
export namespace ArduinoMenus {
|
export namespace ArduinoMenus {
|
||||||
|
|
||||||
@@ -12,6 +12,19 @@ export namespace ArduinoMenus {
|
|||||||
export const FILE__SETTINGS_GROUP = [...(isOSX ? MAIN_MENU_BAR : CommonMenus.FILE), '2_settings'];
|
export const FILE__SETTINGS_GROUP = [...(isOSX ? MAIN_MENU_BAR : CommonMenus.FILE), '2_settings'];
|
||||||
export const FILE__QUIT_GROUP = [...CommonMenus.FILE, '3_quit'];
|
export const FILE__QUIT_GROUP = [...CommonMenus.FILE, '3_quit'];
|
||||||
|
|
||||||
|
// -- File / Open Recent
|
||||||
|
export const FILE__OPEN_RECENT_SUBMENU = [...FILE__SKETCH_GROUP, '0_open_recent'];
|
||||||
|
|
||||||
|
// -- File / Sketchbook
|
||||||
|
export const FILE__SKETCHBOOK_SUBMENU = [...FILE__SKETCH_GROUP, '1_sketchbook'];
|
||||||
|
|
||||||
|
// -- File / Examples
|
||||||
|
export const FILE__EXAMPLES_SUBMENU = [...FILE__SKETCH_GROUP, '2_examples'];
|
||||||
|
export const EXAMPLES__BUILT_IN_GROUP = [...FILE__EXAMPLES_SUBMENU, '0_built_ins'];
|
||||||
|
export const EXAMPLES__ANY_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '1_any_board'];
|
||||||
|
export const EXAMPLES__CURRENT_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '2_current_board'];
|
||||||
|
export const EXAMPLES__USER_LIBS_GROUP = [...FILE__EXAMPLES_SUBMENU, '3_user_libs'];
|
||||||
|
|
||||||
// -- Edit
|
// -- Edit
|
||||||
// `Copy`, `Copy to Forum`, `Paste`, etc.
|
// `Copy`, `Copy to Forum`, `Paste`, etc.
|
||||||
// Note: `1_undo` is the first group from Theia, we start with `2`
|
// Note: `1_undo` is the first group from Theia, we start with `2`
|
||||||
@@ -30,10 +43,26 @@ export namespace ArduinoMenus {
|
|||||||
export const TOOLS = [...MAIN_MENU_BAR, '4_tools'];
|
export const TOOLS = [...MAIN_MENU_BAR, '4_tools'];
|
||||||
// `Auto Format`, `Library Manager...`, `Boards Manager...`
|
// `Auto Format`, `Library Manager...`, `Boards Manager...`
|
||||||
export const TOOLS__MAIN_GROUP = [...TOOLS, '0_main'];
|
export const TOOLS__MAIN_GROUP = [...TOOLS, '0_main'];
|
||||||
// Core settings, such as `Processor` and `Programmers` for the board.
|
// `Board`, `Port`, and `Get Board Info`.
|
||||||
export const TOOLS__BOARD_SETTINGS_GROUP = [...TOOLS, '1_board_settings'];
|
export const TOOLS__BOARD_SELECTION_GROUP = [...TOOLS, '2_board_selection'];
|
||||||
|
// Core settings, such as `Processor` and `Programmers` for the board and `Burn Bootloader`
|
||||||
|
export const TOOLS__BOARD_SETTINGS_GROUP = [...TOOLS, '3_board_settings'];
|
||||||
|
|
||||||
// Context menu
|
// -- Help
|
||||||
|
// `Getting Started`, `Environment`, `Troubleshooting`, etc.
|
||||||
|
export const HELP__MAIN_GROUP = [...CommonMenus.HELP, '0_main'];
|
||||||
|
// `Find in reference`, `FAQ`, etc.
|
||||||
|
export const HELP__FIND_GROUP = [...CommonMenus.HELP, '1_find'];
|
||||||
|
// `Advanced Mode`.
|
||||||
|
// XXX: this will be removed.
|
||||||
|
export const HELP__CONTROL_GROUP = [...CommonMenus.HELP, '2_control'];
|
||||||
|
// `About` group
|
||||||
|
// XXX: on macOS, the about group is not under `Help`
|
||||||
|
export const HELP__ABOUT_GROUP = [...(isOSX ? MAIN_MENU_BAR : CommonMenus.HELP), '999_about'];
|
||||||
|
|
||||||
|
// ------------
|
||||||
|
|
||||||
|
// Context menus
|
||||||
// -- Open
|
// -- Open
|
||||||
export const OPEN_SKETCH__CONTEXT = ['arduino-open-sketch--context'];
|
export const OPEN_SKETCH__CONTEXT = ['arduino-open-sketch--context'];
|
||||||
export const OPEN_SKETCH__CONTEXT__OPEN_GROUP = [...OPEN_SKETCH__CONTEXT, '0_open'];
|
export const OPEN_SKETCH__CONTEXT__OPEN_GROUP = [...OPEN_SKETCH__CONTEXT, '0_open'];
|
||||||
@@ -50,3 +79,44 @@ export namespace ArduinoMenus {
|
|||||||
export const SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP = [...SKETCH_CONTROL__CONTEXT, '2_resources'];
|
export const SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP = [...SKETCH_CONTROL__CONTEXT, '2_resources'];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a hack. It removes a submenu with all its children if any.
|
||||||
|
* Theia cannot dispose submenu entries with a proper API: https://github.com/eclipse-theia/theia/issues/7299
|
||||||
|
*/
|
||||||
|
export function unregisterSubmenu(menuPath: string[], menuRegistry: MenuModelRegistry): void {
|
||||||
|
if (menuPath.length < 2) {
|
||||||
|
throw new Error(`Expected at least two item as a menu-path. Got ${JSON.stringify(menuPath)} instead.`);
|
||||||
|
}
|
||||||
|
const toRemove = menuPath[menuPath.length - 1];
|
||||||
|
const parentMenuPath = menuPath.slice(0, menuPath.length - 1);
|
||||||
|
// This is unsafe. Calling `getMenu` with a non-existing menu-path will result in a new menu creation.
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/7300
|
||||||
|
const parent = menuRegistry.getMenu(parentMenuPath);
|
||||||
|
const index = parent.children.findIndex(({ id }) => id === toRemove);
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error(`Could not find menu with menu-path: ${JSON.stringify(menuPath)}.`);
|
||||||
|
}
|
||||||
|
(parent.children as Array<MenuNode>).splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special menu node that is not backed by any commands and is always disabled.
|
||||||
|
*/
|
||||||
|
export class PlaceholderMenuNode implements MenuNode {
|
||||||
|
|
||||||
|
constructor(protected readonly menuPath: MenuPath, readonly label: string, protected options: SubMenuOptions = { order: '0' }) { }
|
||||||
|
|
||||||
|
get icon(): string | undefined {
|
||||||
|
return this.options?.iconClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
get sortString(): string {
|
||||||
|
return this.options?.order || this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
get id(): string {
|
||||||
|
return [...this.menuPath, 'placeholder'].join('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@@ -4,11 +4,12 @@ import { Emitter, Event } from '@theia/core/lib/common/event';
|
|||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
import { MonitorService, MonitorConfig, MonitorError, Status, MonitorReadEvent } from '../../common/protocol/monitor-service';
|
import { MonitorService, MonitorConfig, MonitorError, Status, MonitorReadEvent } from '../../common/protocol/monitor-service';
|
||||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
import { Port, Board, BoardsService, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
import { Port, Board, BoardsService, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
||||||
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||||
import { BoardsConfig } from '../boards/boards-config';
|
import { BoardsConfig } from '../boards/boards-config';
|
||||||
import { MonitorModel } from './monitor-model';
|
import { MonitorModel } from './monitor-model';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class MonitorConnection {
|
export class MonitorConnection {
|
||||||
@@ -25,8 +26,11 @@ export class MonitorConnection {
|
|||||||
@inject(BoardsService)
|
@inject(BoardsService)
|
||||||
protected readonly boardsService: BoardsService;
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceProvider)
|
||||||
protected boardsServiceClient: BoardsServiceClientImpl;
|
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
@inject(MessageService)
|
@inject(MessageService)
|
||||||
protected messageService: MessageService;
|
protected messageService: MessageService;
|
||||||
@@ -110,11 +114,11 @@ export class MonitorConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.boardsServiceClient.onBoardsConfigChanged(this.handleBoardConfigChange.bind(this));
|
this.boardsServiceProvider.onBoardsConfigChanged(this.handleBoardConfigChange.bind(this));
|
||||||
this.boardsServiceClient.onAttachedBoardsChanged(event => {
|
this.notificationCenter.onAttachedBoardsChanged(event => {
|
||||||
if (this.autoConnect && this.connected) {
|
if (this.autoConnect && this.connected) {
|
||||||
const { boardsConfig } = this.boardsServiceClient;
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
|
if (this.boardsServiceProvider.canUploadTo(boardsConfig, { silent: false })) {
|
||||||
const { attached } = AttachedBoardsChangeEvent.diff(event);
|
const { attached } = AttachedBoardsChangeEvent.diff(event);
|
||||||
if (attached.boards.some(board => !!board.port && BoardsConfig.Config.sameAs(boardsConfig, board))) {
|
if (attached.boards.some(board => !!board.port && BoardsConfig.Config.sameAs(boardsConfig, board))) {
|
||||||
const { selectedBoard: board, selectedPort: port } = boardsConfig;
|
const { selectedBoard: board, selectedPort: port } = boardsConfig;
|
||||||
@@ -128,7 +132,7 @@ export class MonitorConnection {
|
|||||||
// Handles the `baudRate` changes by reconnecting if required.
|
// Handles the `baudRate` changes by reconnecting if required.
|
||||||
this.monitorModel.onChange(({ property }) => {
|
this.monitorModel.onChange(({ property }) => {
|
||||||
if (property === 'baudRate' && this.autoConnect && this.connected) {
|
if (property === 'baudRate' && this.autoConnect && this.connected) {
|
||||||
const { boardsConfig } = this.boardsServiceClient;
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
this.handleBoardConfigChange(boardsConfig);
|
this.handleBoardConfigChange(boardsConfig);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -154,7 +158,7 @@ export class MonitorConnection {
|
|||||||
// We have to make sure the previous boards config has been restored.
|
// We have to make sure the previous boards config has been restored.
|
||||||
// Otherwise, we might start the auto-connection without configured boards.
|
// Otherwise, we might start the auto-connection without configured boards.
|
||||||
this.applicationState.reachedState('started_contributions').then(() => {
|
this.applicationState.reachedState('started_contributions').then(() => {
|
||||||
const { boardsConfig } = this.boardsServiceClient;
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
this.handleBoardConfigChange(boardsConfig);
|
this.handleBoardConfigChange(boardsConfig);
|
||||||
});
|
});
|
||||||
} else if (oldValue && !value) {
|
} else if (oldValue && !value) {
|
||||||
@@ -227,7 +231,7 @@ export class MonitorConnection {
|
|||||||
|
|
||||||
protected async handleBoardConfigChange(boardsConfig: BoardsConfig.Config): Promise<void> {
|
protected async handleBoardConfigChange(boardsConfig: BoardsConfig.Config): Promise<void> {
|
||||||
if (this.autoConnect) {
|
if (this.autoConnect) {
|
||||||
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
|
if (this.boardsServiceProvider.canUploadTo(boardsConfig, { silent: false })) {
|
||||||
// Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports.
|
// Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports.
|
||||||
// The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881
|
// The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881
|
||||||
this.boardsService.getAvailablePorts().then(ports => {
|
this.boardsService.getAvailablePorts().then(ports => {
|
||||||
|
@@ -2,7 +2,7 @@ import { injectable, inject } from 'inversify';
|
|||||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||||
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
||||||
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
||||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class MonitorModel implements FrontendApplicationContribution {
|
export class MonitorModel implements FrontendApplicationContribution {
|
||||||
@@ -12,8 +12,8 @@ export class MonitorModel implements FrontendApplicationContribution {
|
|||||||
@inject(LocalStorageService)
|
@inject(LocalStorageService)
|
||||||
protected readonly localStorageService: LocalStorageService;
|
protected readonly localStorageService: LocalStorageService;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||||
|
|
||||||
protected readonly onChangeEmitter: Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>;
|
protected readonly onChangeEmitter: Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>;
|
||||||
protected _autoscroll: boolean;
|
protected _autoscroll: boolean;
|
||||||
|
@@ -5,7 +5,7 @@ import { OptionsType } from 'react-select/src/types';
|
|||||||
import { isOSX } from '@theia/core/lib/common/os';
|
import { isOSX } from '@theia/core/lib/common/os';
|
||||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||||
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
|
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
|
||||||
import { DisposableCollection } from '@theia/core/lib/common/disposable'
|
import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable'
|
||||||
import { ReactWidget, Message, Widget, MessageLoop } from '@theia/core/lib/browser/widgets';
|
import { ReactWidget, Message, Widget, MessageLoop } from '@theia/core/lib/browser/widgets';
|
||||||
import { Board, Port } from '../../common/protocol/boards-service';
|
import { Board, Port } from '../../common/protocol/boards-service';
|
||||||
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
||||||
@@ -45,10 +45,16 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
super();
|
super();
|
||||||
this.id = MonitorWidget.ID;
|
this.id = MonitorWidget.ID;
|
||||||
this.title.label = 'Serial Monitor';
|
this.title.label = 'Serial Monitor';
|
||||||
this.title.iconClass = 'arduino-serial-monitor-tab-icon';
|
this.title.iconClass = 'monitor-tab-icon';
|
||||||
this.title.closable = true;
|
this.title.closable = true;
|
||||||
this.scrollOptions = undefined;
|
this.scrollOptions = undefined;
|
||||||
this.toDispose.push(this.clearOutputEmitter);
|
this.toDispose.push(this.clearOutputEmitter);
|
||||||
|
this.toDispose.push(Disposable.create(() => {
|
||||||
|
this.monitorConnection.autoConnect = false;
|
||||||
|
if (this.monitorConnection.connected) {
|
||||||
|
this.monitorConnection.disconnect();
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
@@ -73,10 +79,6 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
|
|
||||||
onCloseRequest(msg: Message): void {
|
onCloseRequest(msg: Message): void {
|
||||||
this.closing = true;
|
this.closing = true;
|
||||||
this.monitorConnection.autoConnect = false;
|
|
||||||
if (this.monitorConnection.connected) {
|
|
||||||
this.monitorConnection.disconnect();
|
|
||||||
}
|
|
||||||
super.onCloseRequest(msg);
|
super.onCloseRequest(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +102,9 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
||||||
|
if (this.closing || !this.isAttached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.focusNode = element;
|
this.focusNode = element;
|
||||||
requestAnimationFrame(() => MessageLoop.sendMessage(this, Widget.Msg.ActivateRequest));
|
requestAnimationFrame(() => MessageLoop.sendMessage(this, Widget.Msg.ActivateRequest));
|
||||||
}
|
}
|
||||||
|
98
arduino-ide-extension/src/browser/notification-center.ts
Normal file
98
arduino-ide-extension/src/browser/notification-center.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { inject, injectable, postConstruct } from 'inversify';
|
||||||
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
|
import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||||
|
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { NotificationServiceClient, NotificationServiceServer } from '../common/protocol/notification-service';
|
||||||
|
import { AttachedBoardsChangeEvent, BoardsPackage, LibraryPackage, Config, Sketch } from '../common/protocol';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class NotificationCenter implements NotificationServiceClient, FrontendApplicationContribution {
|
||||||
|
|
||||||
|
@inject(NotificationServiceServer)
|
||||||
|
protected readonly server: JsonRpcProxy<NotificationServiceServer>;
|
||||||
|
|
||||||
|
protected readonly indexUpdatedEmitter = new Emitter<void>();
|
||||||
|
protected readonly daemonStartedEmitter = new Emitter<void>();
|
||||||
|
protected readonly daemonStoppedEmitter = new Emitter<void>();
|
||||||
|
protected readonly configChangedEmitter = new Emitter<{ config: Config | undefined }>();
|
||||||
|
protected readonly platformInstalledEmitter = new Emitter<{ item: BoardsPackage }>();
|
||||||
|
protected readonly platformUninstalledEmitter = new Emitter<{ item: BoardsPackage }>();
|
||||||
|
protected readonly libraryInstalledEmitter = new Emitter<{ item: LibraryPackage }>();
|
||||||
|
protected readonly libraryUninstalledEmitter = new Emitter<{ item: LibraryPackage }>();
|
||||||
|
protected readonly attachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
|
||||||
|
protected readonly recentSketchesChangedEmitter = new Emitter<{ sketches: Sketch[] }>();
|
||||||
|
|
||||||
|
protected readonly toDispose = new DisposableCollection(
|
||||||
|
this.indexUpdatedEmitter,
|
||||||
|
this.daemonStartedEmitter,
|
||||||
|
this.daemonStoppedEmitter,
|
||||||
|
this.configChangedEmitter,
|
||||||
|
this.platformInstalledEmitter,
|
||||||
|
this.platformUninstalledEmitter,
|
||||||
|
this.libraryInstalledEmitter,
|
||||||
|
this.libraryUninstalledEmitter,
|
||||||
|
this.attachedBoardsChangedEmitter
|
||||||
|
);
|
||||||
|
|
||||||
|
readonly onIndexUpdated = this.indexUpdatedEmitter.event;
|
||||||
|
readonly onDaemonStarted = this.daemonStartedEmitter.event;
|
||||||
|
readonly onDaemonStopped = this.daemonStoppedEmitter.event;
|
||||||
|
readonly onConfigChanged = this.configChangedEmitter.event;
|
||||||
|
readonly onPlatformInstalled = this.platformInstalledEmitter.event;
|
||||||
|
readonly onPlatformUninstalled = this.platformUninstalledEmitter.event;
|
||||||
|
readonly onLibraryInstalled = this.libraryInstalledEmitter.event;
|
||||||
|
readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event;
|
||||||
|
readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event;
|
||||||
|
readonly onRecentSketchesChanged = this.recentSketchesChangedEmitter.event;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
this.server.setClient(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStop(): void {
|
||||||
|
this.toDispose.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyIndexUpdated(): void {
|
||||||
|
this.indexUpdatedEmitter.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDaemonStarted(): void {
|
||||||
|
this.daemonStartedEmitter.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDaemonStopped(): void {
|
||||||
|
this.daemonStoppedEmitter.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyConfigChanged(event: { config: Config | undefined }): void {
|
||||||
|
this.configChangedEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyPlatformInstalled(event: { item: BoardsPackage }): void {
|
||||||
|
this.platformInstalledEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyPlatformUninstalled(event: { item: BoardsPackage }): void {
|
||||||
|
this.platformUninstalledEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyLibraryInstalled(event: { item: LibraryPackage }): void {
|
||||||
|
this.libraryInstalledEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyLibraryUninstalled(event: { item: LibraryPackage }): void {
|
||||||
|
this.libraryUninstalledEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
|
||||||
|
this.attachedBoardsChangedEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void {
|
||||||
|
this.recentSketchesChangedEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
arduino-ide-extension/src/browser/output-service-impl.ts
Normal file
22
arduino-ide-extension/src/browser/output-service-impl.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
|
||||||
|
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||||
|
import { OutputService, OutputMessage } from '../common/protocol/output-service';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class OutputServiceImpl implements OutputService {
|
||||||
|
|
||||||
|
@inject(OutputContribution)
|
||||||
|
protected outputContribution: OutputContribution;
|
||||||
|
|
||||||
|
@inject(OutputChannelManager)
|
||||||
|
protected outputChannelManager: OutputChannelManager;
|
||||||
|
|
||||||
|
append(message: OutputMessage): void {
|
||||||
|
const { chunk } = message;
|
||||||
|
const channel = this.outputChannelManager.getChannel(`Arduino`);
|
||||||
|
channel.show({ preserveFocus: true });
|
||||||
|
channel.append(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
791
arduino-ide-extension/src/browser/settings.tsx
Normal file
791
arduino-ide-extension/src/browser/settings.tsx
Normal file
@@ -0,0 +1,791 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { injectable, inject, postConstruct } from 'inversify';
|
||||||
|
import { Widget } from '@phosphor/widgets';
|
||||||
|
import { Message } from '@phosphor/messaging';
|
||||||
|
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||||
|
import 'react-tabs/style/react-tabs.css';
|
||||||
|
import { Disable } from 'react-disable';
|
||||||
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||||
|
import { deepClone } from '@theia/core/lib/common/objects';
|
||||||
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||||
|
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||||
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
|
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||||
|
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog/file-dialog-service';
|
||||||
|
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
|
import { AbstractDialog, DialogProps, PreferenceService, PreferenceScope, DialogError, ReactWidget } from '@theia/core/lib/browser';
|
||||||
|
import { Index } from '../common/types';
|
||||||
|
import { ConfigService, FileSystemExt, Network, ProxySettings } from '../common/protocol';
|
||||||
|
|
||||||
|
export interface Settings extends Index {
|
||||||
|
editorFontSize: number; // `editor.fontSize`
|
||||||
|
themeId: string; // `workbench.colorTheme`
|
||||||
|
autoSave: 'on' | 'off'; // `editor.autoSave`
|
||||||
|
|
||||||
|
autoScaleInterface: boolean; // `arduino.window.autoScale`
|
||||||
|
interfaceScale: number; // `arduino.window.zoomLevel` https://github.com/eclipse-theia/theia/issues/8751
|
||||||
|
checkForUpdates?: boolean; // `arduino.ide.autoUpdate`
|
||||||
|
verboseOnCompile: boolean; // `arduino.compile.verbose`
|
||||||
|
verboseOnUpload: boolean; // `arduino.upload.verbose`
|
||||||
|
verifyAfterUpload: boolean; // `arduino.upload.verify`
|
||||||
|
enableLsLogs: boolean; // `arduino.language.log`
|
||||||
|
|
||||||
|
sketchbookPath: string; // CLI
|
||||||
|
additionalUrls: string[]; // CLI
|
||||||
|
network: Network; // CLI
|
||||||
|
}
|
||||||
|
export namespace Settings {
|
||||||
|
|
||||||
|
export function belongsToCli<K extends keyof Settings>(key: K): boolean {
|
||||||
|
return key === 'sketchbookPath' || key === 'additionalUrls';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SettingsService {
|
||||||
|
|
||||||
|
@inject(FileService)
|
||||||
|
protected readonly fileService: FileService;
|
||||||
|
|
||||||
|
@inject(FileSystemExt)
|
||||||
|
protected readonly fileSystemExt: FileSystemExt;
|
||||||
|
|
||||||
|
@inject(ConfigService)
|
||||||
|
protected readonly configService: ConfigService;
|
||||||
|
|
||||||
|
@inject(PreferenceService)
|
||||||
|
protected readonly preferenceService: PreferenceService;
|
||||||
|
|
||||||
|
@inject(FrontendApplicationStateService)
|
||||||
|
protected readonly appStateService: FrontendApplicationStateService;
|
||||||
|
|
||||||
|
protected readonly onDidChangeEmitter = new Emitter<Readonly<Settings>>();
|
||||||
|
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||||
|
|
||||||
|
protected ready = new Deferred<void>();
|
||||||
|
protected _settings: Settings;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected async init(): Promise<void> {
|
||||||
|
await this.appStateService.reachedState('ready'); // Hack for https://github.com/eclipse-theia/theia/issues/8993
|
||||||
|
const settings = await this.loadSettings();
|
||||||
|
this._settings = deepClone(settings);
|
||||||
|
this.ready.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async loadSettings(): Promise<Settings> {
|
||||||
|
await this.preferenceService.ready;
|
||||||
|
const [
|
||||||
|
editorFontSize,
|
||||||
|
themeId,
|
||||||
|
autoSave,
|
||||||
|
autoScaleInterface,
|
||||||
|
interfaceScale,
|
||||||
|
// checkForUpdates,
|
||||||
|
verboseOnCompile,
|
||||||
|
verboseOnUpload,
|
||||||
|
verifyAfterUpload,
|
||||||
|
enableLsLogs,
|
||||||
|
cliConfig
|
||||||
|
] = await Promise.all([
|
||||||
|
this.preferenceService.get<number>('editor.fontSize', 12),
|
||||||
|
this.preferenceService.get<string>('workbench.colorTheme', 'arduino-theme'),
|
||||||
|
this.preferenceService.get<'on' | 'off'>('editor.autoSave', 'on'),
|
||||||
|
this.preferenceService.get<boolean>('arduino.window.autoScale', true),
|
||||||
|
this.preferenceService.get<number>('arduino.window.zoomLevel', 0),
|
||||||
|
// this.preferenceService.get<string>('arduino.ide.autoUpdate', true),
|
||||||
|
this.preferenceService.get<boolean>('arduino.compile.verbose', true),
|
||||||
|
this.preferenceService.get<boolean>('arduino.upload.verbose', true),
|
||||||
|
this.preferenceService.get<boolean>('arduino.upload.verify', true),
|
||||||
|
this.preferenceService.get<boolean>('arduino.language.log', true),
|
||||||
|
this.configService.getConfiguration()
|
||||||
|
]);
|
||||||
|
const { additionalUrls, sketchDirUri, network } = cliConfig;
|
||||||
|
const sketchbookPath = await this.fileService.fsPath(new URI(sketchDirUri));
|
||||||
|
return {
|
||||||
|
editorFontSize,
|
||||||
|
themeId,
|
||||||
|
autoSave,
|
||||||
|
autoScaleInterface,
|
||||||
|
interfaceScale,
|
||||||
|
// checkForUpdates,
|
||||||
|
verboseOnCompile,
|
||||||
|
verboseOnUpload,
|
||||||
|
verifyAfterUpload,
|
||||||
|
enableLsLogs,
|
||||||
|
additionalUrls,
|
||||||
|
sketchbookPath,
|
||||||
|
network
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async settings(): Promise<Settings> {
|
||||||
|
await this.ready.promise;
|
||||||
|
return this._settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(settings: Settings, fireDidChange: boolean = false): Promise<void> {
|
||||||
|
await this.ready.promise;
|
||||||
|
for (const key of Object.keys(settings)) {
|
||||||
|
this._settings[key] = settings[key];
|
||||||
|
}
|
||||||
|
if (fireDidChange) {
|
||||||
|
this.onDidChangeEmitter.fire(this._settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reset(): Promise<void> {
|
||||||
|
const settings = await this.loadSettings();
|
||||||
|
return this.update(settings, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(settings: MaybePromise<Settings> = this.settings()): Promise<string | true> {
|
||||||
|
try {
|
||||||
|
const { sketchbookPath, editorFontSize, themeId } = await settings;
|
||||||
|
const sketchbookDir = await this.fileSystemExt.getUri(sketchbookPath);
|
||||||
|
if (!await this.fileService.exists(new URI(sketchbookDir))) {
|
||||||
|
return `Invalid sketchbook location: ${sketchbookPath}`;
|
||||||
|
}
|
||||||
|
if (editorFontSize <= 0) {
|
||||||
|
return `Invalid editor font size. It must be a positive integer.`;
|
||||||
|
}
|
||||||
|
if (!ThemeService.get().getThemes().find(({ id }) => id === themeId)) {
|
||||||
|
return `Invalid theme.`;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
return String(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(): Promise<string | true> {
|
||||||
|
await this.ready.promise;
|
||||||
|
const {
|
||||||
|
editorFontSize,
|
||||||
|
themeId,
|
||||||
|
autoSave,
|
||||||
|
autoScaleInterface,
|
||||||
|
interfaceScale,
|
||||||
|
// checkForUpdates,
|
||||||
|
verboseOnCompile,
|
||||||
|
verboseOnUpload,
|
||||||
|
verifyAfterUpload,
|
||||||
|
enableLsLogs,
|
||||||
|
sketchbookPath,
|
||||||
|
additionalUrls,
|
||||||
|
network
|
||||||
|
} = this._settings;
|
||||||
|
const [config, sketchDirUri] = await Promise.all([
|
||||||
|
this.configService.getConfiguration(),
|
||||||
|
this.fileSystemExt.getUri(sketchbookPath)
|
||||||
|
]);
|
||||||
|
(config as any).additionalUrls = additionalUrls;
|
||||||
|
(config as any).sketchDirUri = sketchDirUri;
|
||||||
|
(config as any).network = network;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.preferenceService.set('editor.fontSize', editorFontSize, PreferenceScope.User),
|
||||||
|
this.preferenceService.set('workbench.colorTheme', themeId, PreferenceScope.User),
|
||||||
|
this.preferenceService.set('editor.autoSave', autoSave, PreferenceScope.User),
|
||||||
|
this.preferenceService.set('arduino.window.autoScale', autoScaleInterface, PreferenceScope.User),
|
||||||
|
this.preferenceService.set('arduino.window.zoomLevel', interfaceScale, PreferenceScope.User),
|
||||||
|
// this.preferenceService.set('arduino.ide.autoUpdate', checkForUpdates, PreferenceScope.User),
|
||||||
|
this.preferenceService.set('arduino.compile.verbose', verboseOnCompile, PreferenceScope.User),
|
||||||
|
this.preferenceService.set('arduino.upload.verbose', verboseOnUpload, PreferenceScope.User),
|
||||||
|
this.preferenceService.set('arduino.upload.verify', verifyAfterUpload, PreferenceScope.User),
|
||||||
|
this.preferenceService.set('arduino.language.log', enableLsLogs, PreferenceScope.User),
|
||||||
|
this.configService.setConfiguration(config)
|
||||||
|
]);
|
||||||
|
this.onDidChangeEmitter.fire(this._settings);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SettingsComponent extends React.Component<SettingsComponent.Props, SettingsComponent.State> {
|
||||||
|
|
||||||
|
readonly toDispose = new DisposableCollection();
|
||||||
|
|
||||||
|
constructor(props: SettingsComponent.Props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(_: SettingsComponent.Props, prevState: SettingsComponent.State): void {
|
||||||
|
if (this.state && prevState && JSON.stringify(this.state) !== JSON.stringify(prevState)) {
|
||||||
|
this.props.settingsService.update(this.state, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.props.settingsService.settings().then(settings => this.setState(settings));
|
||||||
|
this.toDispose.push(this.props.settingsService.onDidChange(settings => this.setState(settings)));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
this.toDispose.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
if (!this.state) {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
return <Tabs>
|
||||||
|
<TabList>
|
||||||
|
<Tab>Settings</Tab>
|
||||||
|
<Tab>Network</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel>
|
||||||
|
{this.renderSettings()}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
{this.renderNetwork()}
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderSettings(): React.ReactNode {
|
||||||
|
return <div className='content noselect'>
|
||||||
|
Sketchbook location:
|
||||||
|
<div className='flex-line'>
|
||||||
|
<input
|
||||||
|
className='theia-input stretch'
|
||||||
|
type='text'
|
||||||
|
value={this.state.sketchbookPath}
|
||||||
|
onChange={this.sketchpathDidChange} />
|
||||||
|
<button className='theia-button shrink' onClick={this.browseSketchbookDidClick}>Browse</button>
|
||||||
|
</div>
|
||||||
|
<div className='flex-line'>
|
||||||
|
<div className='column'>
|
||||||
|
<div className='flex-line'>Editor font size:</div>
|
||||||
|
<div className='flex-line'>Interface scale:</div>
|
||||||
|
<div className='flex-line'>Theme:</div>
|
||||||
|
<div className='flex-line'>Show verbose output during:</div>
|
||||||
|
</div>
|
||||||
|
<div className='column'>
|
||||||
|
<div className='flex-line'>
|
||||||
|
<input
|
||||||
|
className='theia-input small'
|
||||||
|
type='number'
|
||||||
|
step={1}
|
||||||
|
pattern='[0-9]+'
|
||||||
|
onKeyDown={this.numbersOnlyKeyDown}
|
||||||
|
value={this.state.editorFontSize}
|
||||||
|
onChange={this.editorFontSizeDidChange} />
|
||||||
|
</div>
|
||||||
|
<div className='flex-line'>
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.autoScaleInterface}
|
||||||
|
onChange={this.autoScaleInterfaceDidChange} />
|
||||||
|
Automatic
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className='theia-input small with-margin'
|
||||||
|
type='number'
|
||||||
|
step={20}
|
||||||
|
pattern='[0-9]+'
|
||||||
|
onKeyDown={this.noopKeyDown}
|
||||||
|
value={100 + this.state.interfaceScale * 20}
|
||||||
|
onChange={this.interfaceScaleDidChange} />
|
||||||
|
%
|
||||||
|
</div>
|
||||||
|
<div className='flex-line'>
|
||||||
|
<select
|
||||||
|
className='theia-select'
|
||||||
|
value={ThemeService.get().getThemes().find(({ id }) => id === this.state.themeId)?.label || 'Unknown'}
|
||||||
|
onChange={this.themeDidChange}>
|
||||||
|
{ThemeService.get().getThemes().map(({ id, label }) => <option key={id} value={label}>{label}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className='flex-line'>
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.verboseOnCompile}
|
||||||
|
onChange={this.verboseOnCompileDidChange} />
|
||||||
|
compile
|
||||||
|
</label>
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.verboseOnUpload}
|
||||||
|
onChange={this.verboseOnUploadDidChange} />
|
||||||
|
upload
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.verifyAfterUpload}
|
||||||
|
onChange={this.verifyAfterUploadDidChange} />
|
||||||
|
Verify code after upload
|
||||||
|
</label>
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.checkForUpdates}
|
||||||
|
onChange={this.checkForUpdatesDidChange}
|
||||||
|
disabled={true} />
|
||||||
|
Check for updates on startup
|
||||||
|
</label>
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.autoSave === 'on'}
|
||||||
|
onChange={this.autoSaveDidChange} />
|
||||||
|
Auto save
|
||||||
|
</label>
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.state.enableLsLogs}
|
||||||
|
onChange={this.enableLsLogsDidChange} />
|
||||||
|
Enable language server logging
|
||||||
|
</label>
|
||||||
|
<div className='flex-line'>
|
||||||
|
Additional boards manager URLs:
|
||||||
|
<input
|
||||||
|
className='theia-input stretch with-margin'
|
||||||
|
type='text'
|
||||||
|
value={this.state.additionalUrls.join(',')}
|
||||||
|
onChange={this.additionalUrlsDidChange} />
|
||||||
|
<i className='fa fa-window-restore theia-button shrink' onClick={this.editAdditionalUrlDidClick} />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderNetwork(): React.ReactNode {
|
||||||
|
return <div className='content noselect'>
|
||||||
|
<form>
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
checked={this.state.network === 'none'}
|
||||||
|
onChange={this.noProxyDidChange} />
|
||||||
|
No proxy
|
||||||
|
</label>
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
checked={this.state.network !== 'none'}
|
||||||
|
onChange={this.manualProxyDidChange} />
|
||||||
|
Manual proxy configuration
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
{this.renderProxySettings()}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderProxySettings(): React.ReactNode {
|
||||||
|
const disabled = this.state.network === 'none';
|
||||||
|
return <Disable disabled={disabled}>
|
||||||
|
<div className='proxy-settings' aria-disabled={disabled}>
|
||||||
|
<form className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
checked={this.state.network === 'none' ? true : this.state.network.protocol === 'http'}
|
||||||
|
onChange={this.httpProtocolDidChange} />
|
||||||
|
HTTP
|
||||||
|
<label className='flex-line'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
checked={this.state.network === 'none' ? false : this.state.network.protocol !== 'http'}
|
||||||
|
onChange={this.socksProtocolDidChange} />
|
||||||
|
SOCKS
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
<div className='flex-line proxy-settings'>
|
||||||
|
<div className='column'>
|
||||||
|
<div className='flex-line'>Host name:</div>
|
||||||
|
<div className='flex-line'>Port number:</div>
|
||||||
|
<div className='flex-line'>Username:</div>
|
||||||
|
<div className='flex-line'>Password:</div>
|
||||||
|
</div>
|
||||||
|
<div className='column stretch'>
|
||||||
|
<div className='flex-line'>
|
||||||
|
<input
|
||||||
|
className='theia-input stretch with-margin'
|
||||||
|
type='text'
|
||||||
|
value={this.state.network === 'none' ? '' : this.state.network.hostname}
|
||||||
|
onChange={this.hostnameDidChange} />
|
||||||
|
</div>
|
||||||
|
<div className='flex-line'>
|
||||||
|
<input
|
||||||
|
className='theia-input small with-margin'
|
||||||
|
type='number'
|
||||||
|
pattern='[0-9]'
|
||||||
|
value={this.state.network === 'none' ? '' : this.state.network.port}
|
||||||
|
onKeyDown={this.numbersOnlyKeyDown}
|
||||||
|
onChange={this.portDidChange} />
|
||||||
|
</div>
|
||||||
|
<div className='flex-line'>
|
||||||
|
<input
|
||||||
|
className='theia-input stretch with-margin'
|
||||||
|
type='text'
|
||||||
|
value={this.state.network === 'none' ? '' : this.state.network.username}
|
||||||
|
onChange={this.usernameDidChange} />
|
||||||
|
</div>
|
||||||
|
<div className='flex-line'>
|
||||||
|
<input
|
||||||
|
className='theia-input stretch with-margin'
|
||||||
|
type='password'
|
||||||
|
value={this.state.network === 'none' ? '' : this.state.network.password}
|
||||||
|
onChange={this.passwordDidChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Disable>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isControlKey(event: React.KeyboardEvent<HTMLInputElement>): boolean {
|
||||||
|
return !!event.key && ['tab', 'delete', 'backspace', 'arrowleft', 'arrowright'].some(key => event.key.toLocaleLowerCase() === key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected noopKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (this.isControlKey(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.nativeEvent.preventDefault();
|
||||||
|
event.nativeEvent.returnValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected numbersOnlyKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (this.isControlKey(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const key = Number(event.key)
|
||||||
|
if (isNaN(key) || event.key === null || event.key === ' ') {
|
||||||
|
event.nativeEvent.preventDefault();
|
||||||
|
event.nativeEvent.returnValue = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected browseSketchbookDidClick = async () => {
|
||||||
|
const uri = await this.props.fileDialogService.showOpenDialog({
|
||||||
|
title: 'Select new sketchbook location',
|
||||||
|
openLabel: 'Chose',
|
||||||
|
canSelectFiles: false,
|
||||||
|
canSelectMany: false,
|
||||||
|
canSelectFolders: true
|
||||||
|
});
|
||||||
|
if (uri) {
|
||||||
|
const sketchbookPath = await this.props.fileService.fsPath(uri);
|
||||||
|
this.setState({ sketchbookPath });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected editAdditionalUrlDidClick = async () => {
|
||||||
|
const additionalUrls = await new AdditionalUrlsDialog(this.state.additionalUrls, this.props.windowService).open();
|
||||||
|
if (additionalUrls) {
|
||||||
|
this.setState({ additionalUrls });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected editorFontSizeDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
if (value) {
|
||||||
|
this.setState({ editorFontSize: parseInt(value, 10) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected additionalUrlsDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ additionalUrls: event.target.value.split(',').map(url => url.trim()) });
|
||||||
|
};
|
||||||
|
|
||||||
|
protected autoScaleInterfaceDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ autoScaleInterface: event.target.checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
protected enableLsLogsDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ enableLsLogs: event.target.checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
protected interfaceScaleDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
const percentage = parseInt(value, 10);
|
||||||
|
if (isNaN(percentage)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let interfaceScale = (percentage - 100) / 20;
|
||||||
|
if (!isNaN(interfaceScale)) {
|
||||||
|
this.setState({ interfaceScale });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected verifyAfterUploadDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ verifyAfterUpload: event.target.checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
protected checkForUpdatesDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ checkForUpdates: event.target.checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
protected autoSaveDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ autoSave: event.target.checked ? 'on' : 'off' });
|
||||||
|
};
|
||||||
|
|
||||||
|
protected themeDidChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const { selectedIndex } = event.target.options;
|
||||||
|
const theme = ThemeService.get().getThemes()[selectedIndex];
|
||||||
|
if (theme) {
|
||||||
|
this.setState({ themeId: theme.id });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected verboseOnCompileDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ verboseOnCompile: event.target.checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
protected verboseOnUploadDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ verboseOnUpload: event.target.checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
protected sketchpathDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const sketchbookPath = event.target.value;
|
||||||
|
if (sketchbookPath) {
|
||||||
|
this.setState({ sketchbookPath });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected noProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
this.setState({ network: 'none' });
|
||||||
|
} else {
|
||||||
|
this.setState({ network: Network.Default() });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected manualProxyDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
this.setState({ network: Network.Default() });
|
||||||
|
} else {
|
||||||
|
this.setState({ network: 'none' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected httpProtocolDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (this.state.network !== 'none') {
|
||||||
|
const network = this.cloneProxySettings;
|
||||||
|
network.protocol = event.target.checked ? 'http' : 'socks';
|
||||||
|
this.setState({ network });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected socksProtocolDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (this.state.network !== 'none') {
|
||||||
|
const network = this.cloneProxySettings;
|
||||||
|
network.protocol = event.target.checked ? 'socks' : 'http';
|
||||||
|
this.setState({ network });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected hostnameDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (this.state.network !== 'none') {
|
||||||
|
const network = this.cloneProxySettings;
|
||||||
|
network.hostname = event.target.value;
|
||||||
|
this.setState({ network });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected portDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (this.state.network !== 'none') {
|
||||||
|
const network = this.cloneProxySettings;
|
||||||
|
network.port = event.target.value;
|
||||||
|
this.setState({ network });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected usernameDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (this.state.network !== 'none') {
|
||||||
|
const network = this.cloneProxySettings;
|
||||||
|
network.username = event.target.value;
|
||||||
|
this.setState({ network });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected passwordDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (this.state.network !== 'none') {
|
||||||
|
const network = this.cloneProxySettings;
|
||||||
|
network.password = event.target.value;
|
||||||
|
this.setState({ network });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private get cloneProxySettings(): ProxySettings {
|
||||||
|
const { network } = this.state;
|
||||||
|
if (network === 'none') {
|
||||||
|
throw new Error('Must be called when proxy is enabled.');
|
||||||
|
}
|
||||||
|
const copyNetwork = deepClone(network);
|
||||||
|
return copyNetwork;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export namespace SettingsComponent {
|
||||||
|
export interface Props {
|
||||||
|
readonly settingsService: SettingsService;
|
||||||
|
readonly fileService: FileService;
|
||||||
|
readonly fileDialogService: FileDialogService;
|
||||||
|
readonly windowService: WindowService;
|
||||||
|
}
|
||||||
|
export interface State extends Settings { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SettingsWidget extends ReactWidget {
|
||||||
|
|
||||||
|
@inject(SettingsService)
|
||||||
|
protected readonly settingsService: SettingsService;
|
||||||
|
|
||||||
|
@inject(FileService)
|
||||||
|
protected readonly fileService: FileService;
|
||||||
|
|
||||||
|
@inject(FileDialogService)
|
||||||
|
protected readonly fileDialogService: FileDialogService;
|
||||||
|
|
||||||
|
@inject(WindowService)
|
||||||
|
protected readonly windowService: WindowService;
|
||||||
|
|
||||||
|
protected render(): React.ReactNode {
|
||||||
|
return <SettingsComponent
|
||||||
|
settingsService={this.settingsService}
|
||||||
|
fileService={this.fileService}
|
||||||
|
fileDialogService={this.fileDialogService}
|
||||||
|
windowService={this.windowService} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SettingsDialogProps extends DialogProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
|
||||||
|
|
||||||
|
@inject(SettingsService)
|
||||||
|
protected readonly settingsService: SettingsService;
|
||||||
|
|
||||||
|
@inject(SettingsWidget)
|
||||||
|
protected readonly widget: SettingsWidget;
|
||||||
|
|
||||||
|
constructor(@inject(SettingsDialogProps) protected readonly props: SettingsDialogProps) {
|
||||||
|
super(props);
|
||||||
|
this.contentNode.classList.add('arduino-settings-dialog');
|
||||||
|
this.appendCloseButton('CANCEL');
|
||||||
|
this.appendAcceptButton('OK');
|
||||||
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
this.toDispose.push(this.settingsService.onDidChange(this.validate.bind(this)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async isValid(settings: Promise<Settings>): Promise<DialogError> {
|
||||||
|
const result = await this.settingsService.validate(settings);
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): Promise<Settings> {
|
||||||
|
return this.settingsService.settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAfterAttach(msg: Message): void {
|
||||||
|
if (this.widget.isAttached) {
|
||||||
|
Widget.detach(this.widget);
|
||||||
|
}
|
||||||
|
Widget.attach(this.widget, this.contentNode);
|
||||||
|
this.toDisposeOnDetach.push(this.settingsService.onDidChange(() => this.update()));
|
||||||
|
super.onAfterAttach(msg);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onUpdateRequest(msg: Message) {
|
||||||
|
super.onUpdateRequest(msg);
|
||||||
|
this.widget.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onActivateRequest(msg: Message): void {
|
||||||
|
super.onActivateRequest(msg);
|
||||||
|
this.widget.activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
|
||||||
|
|
||||||
|
protected readonly textArea: HTMLTextAreaElement;
|
||||||
|
|
||||||
|
constructor(urls: string[], windowService: WindowService) {
|
||||||
|
super({ title: 'Additional Boards Manager URLs' });
|
||||||
|
|
||||||
|
this.contentNode.classList.add('additional-urls-dialog');
|
||||||
|
|
||||||
|
const description = document.createElement('div');
|
||||||
|
description.textContent = 'Enter additional URLs, one for each row';
|
||||||
|
description.style.marginBottom = '5px';
|
||||||
|
this.contentNode.appendChild(description);
|
||||||
|
|
||||||
|
this.textArea = document.createElement('textarea');
|
||||||
|
this.textArea.className = 'theia-input';
|
||||||
|
this.textArea.setAttribute('style', 'flex: 0;');
|
||||||
|
this.textArea.value = urls.filter(url => url.trim()).filter(url => !!url).join('\n');
|
||||||
|
this.textArea.wrap = 'soft';
|
||||||
|
this.textArea.cols = 90;
|
||||||
|
this.textArea.rows = 5;
|
||||||
|
this.contentNode.appendChild(this.textArea);
|
||||||
|
|
||||||
|
const anchor = document.createElement('div');
|
||||||
|
anchor.classList.add('link');
|
||||||
|
anchor.textContent = 'Click for a list of unofficial board support URLs';
|
||||||
|
anchor.style.marginTop = '5px';
|
||||||
|
anchor.style.cursor = 'pointer';
|
||||||
|
this.addEventListener(
|
||||||
|
anchor,
|
||||||
|
'click',
|
||||||
|
() => windowService.openNewWindow('https://github.com/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls', { external: true })
|
||||||
|
);
|
||||||
|
this.contentNode.appendChild(anchor);
|
||||||
|
|
||||||
|
this.appendAcceptButton('OK');
|
||||||
|
this.appendCloseButton('Cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): string[] {
|
||||||
|
return this.textArea.value.split('\n').map(url => url.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAfterAttach(message: Message): void {
|
||||||
|
super.onAfterAttach(message);
|
||||||
|
this.addUpdateListener(this.textArea, 'input');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onActivateRequest(message: Message): void {
|
||||||
|
super.onActivateRequest(message);
|
||||||
|
this.textArea.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleEnter(event: KeyboardEvent): boolean | void {
|
||||||
|
if (event.target instanceof HTMLInputElement) {
|
||||||
|
return super.handleEnter(event);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -135,18 +135,6 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
|||||||
width: 740px;
|
width: 740px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.theia-button {
|
|
||||||
height: 31px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.theia-button.secondary {
|
|
||||||
background-color: var(--theia-secondaryButton-background);
|
|
||||||
color: var(--theia-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
button.theia-button.main {
|
|
||||||
color: var(--theia-button-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialogControl {
|
.dialogControl {
|
||||||
margin: 0 20px 30px 0;
|
margin: 0 20px 30px 0;
|
||||||
@@ -203,12 +191,14 @@ button.theia-button.main {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0px 3px 0px 3px;
|
margin: 0px 3px 0px 3px;
|
||||||
|
border: 1px solid var(--theia-dropdown-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-list {
|
.arduino-boards-dropdown-list {
|
||||||
border: 3px solid var(--theia-activityBar-background);
|
border: 3px solid var(--theia-activityBar-background);
|
||||||
margin: -3px;
|
margin: -1px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
border: 1px solid var(--theia-dropdown-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-item {
|
.arduino-boards-dropdown-item {
|
||||||
@@ -218,6 +208,7 @@ button.theia-button.main {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--theia-foreground);
|
color: var(--theia-foreground);
|
||||||
background: var(--theia-tab-unfocusedActiveBackground);
|
background: var(--theia-tab-unfocusedActiveBackground);
|
||||||
|
border: 1px solid var(--theia-tab-unfocusedActiveBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-item .fa-check {
|
.arduino-boards-dropdown-item .fa-check {
|
||||||
@@ -227,5 +218,5 @@ button.theia-button.main {
|
|||||||
|
|
||||||
.arduino-boards-dropdown-item.selected,
|
.arduino-boards-dropdown-item.selected,
|
||||||
.arduino-boards-dropdown-item:hover {
|
.arduino-boards-dropdown-item:hover {
|
||||||
background: var(--theia-list-hoverBackground);
|
border: 1px solid var(--theia-focusBorder);
|
||||||
}
|
}
|
||||||
|
6
arduino-ide-extension/src/browser/style/editor.css
Normal file
6
arduino-ide-extension/src/browser/style/editor.css
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/* 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.theia-mod-dirty > .p-TabBar-tabCloseIcon:hover {
|
||||||
|
background-size: 13px;
|
||||||
|
background-image: var(--theia-icon-circle);
|
||||||
|
}
|
@@ -5,6 +5,8 @@
|
|||||||
@import './arduino-select.css';
|
@import './arduino-select.css';
|
||||||
@import './status-bar.css';
|
@import './status-bar.css';
|
||||||
@import './terminal.css';
|
@import './terminal.css';
|
||||||
|
@import './editor.css';
|
||||||
|
@import './settings-dialog.css';
|
||||||
|
|
||||||
.theia-input.warning:focus {
|
.theia-input.warning:focus {
|
||||||
outline-width: 1px;
|
outline-width: 1px;
|
||||||
@@ -34,3 +36,28 @@
|
|||||||
color: var(--theia-warningForeground);
|
color: var(--theia-warningForeground);
|
||||||
background-color: var(--theia-warningBackground);
|
background-color: var(--theia-warningBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Overrule the default Theia CSS button styles. */
|
||||||
|
|
||||||
|
button.theia-button,
|
||||||
|
.theia-button {
|
||||||
|
border: 1px solid var(--theia-dropdown-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.theia-button:hover,
|
||||||
|
.theia-button:hover {
|
||||||
|
border: 1px solid var(--theia-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.theia-button {
|
||||||
|
height: 31px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.theia-button.secondary {
|
||||||
|
background-color: var(--theia-secondaryButton-background);
|
||||||
|
color: var(--theia-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.theia-button.main {
|
||||||
|
color: var(--theia-button-foreground);
|
||||||
|
}
|
||||||
|
@@ -64,6 +64,23 @@
|
|||||||
mask-position: 28px -4px;
|
mask-position: 28px -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.arduino-start-debug-icon {
|
||||||
|
-webkit-mask: url('../icons/debug-dark.svg') 50%;
|
||||||
|
mask: url('../icons/debug-dark.svg') 50%;
|
||||||
|
-webkit-mask-size: 100%;
|
||||||
|
mask-size: 100%;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--theia-ui-button-font-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-start-debug {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
#arduino-toolbar-container {
|
#arduino-toolbar-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
.p-TabBar.theia-app-centers .p-TabBar-tabIcon.arduino-serial-monitor-tab-icon {
|
.monitor-tab-icon {
|
||||||
background: url(../icons/buttons.svg);
|
-webkit-mask: url('../icons/monitor-tab-icon.svg');
|
||||||
background-size: 800%;
|
mask: url('../icons/monitor-tab-icon.svg');
|
||||||
background-position-y: 41px;
|
|
||||||
background-position-x: 19px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.serial-monitor {
|
.serial-monitor {
|
||||||
|
57
arduino-ide-extension/src/browser/style/settings-dialog.css
Normal file
57
arduino-ide-extension/src/browser/style/settings-dialog.css
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
.arduino-settings-dialog {
|
||||||
|
width: 740px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .content {
|
||||||
|
padding: 5px;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .flex-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .with-margin {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .theia-select {
|
||||||
|
background: var(--theia-input-background) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .column > div {
|
||||||
|
height: 26px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .stretch {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .flex-line .theia-button.shrink {
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .proxy-settings {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog input[type="radio"] {
|
||||||
|
margin: 3px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .theia-input.small {
|
||||||
|
max-width: 50px;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.additional-urls-dialog .link:hover {
|
||||||
|
color: var(--theia-textLink-activeForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-settings-dialog .react-tabs__tab-list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
@@ -1,25 +0,0 @@
|
|||||||
import { injectable, inject, postConstruct } from 'inversify';
|
|
||||||
import { AboutDialog as TheiaAboutDialog, ABOUT_CONTENT_CLASS } from '@theia/core/lib/browser/about-dialog';
|
|
||||||
import { ConfigService } from '../../../common/protocol/config-service';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class AboutDialog extends TheiaAboutDialog {
|
|
||||||
|
|
||||||
@inject(ConfigService)
|
|
||||||
protected readonly configService: ConfigService;
|
|
||||||
|
|
||||||
@postConstruct()
|
|
||||||
protected async init(): Promise<void> {
|
|
||||||
const [, version] = await Promise.all([super.init(), this.configService.getVersion()]);
|
|
||||||
if (version) {
|
|
||||||
const { firstChild } = this.contentNode;
|
|
||||||
if (firstChild instanceof HTMLElement && firstChild.classList.contains(ABOUT_CONTENT_CLASS)) {
|
|
||||||
const cliVersion = document.createElement('div');
|
|
||||||
cliVersion.textContent = version;
|
|
||||||
firstChild.appendChild(cliVersion);
|
|
||||||
// TODO: anchor to the commit in the `arduino-cli` repository.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -2,31 +2,35 @@
|
|||||||
import { injectable, inject } from 'inversify';
|
import { injectable, inject } from 'inversify';
|
||||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||||
import { CommandService } from '@theia/core/lib/common/command';
|
import { CommandService } from '@theia/core/lib/common/command';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||||
|
import { ConnectionStatusService, ConnectionStatus } from '@theia/core/lib/browser/connection-status-service';
|
||||||
import { ApplicationShell as TheiaApplicationShell, Widget } from '@theia/core/lib/browser';
|
import { ApplicationShell as TheiaApplicationShell, Widget } from '@theia/core/lib/browser';
|
||||||
import { Sketch } from '../../../common/protocol';
|
import { Sketch } from '../../../common/protocol';
|
||||||
import { EditorMode } from '../../editor-mode';
|
|
||||||
import { SaveAsSketch } from '../../contributions/save-as-sketch';
|
import { SaveAsSketch } from '../../contributions/save-as-sketch';
|
||||||
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ApplicationShell extends TheiaApplicationShell {
|
export class ApplicationShell extends TheiaApplicationShell {
|
||||||
|
|
||||||
@inject(EditorMode)
|
|
||||||
protected readonly editorMode: EditorMode;
|
|
||||||
|
|
||||||
@inject(CommandService)
|
@inject(CommandService)
|
||||||
protected readonly commandService: CommandService;
|
protected readonly commandService: CommandService;
|
||||||
|
|
||||||
|
@inject(MessageService)
|
||||||
|
protected readonly messageService: MessageService;
|
||||||
|
|
||||||
@inject(SketchesServiceClientImpl)
|
@inject(SketchesServiceClientImpl)
|
||||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||||
|
|
||||||
|
@inject(ConnectionStatusService)
|
||||||
|
protected readonly connectionStatusService: ConnectionStatusService;
|
||||||
|
|
||||||
protected track(widget: Widget): void {
|
protected track(widget: Widget): void {
|
||||||
super.track(widget);
|
super.track(widget);
|
||||||
if (widget instanceof OutputWidget) {
|
if (widget instanceof OutputWidget) {
|
||||||
widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
|
widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
|
||||||
}
|
}
|
||||||
if (!this.editorMode.proMode && widget instanceof EditorWidget) {
|
if (widget instanceof EditorWidget) {
|
||||||
// Make the editor un-closeable asynchronously.
|
// Make the editor un-closeable asynchronously.
|
||||||
this.sketchesServiceClient.currentSketch().then(sketch => {
|
this.sketchesServiceClient.currentSketch().then(sketch => {
|
||||||
if (sketch) {
|
if (sketch) {
|
||||||
@@ -60,6 +64,10 @@ export class ApplicationShell extends TheiaApplicationShell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveAll(): Promise<void> {
|
async saveAll(): Promise<void> {
|
||||||
|
if (this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE) {
|
||||||
|
this.messageService.error('Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.');
|
||||||
|
return; // Theia does not reject on failed save: https://github.com/eclipse-theia/theia/pull/8803
|
||||||
|
}
|
||||||
await super.saveAll();
|
await super.saveAll();
|
||||||
const options = { execOnlyIfTemp: true, openAfterMove: true };
|
const options = { execOnlyIfTemp: true, openAfterMove: true };
|
||||||
await this.commandService.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, options);
|
await this.commandService.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, options);
|
||||||
|
@@ -19,7 +19,9 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
|
|||||||
CommonCommands.AUTO_SAVE,
|
CommonCommands.AUTO_SAVE,
|
||||||
CommonCommands.OPEN_PREFERENCES,
|
CommonCommands.OPEN_PREFERENCES,
|
||||||
CommonCommands.SELECT_ICON_THEME,
|
CommonCommands.SELECT_ICON_THEME,
|
||||||
CommonCommands.SELECT_COLOR_THEME
|
CommonCommands.SELECT_COLOR_THEME,
|
||||||
|
CommonCommands.ABOUT_COMMAND,
|
||||||
|
CommonCommands.SAVE_WITHOUT_FORMATTING // Patched for https://github.com/eclipse-theia/theia/pull/8877
|
||||||
]) {
|
]) {
|
||||||
registry.unregisterMenuAction(command);
|
registry.unregisterMenuAction(command);
|
||||||
}
|
}
|
||||||
|
@@ -6,20 +6,30 @@ import {
|
|||||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
||||||
ConnectionStatus
|
ConnectionStatus
|
||||||
} from '@theia/core/lib/browser/connection-status-service';
|
} from '@theia/core/lib/browser/connection-status-service';
|
||||||
import { ArduinoDaemonClientImpl } from '../../arduino-daemon-client-impl';
|
import { ArduinoDaemon } from '../../../common/protocol';
|
||||||
|
import { NotificationCenter } from '../../notification-center';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
|
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
|
||||||
|
|
||||||
@inject(ArduinoDaemonClientImpl)
|
@inject(ArduinoDaemon)
|
||||||
protected readonly daemonClient: ArduinoDaemonClientImpl;
|
protected readonly daemon: ArduinoDaemon;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
protected isRunning = false;
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected init(): void {
|
protected async init(): Promise<void> {
|
||||||
this.schedulePing();
|
this.schedulePing();
|
||||||
|
try {
|
||||||
|
this.isRunning = await this.daemon.isRunning();
|
||||||
|
} catch { }
|
||||||
|
this.notificationCenter.onDaemonStarted(() => this.isRunning = true);
|
||||||
|
this.notificationCenter.onDaemonStopped(() => this.isRunning = false);
|
||||||
this.wsConnectionProvider.onIncomingMessageActivity(() => {
|
this.wsConnectionProvider.onIncomingMessageActivity(() => {
|
||||||
// natural activity
|
this.updateStatus(this.isRunning);
|
||||||
this.updateStatus(this.daemonClient.isRunning);
|
|
||||||
this.schedulePing();
|
this.schedulePing();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -29,22 +39,35 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class ApplicationConnectionStatusContribution extends TheiaApplicationConnectionStatusContribution {
|
export class ApplicationConnectionStatusContribution extends TheiaApplicationConnectionStatusContribution {
|
||||||
|
|
||||||
@inject(ArduinoDaemonClientImpl)
|
@inject(ArduinoDaemon)
|
||||||
protected readonly daemonClient: ArduinoDaemonClientImpl;
|
protected readonly daemon: ArduinoDaemon;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
protected isRunning = false;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected async init(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.isRunning = await this.daemon.isRunning();
|
||||||
|
} catch { }
|
||||||
|
this.notificationCenter.onDaemonStarted(() => this.isRunning = true);
|
||||||
|
this.notificationCenter.onDaemonStopped(() => this.isRunning = false);
|
||||||
|
}
|
||||||
|
|
||||||
protected onStateChange(state: ConnectionStatus): void {
|
protected onStateChange(state: ConnectionStatus): void {
|
||||||
if (!this.daemonClient.isRunning && state === ConnectionStatus.ONLINE) {
|
if (!this.isRunning && state === ConnectionStatus.ONLINE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
super.onStateChange(state);
|
super.onStateChange(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleOffline(): void {
|
protected handleOffline(): void {
|
||||||
const { isRunning } = this.daemonClient;
|
|
||||||
this.statusBar.setElement('connection-status', {
|
this.statusBar.setElement('connection-status', {
|
||||||
alignment: StatusBarAlignment.LEFT,
|
alignment: StatusBarAlignment.LEFT,
|
||||||
text: isRunning ? 'Offline' : '$(bolt) CLI Daemon Offline',
|
text: this.isRunning ? 'Offline' : '$(bolt) CLI Daemon Offline',
|
||||||
tooltip: isRunning ? 'Cannot connect to the backend.' : 'Cannot connect to the CLI daemon.',
|
tooltip: this.isRunning ? 'Cannot connect to the backend.' : 'Cannot connect to the CLI daemon.',
|
||||||
priority: 5000
|
priority: 5000
|
||||||
});
|
});
|
||||||
this.toDisposeOnOnline.push(Disposable.create(() => this.statusBar.removeElement('connection-status')));
|
this.toDisposeOnOnline.push(Disposable.create(() => this.statusBar.removeElement('connection-status')));
|
||||||
|
@@ -1,15 +1,16 @@
|
|||||||
import { injectable, inject } from 'inversify';
|
import { injectable, inject } from 'inversify';
|
||||||
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||||
import { CommandService } from '@theia/core/lib/common/command';
|
import { CommandService } from '@theia/core/lib/common/command';
|
||||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||||
import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { SketchesService } from '../../../common/protocol';
|
||||||
import { ArduinoCommands } from '../../arduino-commands';
|
import { ArduinoCommands } from '../../arduino-commands';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class FrontendApplication extends TheiaFrontendApplication {
|
export class FrontendApplication extends TheiaFrontendApplication {
|
||||||
|
|
||||||
@inject(FileSystem)
|
@inject(FileService)
|
||||||
protected readonly fileSystem: FileSystem;
|
protected readonly fileService: FileService;
|
||||||
|
|
||||||
@inject(WorkspaceService)
|
@inject(WorkspaceService)
|
||||||
protected readonly workspaceService: WorkspaceService;
|
protected readonly workspaceService: WorkspaceService;
|
||||||
@@ -17,13 +18,17 @@ export class FrontendApplication extends TheiaFrontendApplication {
|
|||||||
@inject(CommandService)
|
@inject(CommandService)
|
||||||
protected readonly commandService: CommandService;
|
protected readonly commandService: CommandService;
|
||||||
|
|
||||||
|
@inject(SketchesService)
|
||||||
|
protected readonly sketchesService: SketchesService;
|
||||||
|
|
||||||
protected async initializeLayout(): Promise<void> {
|
protected async initializeLayout(): Promise<void> {
|
||||||
await super.initializeLayout();
|
await super.initializeLayout();
|
||||||
const roots = await this.workspaceService.roots;
|
const roots = await this.workspaceService.roots;
|
||||||
for (const root of roots) {
|
for (const root of roots) {
|
||||||
const exists = await this.fileSystem.exists(root.uri);
|
const exists = await this.fileService.exists(root.resource);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
await this.commandService.executeCommand(ArduinoCommands.OPEN_SKETCH_FILES.id, root.uri);
|
this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu
|
||||||
|
await this.commandService.executeCommand(ArduinoCommands.OPEN_SKETCH_FILES.id, root.resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { inject, injectable, postConstruct } from 'inversify';
|
import { inject, injectable, postConstruct } from 'inversify';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { Title, Widget } from '@phosphor/widgets';
|
import { Title, Widget } from '@phosphor/widgets';
|
||||||
import { ILogger } from '@theia/core';
|
import { ILogger } from '@theia/core/lib/common/logger';
|
||||||
|
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||||
import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration';
|
import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration';
|
||||||
import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
|
import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
|
||||||
import { ConfigService } from '../../../common/protocol/config-service';
|
import { ConfigService } from '../../../common/protocol/config-service';
|
||||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class TabBarDecoratorService extends TheiaTabBarDecoratorService {
|
export class TabBarDecoratorService extends TheiaTabBarDecoratorService {
|
||||||
@@ -20,7 +20,6 @@ export class TabBarDecoratorService extends TheiaTabBarDecoratorService {
|
|||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
super.init();
|
|
||||||
this.configService.getConfiguration()
|
this.configService.getConfiguration()
|
||||||
.then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri))
|
.then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri))
|
||||||
.catch(err => this.logger.error(`Failed to determine the data directory: ${err}`));
|
.catch(err => this.logger.error(`Failed to determine the data directory: ${err}`));
|
||||||
|
15
arduino-ide-extension/src/browser/theia/core/tab-bars.ts
Normal file
15
arduino-ide-extension/src/browser/theia/core/tab-bars.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { TabBar } from '@phosphor/widgets';
|
||||||
|
import { Saveable } from '@theia/core/lib/browser/saveable';
|
||||||
|
import { TabBarRenderer as TheiaTabBarRenderer } from '@theia/core/lib/browser/shell/tab-bars';
|
||||||
|
|
||||||
|
export class TabBarRenderer extends TheiaTabBarRenderer {
|
||||||
|
|
||||||
|
createTabClass(data: TabBar.IRenderData<any>): string {
|
||||||
|
let className = super.createTabClass(data);
|
||||||
|
if (!data.title.closable && Saveable.isDirty(data.title.owner)) {
|
||||||
|
className += ' p-mod-closable';
|
||||||
|
}
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,14 @@
|
|||||||
|
import { injectable } from 'inversify';
|
||||||
|
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||||
|
import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution, DebugMenus } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||||
|
import { unregisterSubmenu } from '../../menu/arduino-menus';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class DebugFrontendApplicationContribution extends TheiaDebugFrontendApplicationContribution {
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
super.registerMenus(registry);
|
||||||
|
unregisterSubmenu(DebugMenus.DEBUG, registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
import { injectable } from 'inversify';
|
||||||
|
import { DebugError } from '@theia/debug/lib/common/debug-service';
|
||||||
|
import { DebugSession } from '@theia/debug/lib/browser/debug-session';
|
||||||
|
import { DebugSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
|
||||||
|
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class DebugSessionManager extends TheiaDebugSessionManager {
|
||||||
|
|
||||||
|
async start(options: DebugSessionOptions): Promise<DebugSession | undefined> {
|
||||||
|
return this.progressService.withProgress('Start...', 'debug', async () => {
|
||||||
|
try {
|
||||||
|
// Only save when dirty. To avoid saving temporary sketches.
|
||||||
|
// This is a quick fix for not saving the editor when there are no dirty editors.
|
||||||
|
// // https://github.com/bcmi-labs/arduino-editor/pull/172#issuecomment-741831888
|
||||||
|
if (this.shell.canSaveAll()) {
|
||||||
|
await this.shell.saveAll();
|
||||||
|
}
|
||||||
|
await this.fireWillStartDebugSession();
|
||||||
|
const resolved = await this.resolveConfiguration(options);
|
||||||
|
|
||||||
|
// preLaunchTask isn't run in case of auto restart as well as postDebugTask
|
||||||
|
if (!options.configuration.__restart) {
|
||||||
|
const taskRun = await this.runTask(options.workspaceFolderUri, resolved.configuration.preLaunchTask, true);
|
||||||
|
if (!taskRun) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionId = await this.debug.createDebugSession(resolved.configuration);
|
||||||
|
return this.doStart(sessionId, resolved);
|
||||||
|
} catch (e) {
|
||||||
|
if (DebugError.NotFound.is(e)) {
|
||||||
|
this.messageService.error(`The debug session type "${e.data.type}" is not supported.`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messageService.error('There was an error starting the debug session, check the logs for more details.');
|
||||||
|
console.error('Error starting the debug session', e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user