From 8036f14d190638197903e34e304208da1b87fe51 Mon Sep 17 00:00:00 2001 From: "Brian A. Ignacio" Date: Tue, 9 Mar 2021 17:09:47 +0800 Subject: [PATCH] New ESP-IDF project Wizard (VSC-478) (#171) * add new project window * add template page to new project * add create project improve UI * update schema * add compiler path to ccpp props file * improve layout * add espressif icon * add esp config boards * add command to readme * use iconify codicon * update schema file * Rename SerialPort.i18n.json to serialPort.i18n.json * mv open project outsite progress * update create project cmd * refactor fix templates html description Co-authored-by: Brian Ignacio --- README.md | 55 +-- docs/LANG_CONTRIBUTE.md | 2 +- ...ialPort.i18n.json => serialPort.i18n.json} | 0 i18n/en/package.i18n.json | 9 +- ...ialPort.i18n.json => serialPort.i18n.json} | 0 i18n/es/package.i18n.json | 7 +- ...ialPort.i18n.json => serialPort.i18n.json} | 0 i18n/ru/package.i18n.json | 7 +- ...ialPort.i18n.json => serialPort.i18n.json} | 0 i18n/zh-CN/package.i18n.json | 7 +- package.json | 40 +- package.nls.json | 7 +- schema.i18n.json | 7 +- src/espIdf/idfComponent/IdfComponent.ts | 18 + src/espIdf/menuconfig/MenuconfigPanel.ts | 2 +- src/espIdf/serial/serialPort.ts | 4 + src/examples/Example.ts | 44 ++ src/examples/ExamplesPanel.ts | 65 +-- src/extension.ts | 60 ++- src/idfConfiguration.ts | 2 - src/newProject/newProjectInit.ts | 136 +++++++ src/newProject/newProjectPanel.ts | 380 ++++++++++++++++++ src/utils.ts | 65 +++ src/views/examples/Examples.vue | 15 +- src/views/new-project/App.vue | 33 ++ src/views/new-project/Configure.vue | 227 +++++++++++ src/views/new-project/Templates.vue | 157 ++++++++ .../new-project/components/IdfComponent.vue | 24 ++ .../new-project/components/IdfComponents.vue | 94 +++++ .../new-project/components/folderOpen.vue | 60 +++ src/views/new-project/main.ts | 104 +++++ src/views/new-project/store/index.ts | 199 +++++++++ webpack.config.js | 7 + 33 files changed, 1700 insertions(+), 137 deletions(-) rename i18n/en/out/espIdf/serial/{SerialPort.i18n.json => serialPort.i18n.json} (100%) rename i18n/es/out/espIdf/serial/{SerialPort.i18n.json => serialPort.i18n.json} (100%) rename i18n/ru/out/espIdf/serial/{SerialPort.i18n.json => serialPort.i18n.json} (100%) rename i18n/zh-CN/out/espIdf/serial/{SerialPort.i18n.json => serialPort.i18n.json} (100%) create mode 100644 src/espIdf/idfComponent/IdfComponent.ts create mode 100644 src/examples/Example.ts create mode 100644 src/newProject/newProjectInit.ts create mode 100644 src/newProject/newProjectPanel.ts create mode 100644 src/views/new-project/App.vue create mode 100644 src/views/new-project/Configure.vue create mode 100644 src/views/new-project/Templates.vue create mode 100644 src/views/new-project/components/IdfComponent.vue create mode 100644 src/views/new-project/components/IdfComponents.vue create mode 100644 src/views/new-project/components/folderOpen.vue create mode 100644 src/views/new-project/main.ts create mode 100644 src/views/new-project/store/index.ts diff --git a/README.md b/README.md index d942a2a5c..614a043f1 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ All the other dependencies like ESP-IDF and ESP-IDF Tools can be installed using - Check you have installed the [Prerequisites](#Prerequisites) - Press F1 and type **ESP-IDF: Configure ESP-IDF extension** to open the extension configuration wizard. This will install ESP-IDF and tools and configure the extension. - - Please take a look at [SETUP](./docs/SETUP.md) for details about extension configuration. +- Please take a look at [SETUP](./docs/SETUP.md) for details about extension configuration. - Press F1 and type **ESP-IDF: Create ESP-IDF project** to generate a template ESP-IDF project. @@ -76,47 +76,50 @@ Click F1 to show Visual studio code actions, then type **ESP-IDF** to | Command Description | Keyboard Shortcuts (Mac) | Keyboard Shortcuts (Windows/ Linux) | | ------------------------------------------------------- | -------------------------------------- | ----------------------------------------- | -| Configure ESP-IDF extension | | | -| Create ESP-IDF project | E C | Ctrl E C | -| Add vscode configuration folder | | | | Add Arduino ESP32 as ESP-IDF Component | | | +| Add Editor coverage | | | +| Add vscode configuration folder | | | +| Build, Flash and start a monitor on your device | E D | Ctrl E D | +| Build your project | E B | Ctrl E B | +| Configure ESP-IDF extension | | | | Configure Paths | | | -| Set Espressif device target | | | +| Create project from extension template | E C | Ctrl E C | +| Create new ESP-IDF Component | | | | Device configuration | | | -| SDK Configuration editor | | | -| Set default sdkconfig file in project | | | -| Select port to use | E P | Ctrl E P | -| Full clean project | E F | Ctrl E F | -| Build your project | E B | Ctrl E B | +| Dispose current SDK Configuration editor server process | | | +| Doctor command | | | | Flash your project | E F | Ctrl E F | -| Monitor your device | E M | Ctrl E M | -| Build, Flash and start a monitor on your device | E D | Ctrl E D | -| Open ESP-IDF Terminal | E T | Ctrl E T | -| Pick a workspace folder | | | -| Size analysis of the binaries | E S | Ctrl E S | -| Show Examples Projects | | | -| Add Editor coverage | | | -| Remove Editor coverage | | | +| Full clean project | E X | Ctrl E X | | Get HTML Coverage Report for project | | | -| Search in documentation... | E D | Ctrl E D | | Install ESP-ADF | | | -| Install ESP-MDF | | | | Install ESP-IDF Python Packages | | | +| Install ESP-MDF | | | +| Monitor your device | E M | Ctrl E M | +| New Project | E N | Ctrl E N | +| Open ESP-IDF Terminal | E T | Ctrl E T | | Open NVS Partition Editor | | | +| Pick a workspace folder | | | +| SDK Configuration editor | E G | Ctrl E G | +| Search in documentation... | E Q | Ctrl E Q | +| Select port to use | E P | Ctrl E P | | Select OpenOCD Board Configuration | | | -| Doctor command | | | -| Create new ESP-IDF Component | | | +| Set default sdkconfig file in project | | | +| Set Espressif device target | | | +| Show Examples Projects | | | | Show ninja build summary | | | -| Dispose current SDK Configuration editor server process | | | +| Size analysis of the binaries | E S | Ctrl E S | +| Remove Editor coverage | | | The **Add Arduino-ESP32 as ESP-IDF Component** command will add [Arduino-ESP32](https://github.com/espressif/arduino-esp32) as a ESP-IDF component in your current directory (`${CURRENT_DIRECTORY}/components/arduino`). You can also use the **Create ESP-IDF project** command with `arduino-as-component` template to create a new project directory that includes Arduino-esp32 as an ESP-IDF component. > **NOTE** Not all versions of ESP-IDF are supported. Make sure to check [Arduino-ESP32](https://github.com/espressif/arduino-esp32) to see if your ESP-IDF version is compatible. -The **Show Examples Projects** command allows you create a new project using one of the examples in ESP-IDF, ESP-ADF or ESP-MDF directory if related configuration settings are set. The **Install ESP-ADF** will clone ESP-ADF to a selected directory and set `idf.espAdfPath` (`idf.espAdfPathWin` in Windows) configuration setting. + The **Install ESP-MDF** will clone ESP-MDF to a selected directory and set `idf.espMdfPath` (`idf.espMdfPathWin` in Windows) configuration setting. +The **Show Examples Projects** command allows you create a new project using one of the examples in ESP-IDF, ESP-ADF or ESP-MDF directory if related configuration settings are set. + ### Commands for tasks.json and launch.json We have implemented some utilities commands that can be used in tasks.json and launch.json like @@ -127,6 +130,10 @@ We have implemented some utilities commands that can be used in tasks.json and l as shown in the [debugging documentation](./DEBUGGING.md). +- `espIdf.getExtensionPath`: Get the installed location absolute path. +- `espIdf.getOpenOcdScriptValue`: Return the value of OPENOCD_SCRIPTS from `idf.customExtraVars` or from system OPENOCD_SCRIPTS environment variable. +- `espIdf.getOpenOcdConfig`: Return the openOCD configuration files as string. Example `"-f interface/ftdi/esp32_devkitj_v1.cfg" -f board/esp32-wrover.cfg`. +- `espIdf.getProjectName`: Return the project name from current workspace folder `build/project_description.json`. - `espIdf.getXtensaGcc`: Return the absolute path of the xtensa toolchain gcc for the ESP-IDF target given by `idf.adapterTargetName` configuration setting and `idf.customExtraPaths`. - `espIdf.getXtensaGdb`: Return the absolute path of the xtensa toolchain gdb for the ESP-IDF target given by `idf.adapterTargetName` configuration setting and `idf.customExtraPaths`. diff --git a/docs/LANG_CONTRIBUTE.md b/docs/LANG_CONTRIBUTE.md index de59e63e0..3113314f5 100644 --- a/docs/LANG_CONTRIBUTE.md +++ b/docs/LANG_CONTRIBUTE.md @@ -23,7 +23,7 @@ Each file has a key value structure such as: ```json { - "espIdf.createFiles.title": "ESP-IDF: Create ESP-IDF project", + "espIdf.createFiles.title": "ESP-IDF: Create ESP-IDF project from extension template", "textID": "value" } ``` diff --git a/i18n/en/out/espIdf/serial/SerialPort.i18n.json b/i18n/en/out/espIdf/serial/serialPort.i18n.json similarity index 100% rename from i18n/en/out/espIdf/serial/SerialPort.i18n.json rename to i18n/en/out/espIdf/serial/serialPort.i18n.json diff --git a/i18n/en/package.i18n.json b/i18n/en/package.i18n.json index 63a6bceaa..f0b5abeb4 100644 --- a/i18n/en/package.i18n.json +++ b/i18n/en/package.i18n.json @@ -1,14 +1,15 @@ { - "espIdf.createFiles.title": "ESP-IDF: Create project", + "espIdf.createFiles.title": "ESP-IDF: Create project from extension template", "espIdf.addArduinoAsComponentToCurFolder.title": "ESP-IDF: Add Arduino ESP32 as ESP-IDF Component", "espIdf.createIdfTerminal.title": "ESP-IDF: Open ESP-IDF Terminal", "espIdf.createVsCodeFolder.title": "ESP-IDF: Add vscode configuration folder", "espIdf.setPath.title": "ESP-IDF: Configure Paths", "espIdf.setTarget.title": "ESP-IDF: Set Espressif device target", "espIdf.selectConfTarget.title": "ESP-IDF: Select where to save configuration settings", - "espIdf.configDevice.title": "ESP-IDF:Device configuration", - "menuconfig.start.title": "ESP-IDF: SDK Configuration editor", - "cmakeListsEditor.start.title": "ESP-IDF: CMakeLists.txt Editor", + "espIdf.configDevice.title": "ESP-IDF: Device configuration", + "espIdf.menuconfig.start.title": "ESP-IDF: SDK Configuration editor", + "espIdf.cmakeListsEditor.start.title": "ESP-IDF: CMakeLists.txt Editor", + "espIdf.newProject.start.title": "ESP-IDF: New Project", "espIdf.setDefaultConfig.title": "ESP-IDF: Set default sdkconfig file in project", "espIdf.selectPort.title": "ESP-IDF: Select port to use", "espIdf.buildDevice.title": "ESP-IDF: Build your project", diff --git a/i18n/es/out/espIdf/serial/SerialPort.i18n.json b/i18n/es/out/espIdf/serial/serialPort.i18n.json similarity index 100% rename from i18n/es/out/espIdf/serial/SerialPort.i18n.json rename to i18n/es/out/espIdf/serial/serialPort.i18n.json diff --git a/i18n/es/package.i18n.json b/i18n/es/package.i18n.json index 6596b3785..079f47eb3 100644 --- a/i18n/es/package.i18n.json +++ b/i18n/es/package.i18n.json @@ -1,5 +1,5 @@ { - "espIdf.createFiles.title": "ESP-IDF: Crear proyecto", + "espIdf.createFiles.title": "ESP-IDF: Crear proyecto a partir de plantilla de la extensión", "espIdf.addArduinoAsComponentToCurFolder.title": "ESP-IDF: Agregar Arduino ESP32 como componente ESP-IDF", "espIdf.createIdfTerminal.title": "ESP-IDF: Abrir terminal ESP-IDF", "espIdf.createVsCodeFolder.title": "ESP-IDF: Agregar carpeta de configuración vscode", @@ -7,8 +7,9 @@ "espIdf.selectConfTarget.title": "ESP-IDF: Selecciona donde almacenar tu configuración", "espIdf.setTarget.title": "ESP-IDF: Selecciona el dispositivo Espressif a utilizar", "espIdf.configDevice.title": "ESP-IDF: Configuración del dispositivo", - "menuconfig.start.title": "ESP-IDF: Editor de Configuración SDK", - "cmakeListsEditor.start.title": "ESP-IDF: Editor de CMakeLists.txt", + "espIdf.menuconfig.start.title": "ESP-IDF: Editor de Configuración SDK", + "espIdf.cmakeListsEditor.start.title": "ESP-IDF: Editor de CMakeLists.txt", + "espIdf.newProject.start.title": "ESP-IDF: Nuevo Proyecto", "espIdf.setDefaultConfig.title": "ESP-IDF: Establecer archivo sdkconfig por defecto", "espIdf.selectPort.title": "ESP-IDF: Seleccionar puerto a utilizar", "espIdf.buildDevice.title": "ESP-IDF: Construir el proyecto", diff --git a/i18n/ru/out/espIdf/serial/SerialPort.i18n.json b/i18n/ru/out/espIdf/serial/serialPort.i18n.json similarity index 100% rename from i18n/ru/out/espIdf/serial/SerialPort.i18n.json rename to i18n/ru/out/espIdf/serial/serialPort.i18n.json diff --git a/i18n/ru/package.i18n.json b/i18n/ru/package.i18n.json index e53945f8b..a73f62307 100644 --- a/i18n/ru/package.i18n.json +++ b/i18n/ru/package.i18n.json @@ -1,5 +1,5 @@ { - "espIdf.createFiles.title": "ESP-IDF: Создать проект", + "espIdf.createFiles.title": "Создать проект из шаблона расширения", "espIdf.addArduinoAsComponentToCurFolder.title": "ESP-IDF: Добавить Arduino ESP32 как компонент ESP-IDF", "espIdf.createIdfTerminal.title": "ESP-IDF: Открыть терминал ESP-IDF", "espIdf.createVsCodeFolder.title": "ESP-IDF: Добавить папку конфигурации vscode", @@ -7,8 +7,9 @@ "espIdf.setTarget.title": "ESP-IDF: Установить целевое устройство Espressif", "espIdf.selectConfTarget.title": "ESP-IDF: Выбрать место для сохранения настроек конфигурации", "espIdf.configDevice.title": "ESP-IDF: Конфигурация устройства", - "menuconfig.start.title": "ESP-IDF: Редактор конфигурации SDK", - "cmakeListsEditor.start.title": "ESP-IDF: CMakeLists.txt Редактор", + "espIdf.menuconfig.start.title": "ESP-IDF: Редактор конфигурации SDK", + "espIdf.cmakeListsEditor.start.title": "ESP-IDF: CMakeLists.txt Редактор", + "espIdf.newProject.start.title": "ESP-IDF: Новый проект", "espIdf.setDefaultConfig.title": "ESP-IDF: Установить файл sdkconfig по умолчанию в проекте", "espIdf.selectPort.title": "ESP-IDF: Выбрать последовательный порт", "espIdf.buildDevice.title": "ESP-IDF: Собрать проект", diff --git a/i18n/zh-CN/out/espIdf/serial/SerialPort.i18n.json b/i18n/zh-CN/out/espIdf/serial/serialPort.i18n.json similarity index 100% rename from i18n/zh-CN/out/espIdf/serial/SerialPort.i18n.json rename to i18n/zh-CN/out/espIdf/serial/serialPort.i18n.json diff --git a/i18n/zh-CN/package.i18n.json b/i18n/zh-CN/package.i18n.json index 7c2889d85..85f73b274 100644 --- a/i18n/zh-CN/package.i18n.json +++ b/i18n/zh-CN/package.i18n.json @@ -1,5 +1,5 @@ { - "espIdf.createFiles.title": "ESP-IDF: 新建项目", + "espIdf.createFiles.title": "ESP-IDF: 从扩展模板创建项目", "espIdf.addArduinoAsComponentToCurFolder.title": "ESP-IDF: 添加ArduinoESP32作为ESP-IDF组件", "espIdf.createIdfTerminal.title": "ESP-IDF: 打开ESP-IDF终端", "espIdf.createVsCodeFolder.title": "ESP-IDF: 添加 vscode 配置文件夹", @@ -7,8 +7,9 @@ "espIdf.selectConfTarget.title": "ESP-IDF:选择保存配置设置的位置", "espIdf.setTarget.title": "ESP-IDF: 设置espressif设备目标", "espIdf.configDevice.title": "ESP-IDF:配置设备", - "menuconfig.start.title": "ESP-IDF:SDK配置编辑器", - "cmakeListsEditor.start.title": "ESP-IDF:CMakeLists.txt文件编辑", + "espIdf.menuconfig.start.title": "ESP-IDF:SDK配置编辑器", + "espIdf.cmakeListsEditor.start.title": "ESP-IDF:CMakeLists.txt文件编辑", + "espIdf.newProject.start.title": "ESP-IDF: 新建项目", "espIdf.setDefaultConfig.title": "ESP-IDF:设置项目默认的 sdkconfig 文件", "espIdf.selectPort.title": "ESP-IDF:选择要使用的烧录端口", "espIdf.buildDevice.title": "ESP-IDF:构建项目", diff --git a/package.json b/package.json index 8e78c1058..bfe2ed16c 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "onCommand:espIdf.setPath", "onCommand:espIdf.selectConfTarget", "onCommand:espIdf.configDevice", - "onCommand:menuconfig.start", - "onCommand:cmakeListsEditor.start", + "onCommand:espIdf.menuconfig.start", + "onCommand:espIdf.cmakeListsEditor.start", "onCommand:espIdf.setDefaultConfig", "onCommand:espIdf.selectPort", "onCommand:espIdf.setTarget", @@ -69,7 +69,7 @@ "onCommand:espIdf.removeCoverage", "onCommand:espIdf.searchInEspIdfDocs", "onCommand:espIdf.setup.start", - "onCommand:examples.start", + "onCommand:espIdf.examples.start", "onCommand:esp.rainmaker.backend.connect", "onCommand:esp.rainmaker.backend.sync", "onCommand:espIdf.getEspAdf", @@ -81,6 +81,7 @@ "onCommand:espIdf.doctorCommand", "onCommand:espIdf.createNewComponent", "onCommand:espIdf.ninja.summary", + "onCommand:espIdf.newProject.start", "onView:idfAppTracer", "onView:idfAppTraceArchive", "onView:espRainmaker", @@ -226,7 +227,7 @@ "explorer/context": [ { "when": "resourceFilename == sdkconfig", - "command": "menuconfig.start", + "command": "espIdf.menuconfig.start", "group": "navigation" }, { @@ -241,7 +242,7 @@ }, { "when": "resourceFilename == CMakeLists.txt", - "command": "cmakeListsEditor.start", + "command": "espIdf.cmakeListsEditor.start", "group": "navigation" } ] @@ -254,8 +255,8 @@ }, { "command": "espIdf.fullClean", - "key": "ctrl+e f", - "mac": "cmd+e f" + "key": "ctrl+e x", + "mac": "cmd+e x" }, { "command": "espIdf.buildDevice", @@ -272,6 +273,11 @@ "key": "ctrl+e m", "mac": "cmd+e m" }, + { + "command": "espIdf.newProject.start", + "key": "ctrl+e n", + "mac": "cmd+e n" + }, { "command": "espIdf.createIdfTerminal", "key": "ctrl+e t", @@ -288,7 +294,7 @@ "mac": "cmd+e d" }, { - "command": "menuconfig.start", + "command": "espIdf.menuconfig.start", "key": "ctrl+e g", "mac": "cmd+e g" }, @@ -299,8 +305,8 @@ }, { "command": "espIdf.searchInEspIdfDocs", - "key": "ctrl+e d", - "mac": "cmd+e d" + "key": "ctrl+e q", + "mac": "cmd+e q" } ], "configuration": [ @@ -613,20 +619,20 @@ "title": "%espIdf.configDevice.title%" }, { - "command": "menuconfig.start", - "title": "%menuconfig.start.title%" + "command": "espIdf.menuconfig.start", + "title": "%espIdf.menuconfig.start.title%" }, { "command": "espIdf.setup.start", "title": "%espIdf.setup.title%" }, { - "command": "examples.start", + "command": "espIdf.examples.start", "title": "%espIdf.examples.title%" }, { - "command": "cmakeListsEditor.start", - "title": "%cmakeListsEditor.start.title%" + "command": "espIdf.cmakeListsEditor.start", + "title": "%espIdf.cmakeListsEditor.start.title%" }, { "command": "espIdf.setDefaultConfig", @@ -810,6 +816,10 @@ { "command": "espIdf.disposeConfserverProcess", "title": "%espIdf.disposeConfserverProcess.title%" + }, + { + "command": "espIdf.newProject.start", + "title": "%espIdf.newProject.start.title%" } ], "breakpoints": [ diff --git a/package.nls.json b/package.nls.json index e33ee5026..93d47a10a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -1,5 +1,5 @@ { - "espIdf.createFiles.title": "ESP-IDF: Create project", + "espIdf.createFiles.title": "ESP-IDF: Create project from extension template", "espIdf.addArduinoAsComponentToCurFolder.title": "ESP-IDF: Add Arduino ESP32 as ESP-IDF Component", "espIdf.createIdfTerminal.title": "ESP-IDF: Open ESP-IDF Terminal", "espIdf.createVsCodeFolder.title": "ESP-IDF: Add vscode configuration folder", @@ -7,8 +7,9 @@ "espIdf.selectConfTarget.title": "ESP-IDF: Select where to save configuration settings", "espIdf.setTarget.title": "ESP-IDF: Set Espressif device target", "espIdf.configDevice.title": "ESP-IDF: Device configuration", - "menuconfig.start.title": "ESP-IDF: SDK Configuration editor", - "cmakeListsEditor.start.title": "ESP-IDF: CMakeLists.txt Editor", + "espIdf.menuconfig.start.title": "ESP-IDF: SDK Configuration editor", + "espIdf.cmakeListsEditor.start.title": "ESP-IDF: CMakeLists.txt Editor", + "espIdf.newProject.start.title": "ESP-IDF: New Project", "espIdf.setDefaultConfig.title": "ESP-IDF: Set default sdkconfig file in project", "espIdf.selectPort.title": "ESP-IDF: Select port to use", "espIdf.buildDevice.title": "ESP-IDF: Build your project", diff --git a/schema.i18n.json b/schema.i18n.json index 8baa1c29f..e3ee9ddf1 100644 --- a/schema.i18n.json +++ b/schema.i18n.json @@ -15,7 +15,7 @@ ] }, "serial": { - "SerialPort": [ + "serialPort": [ "serial.notSerialPortFoundMessage", "serial.noPortSelectedMessage", "serial.portHasBeenSelectedMessage", @@ -74,8 +74,9 @@ "espIdf.selectConfTarget.title", "espIdf.setTarget.title", "espIdf.configDevice.title", - "menuconfig.start.title", - "cmakeListsEditor.start.title", + "espIdf.menuconfig.start.title", + "espIdf.cmakeListsEditor.start.title", + "espIdf.newProject.start.title", "espIdf.setDefaultConfig.title", "espIdf.selectPort.title", "espIdf.buildDevice.title", diff --git a/src/espIdf/idfComponent/IdfComponent.ts b/src/espIdf/idfComponent/IdfComponent.ts new file mode 100644 index 000000000..47e41f58d --- /dev/null +++ b/src/espIdf/idfComponent/IdfComponent.ts @@ -0,0 +1,18 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export interface IComponent { + path: string; + name: string; +} diff --git a/src/espIdf/menuconfig/MenuconfigPanel.ts b/src/espIdf/menuconfig/MenuconfigPanel.ts index 16f7c7575..db98eccb8 100644 --- a/src/espIdf/menuconfig/MenuconfigPanel.ts +++ b/src/espIdf/menuconfig/MenuconfigPanel.ts @@ -120,7 +120,7 @@ export class MenuConfigPanel { ConfserverProcess.saveGuiConfigValues(); } else if (selected.title === returnToGuiconfigMsg) { this.dispose(); - vscode.commands.executeCommand("menuconfig.start"); + vscode.commands.executeCommand("espIdf.menuconfig.start"); return; } else { ConfserverProcess.loadGuiConfigValues(true); diff --git a/src/espIdf/serial/serialPort.ts b/src/espIdf/serial/serialPort.ts index e5c34bba1..6235d27f8 100644 --- a/src/espIdf/serial/serialPort.ts +++ b/src/espIdf/serial/serialPort.ts @@ -92,6 +92,10 @@ export class SerialPort { } } + public async getListArray() { + return await this.list(); + } + private async updatePortListStatus(l: string) { const target = idfConf.readParameter("idf.saveScope"); await idfConf.writeParameter("idf.port", l, target); diff --git a/src/examples/Example.ts b/src/examples/Example.ts new file mode 100644 index 000000000..7c6b2bf75 --- /dev/null +++ b/src/examples/Example.ts @@ -0,0 +1,44 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as path from "path"; +import * as utils from "../utils"; + +export interface IExample { + category: string; + name: string; + path: string; +} + +export function getExamplesList(targetFrameworkFolder: string): IExample[] { + const examplesPath = path.join(targetFrameworkFolder, "examples"); + const exampleCategories = utils.getDirectories(examplesPath); + const examplesPathList = utils.getSubProjects(examplesPath); + + const examplesList = examplesPathList.map((examplePath) => { + const category = + exampleCategories.find((c) => examplePath.indexOf(c) > -1) || ""; + const regexToUse = + process.platform === "win32" ? /([^\\]*)\\*$/ : /([^\/]*)\/*$/; + const exampleName = examplePath.match(regexToUse)[1] || ""; + + const example: IExample = { + category, + name: exampleName, + path: examplePath, + }; + return example; + }); + return examplesList; +} diff --git a/src/examples/ExamplesPanel.ts b/src/examples/ExamplesPanel.ts index 2fb14903c..e584ea4da 100644 --- a/src/examples/ExamplesPanel.ts +++ b/src/examples/ExamplesPanel.ts @@ -19,8 +19,8 @@ import { LocDictionary } from "../localizationDictionary"; import { Logger } from "../logger/logger"; import * as utils from "../utils"; import { createExamplesHtml } from "./createExamplesHtml"; -import marked from "marked"; import { ESP } from "../config"; +import { getExamplesList } from "./Example"; const locDic = new LocDictionary("ExamplesPanel"); @@ -132,9 +132,10 @@ export class ExamplesPlanel { ); try { const content = await readFile(pathToUse.fsPath); - const contentStr = this.resolveImgPath( + const contentStr = utils.markdownToWebviewHtml( content.toString(), - message.path + message.path, + this.panel ); this.panel.webview.postMessage({ command: "set_example_detail", @@ -163,64 +164,8 @@ export class ExamplesPlanel { this.panel.dispose(); } - private resolveImgPath(content: string, examplePath: string) { - marked.setOptions({ - baseUrl: null, - breaks: true, - gfm: true, - pedantic: false, - renderer: new marked.Renderer(), - sanitize: true, - smartLists: true, - smartypants: false, - }); - let contentStr = marked(content); - const srcLinkRegex = new RegExp(/src\s*=\s*"(.+?)"/g); - let match: RegExpExecArray; - while ((match = srcLinkRegex.exec(contentStr)) !== null) { - const unresolvedPath = match[1]; - const absPath = `src="${this.panel.webview.asWebviewUri( - vscode.Uri.file(path.resolve(examplePath, unresolvedPath)) - )}"`; - contentStr = contentStr.replace(match[0], absPath); - } - const srcEncodedRegex = new RegExp(/<img src="(.*?)"\s?>/g); - let encodedMatch: RegExpExecArray; - while ((encodedMatch = srcEncodedRegex.exec(contentStr)) !== null) { - const pathToResolve = encodedMatch[0].match( - /(?:src=")(.*?)(?:")/ - ); - const height = encodedMatch[0].match(/(?:height=")(.*?)(?:")/); - const width = encodedMatch[0].match(/(?:width=")(.*?)(?:")/); - const altText = encodedMatch[0].match(/(?:alt=")(.*?)(?:")/); - const absPath = ` 0 ? `height="${height[1]}"` : ""} ${ - width && width.length > 0 ? `width="${width[1]}"` : "" - } ${altText && altText.length > 0 ? `alt="${altText[1]}"` : ""} >`; - contentStr = contentStr.replace(encodedMatch[0], absPath); - } - contentStr = contentStr.replace(/</g, "<").replace(/>/g, ">"); - return contentStr; - } - private async obtainExamplesList(targetFrameworkFolder: string) { - const examplesPath = path.join(targetFrameworkFolder, "examples"); - const examplesCategories = utils.getDirectories(examplesPath); - const examplesListPaths = utils.getSubProjects(examplesPath); - const exampleListInfo = examplesListPaths.map((examplePath) => { - const exampleCategory = examplesCategories.find( - (exampleCat) => examplePath.indexOf(exampleCat) > -1 - ); - const regexToUse = - process.platform === "win32" ? /([^\\]*)\\*$/ : /([^\/]*)\/*$/; - const exampleName = examplePath.match(regexToUse)[1]; - return { - category: exampleCategory, - name: exampleName, - path: examplePath, - }; - }); + const exampleListInfo = getExamplesList(targetFrameworkFolder); this.panel.webview.postMessage({ command: "set_examples_path", example_list: exampleListInfo, diff --git a/src/extension.ts b/src/extension.ts index 0ff975730..eef0270aa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -103,6 +103,8 @@ import { generateConfigurationReport } from "./support"; import { initializeReportObject } from "./support/initReportObj"; import { writeTextReport } from "./support/writeReport"; import { kill } from "process"; +import { getNewProjectArgs } from "./newProject/newProjectInit"; +import { NewProjectPanel } from "./newProject/newProjectPanel"; // Global variables shared by commands let workspaceRoot: vscode.Uri; @@ -1029,7 +1031,7 @@ export async function activate(context: vscode.ExtensionContext) { registerIDFCommand("espIdf.monitorDevice", createMonitor); registerIDFCommand("espIdf.buildFlashMonitor", buildFlashAndMonitor); - registerIDFCommand("menuconfig.start", () => { + registerIDFCommand("espIdf.menuconfig.start", () => { PreCheck.perform([openFolderCheck], () => { try { if (ConfserverProcess.exists()) { @@ -1184,7 +1186,7 @@ export async function activate(context: vscode.ExtensionContext) { }); }); - registerIDFCommand("examples.start", () => { + registerIDFCommand("espIdf.examples.start", () => { try { vscode.window.withProgress( { @@ -1261,17 +1263,51 @@ export async function activate(context: vscode.ExtensionContext) { } }); - registerIDFCommand("cmakeListsEditor.start", async (fileUri: vscode.Uri) => { - if (!fileUri) { - Logger.errorNotify( - "Cannot call this command directly, right click on any CMakeLists.txt file!", - new Error("INVALID_INVOCATION") - ); + registerIDFCommand( + "espIdf.cmakeListsEditor.start", + async (fileUri: vscode.Uri) => { + if (!fileUri) { + Logger.errorNotify( + "Cannot call this command directly, right click on any CMakeLists.txt file!", + new Error("INVALID_INVOCATION") + ); + return; + } + PreCheck.perform([openFolderCheck], async () => { + await CmakeListsEditorPanel.createOrShow( + context.extensionPath, + fileUri + ); + }); + } + ); + + registerIDFCommand("espIdf.newProject.start", () => { + if (NewProjectPanel.isCreatedAndHidden()) { + NewProjectPanel.createOrShow(context.extensionPath); return; } - PreCheck.perform([openFolderCheck], async () => { - await CmakeListsEditorPanel.createOrShow(context.extensionPath, fileUri); - }); + vscode.window.withProgress( + { + cancellable: false, + location: vscode.ProgressLocation.Notification, + title: "ESP-IDF: New Project", + }, + async ( + progress: vscode.Progress<{ increment: number; message: string }>, + cancelToken: vscode.CancellationToken + ) => { + try { + const newProjectArgs = await getNewProjectArgs(progress); + if (!newProjectArgs || !newProjectArgs.targetList) { + throw new Error("Could not find ESP-IDF Targets"); + } + NewProjectPanel.createOrShow(context.extensionPath, newProjectArgs); + } catch (error) { + Logger.errorNotify(error.message, error); + } + } + ); }); registerIDFCommand("espIdf.openIdfDocument", (docUri: vscode.Uri) => { @@ -2185,7 +2221,7 @@ function creatCmdsStatusBarItems() { createStatusBarItem( "$(gear)", "ESP-IDF Launch GUI Configuration tool", - "menuconfig.start", + "espIdf.menuconfig.start", 100 ); createStatusBarItem("$(trash)", "ESP-IDF Full Clean", "espIdf.fullClean", 99); diff --git a/src/idfConfiguration.ts b/src/idfConfiguration.ts index 895f9cf35..8b99628ae 100644 --- a/src/idfConfiguration.ts +++ b/src/idfConfiguration.ts @@ -25,8 +25,6 @@ const platformDepConfigurations: string[] = [ "idf.espMdfPath", "idf.pythonBinPath", "idf.port", - "idf.deviceInterface", - "idf.board", "idf.toolsPath", ]; diff --git a/src/newProject/newProjectInit.ts b/src/newProject/newProjectInit.ts new file mode 100644 index 000000000..b9d57d763 --- /dev/null +++ b/src/newProject/newProjectInit.ts @@ -0,0 +1,136 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { join } from "path"; +import { Progress } from "vscode"; +import { getExamplesList, IExample } from "../examples/Example"; +import { IComponent } from "../espIdf/idfComponent/IdfComponent"; +import * as idfConf from "../idfConfiguration"; +import { IdfBoard, IdfTarget } from "../views/new-project/store"; +import { SerialPort } from "../espIdf/serial/serialPort"; +import { dirExistPromise } from "../utils"; +import { readJSON } from "fs-extra"; +import { Logger } from "../logger/logger"; + +export interface INewProjectArgs { + espIdfPath: string; + espAdfPath: string; + espMdfPath: string; + boards: IdfBoard[]; + components: IComponent[]; + serialPortList: string[]; + targetList: IdfTarget[]; + templates: IExample[]; +} + +const defTargetList: IdfTarget[] = [ + { + id: "esp32", + name: "ESP32", + openOcdFiles: "interface/ftdi/esp32_devkitjv1.cfg,target/esp32.cfg", + } as IdfTarget, + { + id: "esp32s2", + name: "ESP32-S2", + openOcdFiles: "interface/ftdi/esp32_devkitj_v1.cfg,target/esp32s2.cfg", + } as IdfTarget, +]; + +export async function getBoards() { + const customExtraVars = idfConf.readParameter("idf.customExtraVars"); + let openOcdScriptsPath: string; + try { + const jsonDict = JSON.parse(customExtraVars); + openOcdScriptsPath = jsonDict.hasOwnProperty("OPENOCD_SCRIPTS") + ? jsonDict.OPENOCD_SCRIPTS + : process.env.OPENOCD_SCRIPTS + ? process.env.OPENOCD_SCRIPTS + : undefined; + } catch (error) { + Logger.error(error.message, error); + openOcdScriptsPath = process.env.OPENOCD_SCRIPTS + ? process.env.OPENOCD_SCRIPTS + : undefined; + } + if (!openOcdScriptsPath) { + return; + } + const openOcdEspConfig = join(openOcdScriptsPath, "esp-config.json"); + try { + const openOcdEspConfigObj = await readJSON(openOcdEspConfig); + const espBoards: IdfBoard[] = openOcdEspConfigObj.boards.map((b) => { + return { + name: b.name, + description: b.description, + target: b.target, + configFiles: b.config_files, + } as IdfBoard; + }); + const emptyBoard = { + name: "Custom board", + description: "No board selected", + target: defTargetList[0].id, + configFiles: defTargetList[0].openOcdFiles, + } as IdfBoard; + espBoards.push(emptyBoard); + return espBoards; + } catch (error) { + Logger.error(error.message, error); + return; + } +} + +export async function getNewProjectArgs( + progress: Progress<{ message: string; increment: number }> +) { + progress.report({ increment: 10, message: "Loading ESP-IDF components..." }); + const components = []; + progress.report({ increment: 10, message: "Loading serial ports..." }); + const serialPortListDetails = await SerialPort.shared().getListArray(); + const serialPortList = serialPortListDetails.map((p) => p.comName); + progress.report({ increment: 10, message: "Loading ESP-IDF Boards list..." }); + const espBoards = await getBoards(); + progress.report({ increment: 10, message: "Loading ESP-IDF Target list..." }); + const targetList = defTargetList; + progress.report({ increment: 10, message: "Loading ESP-IDF Target list..." }); + const espIdfPath = idfConf.readParameter("idf.espIdfPath") as string; + const espAdfPath = idfConf.readParameter("idf.espAdfPath") as string; + const espMdfPath = idfConf.readParameter("idf.espMdfPath") as string; + let templates = []; + const idfExists = await dirExistPromise(espIdfPath); + if (idfExists) { + const idfTemplates = getExamplesList(espIdfPath); + templates = templates.concat(idfTemplates); + } + const adfExists = await dirExistPromise(espAdfPath); + if (adfExists) { + const adfTemplates = getExamplesList(espAdfPath); + templates = templates.concat(adfTemplates); + } + const mdfExists = await dirExistPromise(espMdfPath); + if (mdfExists) { + const mdfTemplates = getExamplesList(espMdfPath); + templates = templates.concat(mdfTemplates); + } + progress.report({ increment: 50, message: "Initializing wizard..." }); + return { + boards: espBoards, + components, + espAdfPath: adfExists ? espAdfPath : undefined, + espIdfPath: idfExists ? espIdfPath : undefined, + espMdfPath: mdfExists ? espMdfPath : undefined, + serialPortList, + targetList, + templates, + } as INewProjectArgs; +} diff --git a/src/newProject/newProjectPanel.ts b/src/newProject/newProjectPanel.ts new file mode 100644 index 000000000..6747638e6 --- /dev/null +++ b/src/newProject/newProjectPanel.ts @@ -0,0 +1,380 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import marked from "marked"; +import * as path from "path"; +import * as vscode from "vscode"; +import * as idfConf from "../idfConfiguration"; +import { Logger } from "../logger/logger"; +import { OutputChannel } from "../logger/outputChannel"; +import { LocDictionary } from "../localizationDictionary"; +import { INewProjectArgs } from "./newProjectInit"; +import { IComponent } from "../espIdf/idfComponent/IdfComponent"; +import { copy, ensureDir, readFile, readJSON, writeJSON } from "fs-extra"; +import * as utils from "../utils"; +import { IExample } from "../examples/Example"; + +const locDictionary = new LocDictionary("NewProjectPanel"); + +export class NewProjectPanel { + public static currentPanel: NewProjectPanel | undefined; + + public static createOrShow( + extensionPath: string, + newProjectArgs?: INewProjectArgs + ) { + const column = vscode.window.activeTextEditor + ? vscode.window.activeTextEditor.viewColumn + : vscode.ViewColumn.One; + if (NewProjectPanel.currentPanel) { + NewProjectPanel.currentPanel.panel.reveal(column); + } else { + NewProjectPanel.currentPanel = new NewProjectPanel( + extensionPath, + newProjectArgs, + column + ); + } + } + + public static isCreatedAndHidden(): boolean { + return ( + NewProjectPanel.currentPanel && + !NewProjectPanel.currentPanel.panel.visible + ); + } + + private static readonly viewType = "newProjectWizard"; + private readonly panel: vscode.WebviewPanel; + private extensionPath: string; + private _disposables: vscode.Disposable[] = []; + + private constructor( + extensionPath: string, + newProjectArgs: INewProjectArgs, + column: vscode.ViewColumn + ) { + this.extensionPath = extensionPath; + const newProjectTitle = locDictionary.localize( + "newProject.panelName", + "New Project" + ); + let localResourceRoots: vscode.Uri[] = []; + localResourceRoots.push( + vscode.Uri.file(path.join(this.extensionPath, "dist", "views")) + ); + if (newProjectArgs.espAdfPath) { + localResourceRoots.push(vscode.Uri.file(newProjectArgs.espAdfPath)); + } + if (newProjectArgs.espIdfPath) { + localResourceRoots.push(vscode.Uri.file(newProjectArgs.espIdfPath)); + } + if (newProjectArgs.espMdfPath) { + localResourceRoots.push(vscode.Uri.file(newProjectArgs.espMdfPath)); + } + this.panel = vscode.window.createWebviewPanel( + NewProjectPanel.viewType, + newProjectTitle, + column, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots, + } + ); + + this.panel.iconPath = vscode.Uri.file( + path.join(extensionPath, "media", "espressif_icon.png") + ); + + const scriptPath = this.panel.webview.asWebviewUri( + vscode.Uri.file( + path.join(this.extensionPath, "dist", "views", "newProject-bundle.js") + ) + ); + this.panel.webview.html = this.createHtml(scriptPath); + + const containerPath = + process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME; + + this.panel.webview.onDidReceiveMessage(async (message) => { + switch (message.command) { + case "createProject": + if ( + message.components && + message.containerFolder && + message.openOcdConfigFiles && + message.port && + message.projectName && + message.target && + message.template + ) { + this.createProject( + message.components, + message.target, + message.openOcdCfgs, + message.port, + message.containerFolder, + message.projectName, + message.template + ); + } + break; + case "getTemplateDetail": + if (message.path) { + await this.getTemplateDetail(message.path); + } + break; + case "loadComponent": + let selectedFolder = await this.openFolder(); + if (selectedFolder) { + const newComponent: IComponent = { + name: path.basename(selectedFolder), + path: selectedFolder, + }; + this.panel.webview.postMessage({ + command: "addComponentPath", + component: newComponent, + }); + } + break; + case "openProjectDirectory": + let selectedProjectDir = await this.openFolder(); + if (selectedProjectDir) { + this.panel.webview.postMessage({ + command: "setContainerDirectory", + projectDirectory: selectedProjectDir, + }); + } + break; + case "requestInitValues": + if ( + newProjectArgs && + newProjectArgs.targetList && + newProjectArgs.targetList.length > 0 && + newProjectArgs.serialPortList && + newProjectArgs.serialPortList.length > 0 + ) { + const defConfigFiles = + newProjectArgs.boards && newProjectArgs.boards.length > 0 + ? newProjectArgs.boards[0].configFiles + : newProjectArgs.targetList[0].openOcdFiles; + this.panel.webview.postMessage({ + boards: newProjectArgs.boards, + command: "initialLoad", + containerDirectory: containerPath, + projectName: "project-name", + serialPortList: newProjectArgs.serialPortList, + targetList: newProjectArgs.targetList, + openOcdConfigFiles: defConfigFiles, + templates: newProjectArgs.templates, + }); + } + break; + default: + break; + } + }); + + this.panel.onDidDispose( + () => { + NewProjectPanel.currentPanel = undefined; + }, + null, + this._disposables + ); + } + + private async createProject( + components: IComponent[], + idfTarget: string, + openOcdConfigs: string, + port: string, + projectDirectory: string, + projectName: string, + template: IExample + ) { + const newProjectPath = path.join(projectDirectory, projectName); + let isSkipped = false; + await vscode.window.withProgress( + { + cancellable: true, + location: vscode.ProgressLocation.Notification, + title: "ESP-IDF: Create project", + }, + async ( + progress: vscode.Progress<{ message: string; increment: number }>, + token: vscode.CancellationToken + ) => { + try { + const projectDirExists = await utils.dirExistPromise( + projectDirectory + ); + if (!projectDirExists) { + vscode.window.showInformationMessage( + "Project directory doesn't exists." + ); + this.panel.webview.postMessage({ + command: "goToBeginning", + }); + isSkipped = true; + return; + } + const projectNameExists = await utils.dirExistPromise(newProjectPath); + if (projectNameExists) { + const overwriteProject = await vscode.window.showInformationMessage( + `${newProjectPath} already exists. Overwrite content?`, + "Yes", + "No" + ); + if ( + typeof overwriteProject === "undefined" || + overwriteProject === "No" + ) { + isSkipped = true; + return; + } + } + await ensureDir(newProjectPath, { mode: 0o775 }); + if (template && template.path !== "") { + await utils.copyFromSrcProject(template.path, newProjectPath); + } else { + const boilerplatePath = path.join( + this.extensionPath, + "templates", + "boilerplate" + ); + await utils.copyFromSrcProject(boilerplatePath, newProjectPath); + } + const settingsJsonPath = path.join( + newProjectPath, + ".vscode", + "settings.json" + ); + const settingsJson = await readJSON(settingsJsonPath); + const idfPathDir = idfConf.readParameter("idf.espIdfPath"); + const adfPathDir = idfConf.readParameter("idf.espAdfPath"); + const mdfPathDir = idfConf.readParameter("idf.espMdfPath"); + const extraPaths = idfConf.readParameter("idf.customExtraPaths"); + const extraVars = idfConf.readParameter( + "idf.customExtraVars" + ) as string; + const toolsDir = idfConf.readParameter("idf.toolsPath"); + const pyPath = idfConf.readParameter("idf.pythonBinPath"); + const isWin = process.platform === "win32" ? "Win" : ""; + const modifiedEnv = utils.appendIdfAndToolsToPath(); + const idfTarget = modifiedEnv.IDF_TARGET || "esp32"; + settingsJson["idf.adapterTargetName"] = idfTarget; + settingsJson["idf.customExtraPaths"] = extraPaths; + settingsJson["idf.customExtraVars"] = extraVars; + settingsJson["idf.espIdfPath" + isWin] = idfPathDir; + settingsJson["idf.espAdfPath" + isWin] = adfPathDir; + settingsJson["idf.espMdfPath" + isWin] = mdfPathDir; + settingsJson["idf.openOcdConfigs"] = openOcdConfigs; + settingsJson["idf.port" + isWin] = port; + settingsJson["idf.pythonBinPath" + isWin] = pyPath; + settingsJson["idf.toolsPath" + isWin] = toolsDir; + await writeJSON(settingsJsonPath, settingsJson, { + spaces: + vscode.workspace.getConfiguration().get("editor.tabSize") || 2, + }); + + if (components && components.length > 0) { + const componentsPath = path.join(newProjectPath, "components"); + await ensureDir(componentsPath, { mode: 0o775 }); + for (const comp of components) { + const doesComponentExists = await utils.dirExistPromise( + comp.path + ); + if (doesComponentExists) { + const compPath = path.join(componentsPath, comp.name); + await ensureDir(compPath, { mode: 0o775 }); + await copy(comp.path, compPath); + } else { + const msg = `Component ${comp.name} path: ${comp.path} doesn't exist. Ignoring in new project...`; + Logger.info(msg); + OutputChannel.appendLine(msg); + } + } + } + } catch (error) { + Logger.errorNotify(error.message, error); + } + } + ); + if (isSkipped) { + return; + } + const projectCreatedMsg = `Project ${projectName} has been created. Open project in a new window?`; + const openProjectChoice = await vscode.window.showInformationMessage( + projectCreatedMsg, + "Yes", + "No" + ); + + if (openProjectChoice && openProjectChoice === "Yes") { + vscode.commands.executeCommand( + "vscode.openFolder", + vscode.Uri.file(newProjectPath), + true + ); + } + } + + private async openFolder() { + const selectedFolder = await vscode.window.showOpenDialog({ + canSelectFolders: true, + canSelectFiles: false, + canSelectMany: false, + }); + if (selectedFolder && selectedFolder.length > 0) { + return selectedFolder[0].fsPath; + } + } + + private async getTemplateDetail(projectPath: string) { + try { + const pathToUse = vscode.Uri.file(path.join(projectPath, "README.md")); + const readMeContent = await readFile(pathToUse.fsPath); + const contentStr = utils.markdownToWebviewHtml( + readMeContent.toString(), + projectPath, + this.panel + ); + this.panel.webview.postMessage({ + command: "setExampleDetail", + templateDetail: contentStr, + }); + } catch (error) { + this.panel.webview.postMessage({ + command: "set_example_detail", + templateDetail: "No README.md available for this project.", + }); + } + } + + private createHtml(scriptPath: vscode.Uri): string { + return ` + + + + + ESP-IDF Project + + +
+ + + `; + } +} diff --git a/src/utils.ts b/src/utils.ts index 89e0fe18f..a13bcae73 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -27,6 +27,7 @@ import { writeJSON, } from "fs-extra"; import * as HttpsProxyAgent from "https-proxy-agent"; +import marked from "marked"; import { EOL } from "os"; import * as path from "path"; import * as url from "url"; @@ -297,6 +298,19 @@ export function readFileSync(filePath) { return fs.readFileSync(filePath, "utf-8"); } +export function doesPathExists(inputPath: string) { + return pathExists(inputPath); +} +export function readJson(jsonPath: string) { + return readJSON(jsonPath); +} + +export function writeJson(jsonPath: string, object: any) { + return writeJSON(jsonPath, object, { + spaces: vscode.workspace.getConfiguration().get("editor.tabSize") || 2, + }); +} + export function readComponentsDirs(filePath): IdfComponent[] { const filesOrFolders: IdfComponent[] = []; @@ -848,3 +862,54 @@ export function compareVersion(v1: string, v2: string) { ? -1 : 1; } + +/** + * Parse markdown into html and fix images with panel paths + * @param {string} content - String with markdown content + * @param {string} projectPath - Project absolute path where images are + * @param {vscode.WebviewPanel} - Webview panel for webviewURI generation + */ +export function markdownToWebviewHtml( + content: string, + projectPath: string, + panel: vscode.WebviewPanel +) { + marked.setOptions({ + baseUrl: null, + breaks: true, + gfm: true, + pedantic: false, + renderer: new marked.Renderer(), + sanitize: true, + smartLists: true, + smartypants: false, + }); + let contentStr = marked(content); + const srcLinkRegex = new RegExp(/src\s*=\s*"(.+?)"/g); + let match: RegExpExecArray; + while ((match = srcLinkRegex.exec(contentStr)) !== null) { + const unresolvedPath = match[1]; + const absPath = `src="${panel.webview.asWebviewUri( + vscode.Uri.file(path.resolve(projectPath, unresolvedPath)) + )}"`; + contentStr = contentStr.replace(match[0], absPath); + } + const srcEncodedRegex = new RegExp(/<img src="(.*?)"\s?>/g); + let encodedMatch: RegExpExecArray; + while ((encodedMatch = srcEncodedRegex.exec(contentStr)) !== null) { + const pathToResolve = encodedMatch[0].match( + /(?:src=")(.*?)(?:")/ + ); + const height = encodedMatch[0].match(/(?:height=")(.*?)(?:")/); + const width = encodedMatch[0].match(/(?:width=")(.*?)(?:")/); + const altText = encodedMatch[0].match(/(?:alt=")(.*?)(?:")/); + const absPath = ` 0 ? `height="${height[1]}"` : ""} ${ + width && width.length > 0 ? `width="${width[1]}"` : "" + } ${altText && altText.length > 0 ? `alt="${altText[1]}"` : ""} >`; + contentStr = contentStr.replace(encodedMatch[0], absPath); + } + contentStr = contentStr.replace(/</g, "<").replace(/>/g, ">"); + return contentStr; +} diff --git a/src/views/examples/Examples.vue b/src/views/examples/Examples.vue index 7d92a57e4..34a9d79ee 100644 --- a/src/views/examples/Examples.vue +++ b/src/views/examples/Examples.vue @@ -2,10 +2,10 @@
    -
  • -

    +

  • +

      -
    • +
    • t.category)), + ]; + const getStarted = uniqueCategories.indexOf("get-started"); + uniqueCategories.splice(0, 0, uniqueCategories.splice(getStarted, 1)[0]); + return uniqueCategories; + } public toggleExampleDetail(example: IExample) { if (example.path !== this.storeSelectedExample.path) { @@ -127,6 +135,7 @@ ul.examples > li > p:hover { width: 30%; overflow-y: scroll; position: fixed; + text-align: start; } ul > li { list-style-type: none; diff --git a/src/views/new-project/App.vue b/src/views/new-project/App.vue new file mode 100644 index 000000000..77d7b9f44 --- /dev/null +++ b/src/views/new-project/App.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/src/views/new-project/Configure.vue b/src/views/new-project/Configure.vue new file mode 100644 index 000000000..0aaa313f5 --- /dev/null +++ b/src/views/new-project/Configure.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/src/views/new-project/Templates.vue b/src/views/new-project/Templates.vue new file mode 100644 index 000000000..7dc4ec540 --- /dev/null +++ b/src/views/new-project/Templates.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/src/views/new-project/components/IdfComponent.vue b/src/views/new-project/components/IdfComponent.vue new file mode 100644 index 000000000..b09f1bfb3 --- /dev/null +++ b/src/views/new-project/components/IdfComponent.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/views/new-project/components/IdfComponents.vue b/src/views/new-project/components/IdfComponents.vue new file mode 100644 index 000000000..1cdb80e60 --- /dev/null +++ b/src/views/new-project/components/IdfComponents.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/src/views/new-project/components/folderOpen.vue b/src/views/new-project/components/folderOpen.vue new file mode 100644 index 000000000..d581e11c3 --- /dev/null +++ b/src/views/new-project/components/folderOpen.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/views/new-project/main.ts b/src/views/new-project/main.ts new file mode 100644 index 000000000..bca6e6462 --- /dev/null +++ b/src/views/new-project/main.ts @@ -0,0 +1,104 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Vue from "vue"; +import VueRouter from "vue-router"; +// @ts-ignore +import App from "./App.vue"; +// @ts-ignore +import Configure from "./Configure.vue"; +// @ts-ignore +import Templates from "./Templates.vue"; +import { store } from "./store"; +import IconifyIcon from "@iconify/vue"; +import add from "@iconify-icons/codicon/add"; +import close from "@iconify-icons/codicon/close"; +import folder from "@iconify-icons/codicon/folder"; +import folderOpen from "@iconify-icons/codicon/folder-opened"; +IconifyIcon.addIcon("add", add); +IconifyIcon.addIcon("close", close); +IconifyIcon.addIcon("folder", folder); +IconifyIcon.addIcon("folder-opened", folderOpen); +Vue.component("iconify-icon", IconifyIcon); + +Vue.use(VueRouter); +const routes = [ + { path: "/", component: Configure }, + { path: "/templates", component: Templates }, +]; + +export const router = new VueRouter({ + base: __dirname, + routes, +}); + +const app = new Vue({ + el: "#app", + components: { App }, + router, + store, + template: "", +}); + +window.addEventListener("message", (event) => { + const msg = event.data; + switch (msg.command) { + case "goToBeginning": + app.$router.push("/"); + case "addComponentPath": + if (msg.component) { + store.commit("addComponent", msg.component); + } + break; + case "initialLoad": + if (msg.boards && msg.boards.length > 0) { + store.commit("setBoards", msg.boards); + store.commit("setSelectedBoard", msg.boards[0]); + } + if (msg.projectName) { + store.commit("setProjectName", msg.projectName); + } + if (msg.containerDirectory) { + store.commit("setContainerDirectory", msg.containerDirectory); + } + if (msg.serialPortList) { + store.commit("setSerialPortList", msg.serialPortList); + store.commit("setSelectedPort", msg.serialPortList[0]); + } + if (msg.targetList) { + store.commit("setTargetList", msg.targetList); + store.commit("setTarget", msg.targetList[0]); + } + if (msg.templates) { + store.commit("setTemplates", msg.templates); + store.commit("setSelectedTemplate", msg.templates[0]); + } + if (msg.openOcdConfigFiles) { + store.commit("setOpenOcdConfigFiles", msg.openOcdConfigFiles); + } + break; + case "setContainerDirectory": + if (msg.projectDirectory) { + store.commit("setContainerDirectory", msg.projectDirectory); + } + break; + case "setExampleDetail": + if (msg.templateDetail) { + store.commit("setTemplateDetail", msg.templateDetail); + } + break; + default: + break; + } +}); diff --git a/src/views/new-project/store/index.ts b/src/views/new-project/store/index.ts new file mode 100644 index 000000000..f00f01eca --- /dev/null +++ b/src/views/new-project/store/index.ts @@ -0,0 +1,199 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Vue from "vue"; +import Vuex, { ActionTree, MutationTree, StoreOptions } from "vuex"; +import { IComponent } from "../../../espIdf/idfComponent/IdfComponent"; +import { IExample } from "../../../examples/Example"; + +Vue.use(Vuex); + +declare var acquireVsCodeApi: any; +let vscode: any; +try { + vscode = acquireVsCodeApi(); +} catch (error) { + console.error(error); +} + +export interface IdfBoard { + name: string; + description: string; + target: string; + configFiles: string; +} + +export interface IdfTarget { + id: string; + name: string; + openOcdFiles: string; +} + +export interface IState { + boards: IdfBoard[]; + components: IComponent[]; + containerDirectory: string; + currentComponentPath: string; + openOcdConfigFiles: string; + projectName: string; + selectedBoard: IdfBoard; + selectedPort: string; + selectedTemplate: IExample; + serialPortList: string[]; + target: IdfTarget; + targetList: IdfTarget[]; + templateDetail: string; + templates: IExample[]; +} + +const newProjectState: IState = { + boards: [], + components: [], + containerDirectory: "", + currentComponentPath: "", + openOcdConfigFiles: "", + projectName: "project-name", + selectedBoard: null, + selectedPort: "", + selectedTemplate: null, + serialPortList: [], + target: null, + targetList: [], + templateDetail: "", + templates: [], +}; + +export const mutations: MutationTree = { + addComponent(state, newComponent) { + const newState = state; + newState.components.push(newComponent); + state = { ...newState }; + }, + setBoards(state, boards: IdfBoard[]) { + const newState = state; + newState.boards = boards; + state = { ...newState }; + }, + setContainerDirectory(state, containerDir: string) { + const newState = state; + newState.containerDirectory = containerDir; + state = { ...newState }; + }, + setCurrentComponentPath(state, componentPath: string) { + const newState = state; + newState.currentComponentPath = componentPath; + state = { ...newState }; + }, + setOpenOcdConfigFiles(state, openOcdConfigFiles: string) { + const newState = state; + newState.openOcdConfigFiles = openOcdConfigFiles; + state = { ...newState }; + }, + setProjectName(state, projectName: string) { + const newState = state; + newState.projectName = projectName; + state = { ...newState }; + }, + setSelectedBoard(state, board: IdfBoard) { + const newState = state; + newState.selectedBoard = board; + state = { ...newState }; + }, + setSelectedPort(state, port: string) { + const newState = state; + newState.selectedPort = port; + state = { ...newState }; + }, + setSelectedTemplate(state, template: IExample) { + const newState = state; + newState.selectedTemplate = template; + state = { ...newState }; + }, + setSerialPortList(state, serialPortList: string[]) { + const newState = state; + newState.serialPortList = serialPortList; + state = { ...newState }; + }, + setTarget(state, target: IdfTarget) { + const newState = state; + console.log(target); + state.target = target; + state = { ...newState }; + }, + setTargetList(state, targetList: IdfTarget[]) { + const newState = state; + newState.targetList = targetList; + state = { ...newState }; + }, + setTemplateDetail(state, templateDetail: string) { + const newState = state; + newState.templateDetail = templateDetail; + state = { ...newState }; + }, + setTemplates(state, templates: IExample[]) { + const newState = state; + newState.templates = templates; + state = { ...newState }; + }, + removeComponent(state, component: IComponent) { + const newState = state; + const index = newState.components.indexOf(component); + newState.components.splice(index, 1); + state = { ...newState }; + }, +}; + +export const actions: ActionTree = { + createProject(context) { + vscode.postMessage({ + command: "createProject", + components: context.state.components, + containerFolder: context.state.containerDirectory, + openOcdConfigFiles: context.state.openOcdConfigFiles, + port: context.state.selectedPort, + projectName: context.state.projectName, + target: context.state.target, + template: context.state.selectedTemplate, + }); + }, + getTemplateDetail(context, payload) { + vscode.postMessage({ + command: "getTemplateDetail", + path: payload.pathToOpen, + }); + }, + openComponentFolder() { + vscode.postMessage({ + command: "loadComponent", + }); + }, + openProjectDirectory() { + vscode.postMessage({ + command: "openProjectDirectory", + }); + }, + requestInitialValues() { + vscode.postMessage({ + command: "requestInitValues", + }); + }, +}; + +export const newProjectStoreOptions: StoreOptions = { + actions, + mutations, + state: newProjectState, +}; + +export const store = new Vuex.Store(newProjectStoreOptions); diff --git a/webpack.config.js b/webpack.config.js index b0fd96ccd..75faef059 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -85,6 +85,13 @@ const webViewConfig = { "nvs-partition-table", "main.ts" ), + newProject: path.resolve( + __dirname, + "src", + "views", + "new-project", + "main.ts" + ), sysView: path.resolve(__dirname, "src", "views", "system-view", "main.ts"), partition_table: path.resolve( __dirname,