├── .DS_Store ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── README.md ├── images ├── arduino-logo.svg ├── compile.svg └── upload.svg ├── package.json ├── src ├── arduino │ └── index.ts ├── config │ └── index.ts └── extension.ts ├── test ├── extension.test.ts └── index.ts ├── tsconfig.json └── vsc-extension-quickstart.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steve3d/arduino-vscode/7ee39fe9fa8ee3a2f35eea16e33495d03332befa/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | *.vsix 4 | .vscode-test/ 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ], 14 | "preLaunchTask": "npm" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], 25 | "preLaunchTask": "npm" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version 10 | "vsicons.presets.angular": false, 11 | "files.trimTrailingWhitespace": true 12 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isWatching": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json 9 | vsc-extension-quickstart.md 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "arduino-vscode" extension will be documented in this file. 3 | 4 | ## 0.2.2 - 2017-03-21 5 | - Display size summary after building. #15 6 | - and some improvement for setting documentation 7 | 8 | ## 0.2.1 - 2017-02-14 9 | - bug fix 10 | 11 | ## 0.2.0 - 2017-02-06 12 | - support custom uploader 13 | 14 | ## 0.1.4 - 2017-02-04 15 | - make the verbose option the real verbose. 16 | 17 | ## 0.1.3 - 2017-02-04 18 | - added a verbose option, when build/upload, the build/upload flags will also shown in the output 19 | - make sure the file flag in build/upload is placed as last flag. 20 | 21 | ## 0.1.2 - 2017-01-23 22 | - fix a bug of update configuration of serialPort 23 | 24 | ## 0.1.1 - 2017-01-14 25 | - add a extension logo 26 | 27 | ## 0.1.0 - 2017-01-14 28 | - added linux support 29 | - add diagnostic support for failed build 30 | - rewrite the Config class 31 | - automatically use the default installlation of Arduino IDE 32 | - only need to set the serial port setting if Arduino IDE installed with default folder on macOS and Windows 33 | 34 | ## [Unreleased] 35 | - Initial release -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino Support for Visual Studio Code (abandoned) 2 | 3 | 4 | The Arduino extension makes it easy to build and upload Arduino sketch from Visual Studio Code. 5 | 6 | * C++ intellisense with cpptools 7 | * Build/Compile sketch with VSCode 8 | * Upload sketch 9 | * Custom uploader support 10 | 11 | ## Features 12 | 13 | Integrate the Arduino IDE for VSCode, with this extension, you can edit sketch with intellisense and use the VSCode to compile and upload to your board. 14 | 15 | ## Requirements 16 | 17 | * Recent version of Arduino IDE (at least v1.8.x) 18 | * ms-vscode.cpptools extension for C++ intellisense to work 19 | * An Arduino compatiable board to develop. :) 20 | * Manually associate the .ino file to C++ if needed. 21 | 22 | ## Extension Settings 23 | 24 | This extension contributes the following settings: 25 | 26 | * `arduino.idePath`: Specify where the Arduino IDE is (Note: Absolute path only). 27 | * `arduino.libraryPath`: Specifies the serial port board are connected (Note: Absolute path only). 28 | * `arduino.fqbn`: Specifies the board type to use (fully qualified board name). 29 | * `arduino.serialPort`: Specifies the serial port borad are connected. (Windows: COMx, macOS: /dev/cu./dev/cu.usbmodemxxxx, Linux: /dev/ttyUSBxx) 30 | * `arduino.warnPercentage`: set to `blah` to do something 31 | * `arduino.compileOptions`: Additional options for compile the sketch 32 | * `arduino.uploader`: Custom uploader for extra board types 33 | * `arduino.uploadOptions`: Additional options for avrdude upload the compiled sketch 34 | * `arduino.partno`: Specify AVR device (Upload only) 35 | * `arduino.programmer`: Specify programmer type (Upload only) 36 | * `arduino.baudrate`: Override RS-232 baud rate (Upload only) 37 | * `arduino.verbose`: Use verbose output when build and upload. 38 | 39 | Please note, every path here should be absolute path, relative path won't work. 40 | 41 | ## Custom uploader support 42 | 43 | If you need to develop on a custom board like NodeMCU, you need to specify the uploader `esptool` to `arduino.uploader` option. 44 | Once you set the uploader, the extension will use the `arduino.uploadOptions` with this uploader. And there are some replacement arguments for the `uploadOptions`: 45 | 46 | - `$BAUDRATE` will be replaced to the baudrate option 47 | - `$SERIALPORT` will be replaced to the serial port option 48 | - `$TARGET` will be replaced to the compiled object, (Note, this option contains no `bin` or `hex` extension). 49 | 50 | For example: 51 | 52 | ``` 53 | "arduino.fqbn": "esp8266:esp8266:nodemcu:CpuFrequency=80,UploadSpeed=115200,FlashSize=4M3M", 54 | "arduino.uploader" : "/Users/steve/Library/Arduino15/packages/esp8266/tools/esptool/0.4.9/esptool", 55 | "arduino.uploadOptions": "-vv -cd ck -cb $BAUDRATE -cp $SERIALPORT -ca 0x00000 -cf $TARGET.bin", 56 | "arduino.compileOptions": "-hardware /Users/steve/Library/Arduino15/packages -tools /Users/steve/Library/Arduino15/packages -prefs=runtime.tools.esptool.path=/Users/steve/Library/Arduino15/packages/esp8266/tools/esptool/0.4.9 -prefs=runtime.tools.xtensa-lx106-elf-gcc.path=/Users/steve/Library/Arduino15/packages/esp8266/tools/xtensa-lx106-elf-gcc/1.20.0-26-gb404fb9-2 -prefs=runtime.tools.mkspiffs.path=/Users/steve/Library/Arduino15/packages/esp8266/tools/mkspiffs/0.1.2" 57 | ``` 58 | 59 | You can get these options by enabling verbose output when you compile/upload in Arduino IDE. 60 | 61 | ## Known Issues 62 | 63 | Linux support not tested, but it should work. 64 | Custom uploader not tested, because I don't have a board that requires or implements a custom uploader to test. 65 | 66 | ## Change log 67 | 68 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 69 | 70 | ----------------------------------------------------------------------------------------------------------- 71 | 72 | **Enjoy!** 73 | -------------------------------------------------------------------------------- /images/arduino-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 56 | 59 | 60 | 65 | 68 | 72 | 73 | 78 | 79 | 80 | 81 | 88 | 91 | 95 | 96 | 97 | 107 | 111 | 115 | 119 | 123 | 127 | 131 | 135 | 139 | 140 | 141 | 146 | 150 | 154 | 155 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /images/compile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /images/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arduino-vscode", 3 | "displayName": "Arduino", 4 | "description": "Arduino Support for VSCode", 5 | "version": "0.2.2", 6 | "publisher": "steveyin", 7 | "license": "LGPL-3.0", 8 | "icon": "images/arduino-logo.svg", 9 | "preview": true, 10 | "homepage": "https://github.com/steve3d/arduino-vscode/blob/master/README.md", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/steve3d/arduino-vscode.git" 14 | }, 15 | "engines": { 16 | "vscode": "^1.5.0" 17 | }, 18 | "extensionDependencies": [ 19 | "ms-vscode.cpptools" 20 | ], 21 | "categories": [ 22 | "Other" 23 | ], 24 | "activationEvents": [ 25 | "onCommand:extension.build", 26 | "onCommand:extension.rebuild", 27 | "onCommand:extension.clean", 28 | "onCommand:extension.upload", 29 | "onCommand:extension.buildAndUpload", 30 | "onCommand:extension.rebuildAndUpload", 31 | "onCommand:extension.initialize" 32 | ], 33 | "main": "./out/src/extension", 34 | "contributes": { 35 | "configuration": { 36 | "type": "object", 37 | "title": "Arduino configuration", 38 | "properties": { 39 | "arduino.idePath": { 40 | "type": ["string"], 41 | "default": null, 42 | "description": "Specify where the Arduino IDE is (Absolute path only)." 43 | }, 44 | "arduino.libraryPath": { 45 | "type": ["string"], 46 | "default": null, 47 | "description": "Specifies the path to your library directory. Default is used path to /Documents/Arduino/libraries (Absolute path only)." 48 | }, 49 | "arduino.fqbn": { 50 | "type": ["string"], 51 | "default": "arduino:avr:uno", 52 | "description": "Specifies the board type to use (fully qualified board name). \nYou can find this value in Arduino IDE output with checked \"verbose\" option in preferences or in boards.txt file in Arduino IDE directory. \n\nFormat: {packageID}:{platformID}:{boardID}:{boardOptions}. \nPlatform ID is usually name of directory with board.txt and package ID is usually name of directory contains parent of board.txt. \nBoard ID is prefix with board name from board.txt (part before first dot). \nIf board has different versions then you must specify BoardOptions. Usualy you must provide CPU variant in format cpu=version where version is the part from board.txt after part \"cpu\". \n\nFor example for Arduino Pro Mini (Atmega328p, 8MHz) we have in board.txt entry: pro.menu.cpu.8MHzatmega168=ATmega168 (3.3V, 8 MHz). Then full qualified name is: arduino:avr:pro:cpu=8MHzatmega328" 53 | }, 54 | "arduino.serialPort": { 55 | "type": ["string"], 56 | "default": null, 57 | "description": "Specifies the serial port board are connected. (Windows: COMx, macOS: /dev/cu./dev/cu.usbmodemxxxx, Linux: /dev/ttyUSBxx)" 58 | }, 59 | "arduino.warnMode": { 60 | "type":["string"], 61 | "default": "none", 62 | "description": "Specifies warnig verbosity. Possible values: \"none\", \"default\", \"more\", \"all\"." 63 | }, 64 | "arduino.warnPercentage": { 65 | "type": ["number"], 66 | "default": 75, 67 | "description": "" 68 | }, 69 | "arduino.compileOptions": { 70 | "type": ["string"], 71 | "default": "", 72 | "description": "Additional options for compile" 73 | }, 74 | "arduino.uploader": { 75 | "type": ["string"], 76 | "default": null, 77 | "description": "Custom uploader for extra board types (Note: Set full upload options with this)" 78 | }, 79 | "arduino.uploadOptions": { 80 | "type": ["string"], 81 | "default": "-D -q", 82 | "description": "Additional options for avrdude upload" 83 | }, 84 | "arduino.partno": { 85 | "type": ["string"], 86 | "default": "atmega328p", 87 | "description": "Upload: Specify AVR device" 88 | }, 89 | "arduino.programmer": { 90 | "type": ["string"], 91 | "default": "arduino", 92 | "description": "Upload: Specify programmer type" 93 | }, 94 | "arduino.baudrate": { 95 | "type": ["number"], 96 | "default": 115200, 97 | "description": "Upload: Override RS-232 baud rate" 98 | }, 99 | "arduino.verbose": { 100 | "type": ["boolean"], 101 | "default": false, 102 | "description": "Use verbose output in build and upload showing the build/upload flags." 103 | } 104 | } 105 | }, 106 | "commands": [ 107 | { 108 | "command": "extension.build", 109 | "title": "Arduino: Build", 110 | "icon": "./images/compile.svg" 111 | }, 112 | { 113 | "command": "extension.rebuild", 114 | "title": "Arduino: Rebuild" 115 | }, 116 | { 117 | "command": "extension.clean", 118 | "title": "Arduino: Clean" 119 | }, 120 | { 121 | "command": "extension.upload", 122 | "title": "Arduino: Upload", 123 | "icon": "./images/upload.svg" 124 | }, 125 | { 126 | "command": "extension.buildAndUpload", 127 | "title": "Arduino: Build & Upload" 128 | }, 129 | { 130 | "command": "extension.rebuildAndUpload", 131 | "title": "Arduino: Rebuild & Upload" 132 | }, 133 | { 134 | "command": "extension.initialize", 135 | "title": "Arduino: Initialize C++ Intellisense" 136 | } 137 | ], 138 | "menus": { 139 | "editor/title": [ 140 | { 141 | "when": "resourceLangId == cpp", 142 | "command": "extension.build", 143 | "group": "navigation" 144 | }, 145 | { 146 | "when": "resourceLangId == cpp", 147 | "command": "extension.upload", 148 | "group": "navigation" 149 | } 150 | ] 151 | } 152 | }, 153 | "scripts": { 154 | "vscode:prepublish": "tsc -p ./", 155 | "compile": "tsc -watch -p ./", 156 | "postinstall": "node ./node_modules/vscode/bin/install", 157 | "test": "node ./node_modules/vscode/bin/test" 158 | }, 159 | "devDependencies": { 160 | "typescript": "^2.0.3", 161 | "vscode": "^1.0.0", 162 | "mocha": "^2.3.3", 163 | "@types/node": "^6.0.40", 164 | "@types/mocha": "^2.2.32" 165 | } 166 | } -------------------------------------------------------------------------------- /src/arduino/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode' 4 | import * as fs from 'fs'; 5 | import * as path from 'path' 6 | import * as child_process from 'child_process' 7 | import * as os from 'os'; 8 | import { EventEmitter } from "events"; 9 | import { ConfigUtil } from '../config' 10 | 11 | export class ArduinoVS { 12 | private config: ConfigUtil; 13 | private updateSettings = `Please update workspace settings, Can't continue without Arduino IDE and the serial port.`; 14 | private builtEvent = new EventEmitter(); 15 | private building = false; 16 | private uploading = false; 17 | private diagnostics: vscode.DiagnosticCollection; 18 | private errors: Object; 19 | 20 | constructor(private output: vscode.OutputChannel) { 21 | this.config = new ConfigUtil(); 22 | 23 | this.diagnostics = vscode.languages.createDiagnosticCollection(); 24 | } 25 | 26 | get needRebuild(): boolean { 27 | if (vscode.window.activeTextEditor.document.isDirty) 28 | return true; 29 | 30 | let hexPath = this.config.hexPath; 31 | if (!fs.existsSync(hexPath)) 32 | return true; 33 | 34 | let hexStat = fs.lstatSync(hexPath); 35 | let sourceStat = fs.lstatSync(vscode.window.activeTextEditor.document.fileName); 36 | 37 | return sourceStat.mtime > hexStat.mtime; 38 | } 39 | 40 | ouputMessage(data: string) { 41 | this.output.append(data); 42 | } 43 | 44 | initialize() { 45 | if (!this.config.hasIdePath || !this.config.hasSerialPort) { 46 | vscode.window.showErrorMessage(this.updateSettings); 47 | return; 48 | } 49 | 50 | let cppJsonFile = path.join(vscode.workspace.rootPath, '.vscode/c_cpp_properties.json'); 51 | fs.writeFile(cppJsonFile, JSON.stringify(this.config.cppConfig, null, 4), 52 | () => vscode.window.showInformationMessage("Successfully updated C++ Intellisense settings.")); 53 | } 54 | 55 | showProgress(data: string, item: vscode.StatusBarItem, status: string) { 56 | let output = data.split('\n'); 57 | output.forEach(line => { 58 | if(line == '') 59 | return; 60 | 61 | let parts = line.split(' ||| '); 62 | if(parts.length == 3) { 63 | let value = parts[2].substr(1, parts[2].length-2); 64 | if(line.includes('Progress')) 65 | item.text = status + value + '%'; 66 | else { 67 | let values = value.split(' '); 68 | if(values.length == 3) 69 | this.output.append(`Sketch uses ${values[0]} bytes (${values[2]}%) of program storage space. Maximum is ${values[1]} bytes.\n`) 70 | else if(values.length == 4) 71 | this.output.append(`Global variables use ${values[0]} bytes (${values[2]}%) of dynamic memory, leaving ${values[3]} bytes for local variables. Maximum is ${values[1]} bytes.\n`) 72 | else 73 | this.output.append(line + '\n'); 74 | } 75 | 76 | } else 77 | this.output.append(line + '\n'); 78 | }) 79 | } 80 | 81 | addDiagnostic(status: RegExpMatchArray) { 82 | if(status == null) 83 | return; 84 | 85 | if(!this.errors.hasOwnProperty(status[1])) 86 | this.errors[status[1]] = []; 87 | let line = parseInt(status[2], 10)-1; 88 | let column = parseInt(status[3], 10); 89 | let diag = new vscode.Diagnostic( 90 | new vscode.Range(line, column, line, column), 91 | status[5], 92 | status[4] == 'error' ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Warning); 93 | 94 | this.errors[status[1]].push(diag); 95 | } 96 | 97 | build() { 98 | if(this.building) { 99 | vscode.window.showErrorMessage('Building in progress, please wait a moment.'); 100 | return; 101 | } 102 | 103 | let document = vscode.window.activeTextEditor.document; 104 | 105 | if (!this.config.hasIdePath) { 106 | vscode.window.showErrorMessage('Can not build anything without Arduino IDE, please update settings.'); 107 | return; 108 | } 109 | 110 | if (document.isUntitled) { 111 | vscode.window.showInformationMessage('Please save the file first!'); 112 | } 113 | 114 | if (document.isDirty) { 115 | document.save() 116 | .then((success) => console.log('save status:', success)); 117 | } 118 | 119 | this.errors = {}; 120 | this.diagnostics.clear(); 121 | this.output.clear(); 122 | this.output.append('============== Begin to compile. ==============\n') 123 | if(this.config.verbose) { 124 | this.output.append(this.config.builder + ' '); 125 | this.output.append(this.config.buildArgs.join(' ') + '\n'); 126 | } 127 | this.building = true; 128 | let spawn = child_process.spawn(this.config.builder, this.config.buildArgs); 129 | let statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right); 130 | let status = 'Compiling '; 131 | statusBarItem.text = status + '0%'; 132 | statusBarItem.show(); 133 | 134 | spawn.stdout.on('data', data => this.showProgress(String.fromCharCode.apply(null, data), 135 | statusBarItem, 136 | status)); 137 | 138 | spawn.stderr.on('data', data => { 139 | let error:string = String.fromCharCode.apply(null, data); 140 | 141 | error.split('\n') 142 | .forEach(line => this.addDiagnostic(line.match(/(.*):(\d+):(\d+):\s+(warning|error):\s+(.*)/))); 143 | 144 | this.output.append(error); 145 | }); 146 | 147 | spawn.on('close', (result) => { 148 | // If build successed then we display summary output size. 149 | if (result == 0) 150 | this.summarySize((summaryResult) => this.onBuildFinished(result, statusBarItem)); 151 | else 152 | this.onBuildFinished(result, statusBarItem); 153 | }); 154 | } 155 | 156 | onBuildFinished(result: number, statusBarItem: vscode.StatusBarItem) { 157 | this.builtEvent.emit('build', result); 158 | statusBarItem.dispose(); 159 | this.building = false; 160 | if(result) 161 | this.output.show(true); 162 | 163 | let items = []; 164 | for (let key in this.errors) { 165 | if (this.errors.hasOwnProperty(key)) { 166 | items.push([vscode.Uri.file(key), this.errors[key]]); 167 | } 168 | } 169 | 170 | this.diagnostics.set(items); 171 | 172 | if(result) 173 | vscode.window.showErrorMessage('Build failed.'); 174 | } 175 | 176 | deleteFolderRecursive(path) { 177 | var files = []; 178 | if (fs.existsSync(path)) { 179 | files = fs.readdirSync(path); 180 | files.forEach((file) => { 181 | var curPath = path + "/" + file; 182 | if (fs.lstatSync(curPath).isDirectory()) { // recurse 183 | this.deleteFolderRecursive(curPath); 184 | } else { // delete file 185 | fs.unlinkSync(curPath); 186 | } 187 | }); 188 | 189 | fs.rmdirSync(path); 190 | } 191 | }; 192 | 193 | clean(info = true) { 194 | if (fs.existsSync(this.config.buildPath)) { 195 | this.deleteFolderRecursive(this.config.buildPath); 196 | if(info) 197 | vscode.window.showInformationMessage('Intermediate ouput folder cleaned.'); 198 | } else if(info) 199 | vscode.window.showInformationMessage('Nothing need to clean.'); 200 | } 201 | 202 | rebuild() { 203 | this.clean(false); 204 | this.build(); 205 | } 206 | 207 | upload() { 208 | if(this.uploading || this.building) { 209 | vscode.window.showErrorMessage('Building or uploading in progress. Please wait'); 210 | return; 211 | } 212 | 213 | if(!this.config.hasSerialPort) { 214 | vscode.window.showErrorMessage('Can not upload without serial port specified, please update settings.'); 215 | return; 216 | } 217 | 218 | if (this.needRebuild) { 219 | this.build(); 220 | this.builtEvent.on('build', result => { 221 | if(result == 0) 222 | this._realUpload(); 223 | }) 224 | } else 225 | this._realUpload(); 226 | } 227 | 228 | private _realUpload() { 229 | this.uploading = true; 230 | let spawn = child_process.spawn(this.config.avrdude, this.config.uploadArgs); 231 | 232 | this.output.append('\n============== Begin to upload. ==============\n'); 233 | if(this.config.verbose) { 234 | this.output.append(this.config.avrdude + ' '); 235 | this.output.append(this.config.uploadArgs.join(' ') + '\n'); 236 | } 237 | 238 | spawn.stdout.on('data', data => this.output.append(String.fromCharCode.apply(null, data))); 239 | spawn.stderr.on('data', data => this.output.append(String.fromCharCode.apply(null, data))); 240 | spawn.on('close', (result) => { 241 | this.uploading = false; 242 | this.output.append(`\nUpload ${result ? 'failed' : 'success'}.\n`); 243 | if(result) 244 | this.output.show(true); 245 | }); 246 | 247 | this.builtEvent.removeAllListeners(); 248 | } 249 | 250 | buildAndUpload() { 251 | this.build(); 252 | 253 | this.builtEvent.on('build', result => { 254 | console.log('build result', result); 255 | if(!result) 256 | this.upload(); 257 | else 258 | vscode.window.showErrorMessage('Build failed, Can not upload.'); 259 | 260 | }); 261 | } 262 | 263 | rebuildAndUpload() { 264 | this.rebuild(); 265 | 266 | this.builtEvent.on('build', result => { 267 | if(!result) 268 | this.upload(); 269 | else 270 | vscode.window.showErrorMessage('Build failed, Can not upload.'); 271 | 272 | }); 273 | } 274 | 275 | summarySize(onEnd: (resultCode: number) => void): void { 276 | let spawn = child_process.spawn(this.config.avrsize, this.config.sizeArgs); 277 | 278 | // Add empty line 279 | this.output.appendLine(''); 280 | 281 | spawn.stdout.on('data', data => { 282 | let msg: string = String.fromCharCode.apply(null, data); 283 | this.output.append(msg); 284 | }); 285 | 286 | spawn.stderr.on('data', data => { 287 | let error:string = String.fromCharCode.apply(null, data); 288 | 289 | error.split('\n') 290 | .forEach(line => this.addDiagnostic(line.match(/(.*):(\d+):(\d+):\s+(warning|error):\s+(.*)/))); 291 | 292 | this.output.append(error); 293 | }); 294 | 295 | spawn.on('close', (result) => onEnd(result)); 296 | } 297 | } -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import * as os from 'os'; 5 | import * as path from 'path'; 6 | import * as fs from 'fs'; 7 | import * as process from 'process'; 8 | 9 | 10 | export class ConfigUtil { 11 | 12 | private _convertSeprator = false; 13 | private _idePath: string; 14 | private _libraryPath: string; 15 | private _serialPort: string; 16 | private _fqbn: string; 17 | private _warnMode: string; 18 | private _warnPercentage: number; 19 | private _partno: string; 20 | private _programmer: string; 21 | private _baudrate: number; 22 | private _compileOptions: string[]; 23 | private _uploader: string = null; 24 | private _uploadOptions: string[]; 25 | private _verbose: boolean; 26 | 27 | constructor() { 28 | if(os.type() == 'Windows_NT') 29 | this._convertSeprator = true; 30 | this._updateSettings(); 31 | vscode.workspace.onDidChangeConfiguration((e) => this._updateSettings()); 32 | } 33 | 34 | private _updateSettings() { 35 | let config = vscode.workspace.getConfiguration('arduino'); 36 | this._fqbn = config.get('fqbn'); 37 | this._warnMode = config.get('warnMode'); 38 | this._warnPercentage = config.get('warnPercentage'); 39 | this._partno = config.get('partno'); 40 | this._programmer = config.get('programmer'); 41 | this._baudrate = config.get('baudrate'); 42 | this._serialPort = config.get('serialPort'); 43 | this._verbose = config.get('verbose'); 44 | 45 | this._compileOptions = config.get('compileOptions', '').split(' ').filter(x => x != ''); 46 | 47 | this._uploader = config.get('uploader'); 48 | this._uploadOptions = config.get('uploadOptions', '').split(' ').filter(x => x != ''); 49 | 50 | this._updateIdePath(config); 51 | this._updateLibraryPath(config); 52 | } 53 | 54 | private _updateIdePath(config: vscode.WorkspaceConfiguration) { 55 | this._idePath = config.get('idePath'); 56 | 57 | switch(os.type()) { 58 | case 'Windows_NT': 59 | if(this._idePath == null) 60 | this._idePath = 'C:\\Program Files (x86)\\Arduino'; 61 | 62 | if(!fs.existsSync(path.join(this._idePath, 'arduino-builder.exe'))) 63 | this._idePath = null 64 | break; 65 | case 'Linux': 66 | if(!fs.existsSync(path.join(this._idePath, 'arduino-builder'))) 67 | this._idePath = null 68 | break; 69 | case 'Darwin': 70 | if(this._idePath == null) 71 | this._idePath = '/Applications/Arduino.app' 72 | 73 | if(fs.existsSync(path.join(this._idePath, 'Contents/Java/arduino-builder'))) 74 | this._idePath = path.join(this._idePath, 'Contents/Java'); 75 | else 76 | this._idePath = null; 77 | 78 | break; 79 | default: 80 | this._idePath = null; 81 | } 82 | 83 | // custom uploader must also exists 84 | if(this._uploader && !fs.existsSync(this._uploader)) 85 | this._uploader = null; 86 | } 87 | 88 | private _updateLibraryPath(config: vscode.WorkspaceConfiguration) { 89 | this._libraryPath = config.get('libraryPath'); 90 | 91 | if(this._libraryPath == null) { 92 | this._libraryPath = path.join(os.homedir(), 'Documents/Arduino/libraries'); 93 | if(!fs.existsSync(this._libraryPath)) 94 | this._libraryPath = os.homedir(); 95 | } 96 | } 97 | 98 | get hasIdePath(): boolean { 99 | return this._idePath != null; 100 | } 101 | 102 | get hasSerialPort(): boolean { 103 | return this._serialPort != null; 104 | } 105 | 106 | get basename(): string { 107 | return path.basename(vscode.window.activeTextEditor.document.fileName, '.ino') 108 | } 109 | 110 | get filename(): string { 111 | return path.basename(vscode.window.activeTextEditor.document.fileName); 112 | } 113 | 114 | get hexPath(): string { 115 | return path.join(vscode.workspace.rootPath, `.build/${this.basename}/${this.filename}`); 116 | } 117 | 118 | get buildPath(): string { 119 | let document = vscode.window.activeTextEditor.document; 120 | 121 | let buildPath = path.join(vscode.workspace.rootPath, `.build`); 122 | 123 | if (!fs.existsSync(buildPath)) 124 | fs.mkdirSync(buildPath); 125 | 126 | buildPath = path.join(buildPath, this.basename); 127 | if (!fs.existsSync(buildPath)) 128 | fs.mkdirSync(buildPath); 129 | 130 | return buildPath; 131 | } 132 | 133 | get cppConfig(): any { 134 | let includes = [ 135 | path.join(this._idePath, 'hardware/arduino/avr/cores/arduino'), 136 | path.join(this._idePath, 'hardware/arduino/avr/libraries'), 137 | path.join(this._idePath, 'hardware/arduino/avr/variants/standard'), 138 | path.join(this._idePath, 'libraries') 139 | ]; 140 | 141 | if (this._libraryPath) { 142 | includes.push(this._libraryPath); 143 | // libs path 144 | fs.readdirSync(this._libraryPath) 145 | .map(item => path.join(this._libraryPath, item)) 146 | .filter(item => fs.lstatSync(item).isDirectory()) 147 | .forEach(item => includes.push(item)); 148 | } 149 | 150 | if(this._convertSeprator) 151 | includes = includes.map(x => x.replace(/\//g, '\\')); 152 | 153 | return { 154 | configurations: [ 155 | { 156 | name: 'Arduino', 157 | includePath: includes, 158 | browse: { 159 | 'limitSymbolsToIncludedHeaders': true, 160 | 'databaseFilename': '' 161 | } 162 | } 163 | ] 164 | }; 165 | } 166 | 167 | get buildArgs(): string[] { 168 | let args = [ 169 | '-compile', 170 | '-logger', this._verbose ? 'human' : 'machine', 171 | '-hardware', `${this._idePath}/hardware`, 172 | '-tools', `${this._idePath}/tools-builder`, 173 | '-tools', `${this._idePath}/hardware/tools/avr`, 174 | '-built-in-libraries', `${this._idePath}/libraries`, 175 | '-libraries', `${this._libraryPath}`, 176 | `-fqbn=${this._fqbn}`, 177 | '-build-path', this.buildPath, 178 | `-warnings=${this._warnMode}`, 179 | `-prefs=build.warn_data_percentage=${this._warnPercentage}`, 180 | `-prefs=runtime.tools.avr-gcc.path=${this._idePath}/hardware/tools/avr`, 181 | `-prefs=runtime.tools.avrdude.path=${this._idePath}/hardware/tools/avr`, 182 | `-prefs=runtime.tools.arduinoOTA.path=${this._idePath}/hardware/tools/avr`, 183 | ].concat(this._compileOptions); 184 | 185 | if(this._verbose) 186 | args.push('-verbose'); 187 | 188 | args.push(vscode.window.activeTextEditor.document.fileName); 189 | 190 | return this._convertSeprator ? args.map(x => x.replace(/\//g, '\\')) : args; 191 | } 192 | 193 | get uploadArgs(): string[] { 194 | let args: string[]; 195 | if(this._uploader) { 196 | args = this._uploadOptions 197 | .map(x => x.replace(/\$(TARGET|BAUDRATE|SERIALPORT)/, 198 | (match, p1) => { 199 | switch(match) { 200 | case '$TARGET': 201 | return this.hexPath; 202 | case '$BAUDRATE': 203 | return this._baudrate.toString(); 204 | case '$SERIALPORT': 205 | return this._serialPort; 206 | } 207 | 208 | return match; 209 | } )); 210 | } else { 211 | args = [`-C${this._idePath}/hardware/tools/avr/etc/avrdude.conf`, 212 | `-p${this._partno}`, 213 | `-c${this._programmer}`, 214 | `-P${this._serialPort}`, 215 | `-b${this._baudrate}`, 216 | ].concat(this._uploadOptions, 217 | `-Uflash:w:${this.hexPath}.hex:i`); 218 | } 219 | 220 | return this._convertSeprator ? args.map(x => x.replace(/\//g, '\\')) : args; 221 | } 222 | 223 | get sizeArgs(): string[] { 224 | let args: string[]; 225 | args = ['-C', // Used -C option, because the output is immediately in human readable format 226 | `--mcu=${this._partno}`, 227 | `${this.hexPath}.elf`]; 228 | 229 | return this._convertSeprator ? args.map(x => x.replace(/\//g, '\\')) : args; 230 | } 231 | 232 | get builder(): string { 233 | let builder = path.join(this._idePath, 'arduino-builder'); 234 | 235 | if(this._convertSeprator) 236 | builder = builder.replace(/\//g, '\\') + '.exe'; 237 | 238 | return builder; 239 | } 240 | 241 | get avrdude(): string { 242 | if(this._uploader) 243 | return this._uploader; 244 | 245 | let avrdude = path.join(this._idePath, 'hardware/tools/avr/bin/avrdude'); 246 | 247 | if(this._convertSeprator) 248 | avrdude = avrdude.replace(/\\/g, '\\') + '.exe'; 249 | 250 | return avrdude; 251 | } 252 | 253 | get avrsize(): string { 254 | let avrsize = path.join(this._idePath, 'hardware/tools/avr/bin/avr-size'); 255 | 256 | if (this._convertSeprator) 257 | avrsize = avrsize.replace(/\\/g, '\\') + '.exe'; 258 | 259 | return avrsize; 260 | } 261 | 262 | get verbose(): boolean { 263 | return this._verbose; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // The module 'vscode' contains the VS Code extensibility API 3 | // Import the module and reference it with the alias vscode in your code below 4 | import * as vscode from 'vscode'; 5 | import { ArduinoVS } from './arduino' 6 | 7 | // this method is called when your extension is activated 8 | // your extension is activated the very first time the command is executed 9 | export function activate(context: vscode.ExtensionContext) { 10 | 11 | // Use the console to output diagnostic information (console.log) and errors (console.error) 12 | // This line of code will only be executed once when your extension is activated 13 | console.log('ArduinoVS actived!'); 14 | 15 | let output = vscode.window.createOutputChannel('Arduino'); 16 | 17 | let arduino = new ArduinoVS(output); 18 | 19 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('extension.build', () => arduino.build())); 20 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('extension.rebuild', () => arduino.rebuild())); 21 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('extension.clean', () => arduino.clean())); 22 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('extension.upload', () => arduino.upload())); 23 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('extension.buildAndUpload', () => arduino.buildAndUpload())); 24 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('extension.rebuildAndUpload', () => arduino.rebuildAndUpload())); 25 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('extension.initialize', () => arduino.initialize())); 26 | } 27 | 28 | // this method is called when your extension is deactivated 29 | export function deactivate() { 30 | } -------------------------------------------------------------------------------- /test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import * as myExtension from '../src/extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", () => { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", () => { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your first VS Code Extension 2 | 3 | ## What's in the folder 4 | * This folder contains all of the files necessary for your extension 5 | * `package.json` - this is the manifest file in which you declare your extension and command. 6 | The sample plugin registers a command and defines its title and command name. With this information 7 | VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | The file exports one function, `activate`, which is called the very first time your extension is 10 | activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 11 | We pass the function containing the implementation of the command as the second parameter to 12 | `registerCommand`. 13 | 14 | ## Get up and running straight away 15 | * press `F5` to open a new window with your extension loaded 16 | * run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World` 17 | * set breakpoints in your code inside `src/extension.ts` to debug your extension 18 | * find output from your extension in the debug console 19 | 20 | ## Make changes 21 | * you can relaunch the extension from the debug toolbar after changing code in `src/extension.ts` 22 | * you can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes 23 | 24 | ## Explore the API 25 | * you can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts` 26 | 27 | ## Run tests 28 | * open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests` 29 | * press `F5` to run the tests in a new window with your extension loaded 30 | * see the output of the test result in the debug console 31 | * make changes to `test/extension.test.ts` or create new test files inside the `test` folder 32 | * by convention, the test runner will only consider files matching the name pattern `**.test.ts` 33 | * you can create folders inside the `test` folder to structure your tests any way you want --------------------------------------------------------------------------------