diff --git a/BRANCHNOTES.md b/BRANCHNOTES.md index 4b37f00d..7f895035 100644 --- a/BRANCHNOTES.md +++ b/BRANCHNOTES.md @@ -108,20 +108,19 @@ During merging I found some bugs within those functions - mainly due to the abov | | :heavy_check_mark: Merging of parsing result and existing file content | | | :heavy_check_mark: Handling inexistent files and folders | | | :heavy_check_mark: Write configuration on change only | -| | :white_check_mark: Option to backup old configurations? | | **Configuration flags** | :heavy_check_mark: Provide global disable flag for IntelliSense auto-config | | | :heavy_check_mark: Provide project specific override for the global flag - most users will likely use the default setup and disable auto-generation for very specific projects | | **Unit tests** | :heavy_check_mark: Basic parser (known boards, match/no match)| | | :white_check_mark: All unit tests in cocopa | | | :white_check_mark: Test with cpp sketches | | **General** | :heavy_check_mark: Review and remove previous attempts messing with `c_cpp_properties.json` or IntelliSense (documented in the [General Tasks](#General-Tasks) section) `*` | -| | :white_check_mark: *Auto-run verify when* | +| | :heavy_check_mark: *Auto-run verify when* | | |     :heavy_check_mark: a) setting a board `*` | | |     :heavy_check_mark: b) changing the board's configuration `*` | | |     :heavy_check_mark: c) selecting another sketch `*` | -| |     :white_check_mark: d) workbench initialized and no `c_cpp_properties.json` found | -| |     :white_check_mark: e) Identify other occasions where this applies (usually when adding new libraries) | -| | :white_check_mark: Hint the user to run *Arduino: Rebuild IntelliSense Configuration*? -> Good moment would be after the workbench initialization -> message in arduino channel | +| |     :heavy_check_mark: d) ~~workbench initialized and no `c_cpp_properties.json` found~~ obsolete: when board and board configuration is loaded on start up the analysis is triggered anyways | +| |     :white_check_mark: e) Identify other occasions where this applies (usually when adding new libraries) -- any suggestions? | +| | :heavy_check_mark: Hint the user to run *Arduino: Rebuild IntelliSense Configuration* -> printing message after each build (verify, upload, ...) | | | :heavy_check_mark: Better build management such that regular builds and analyze builds do not interfere (done, 2020-02-19) `*` | | | :heavy_check_mark: Analyze task queue which fits in the latter (done, 2020-02-19) `*` | | | :heavy_check_mark: Document configuration settings in [README.md](README.md) | @@ -214,6 +213,9 @@ I will list every supporter here, thanks! * When having adding a library folder to the workspace IntelliSense should use the same configuration for it to enable library navigation and code completion. * Optimization: Abort analysis build as soon as compiler statement has been found * Non-IDE unit testing - to eliminate dependency injection use ts-mock-imports for instance +* Hardcoded and scattered constants: + * Load package.json and use values from therein instead of hard coding redundant values like shortcuts (like I did for the IntelliSense message in `arduino.ts`) + * Scan code for other hard coded stuff and take appropriate countermeasures ## Non-categorized Notes ### Integrate upstream changes into fork diff --git a/package-lock.json b/package-lock.json index 67bf0b1f..1507d73d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1559,9 +1559,9 @@ "dev": true }, "cocopa": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/cocopa/-/cocopa-0.0.9.tgz", - "integrity": "sha512-lAV7D7FWZruC8wu0XkStVTI2CnahRXsYeqcbQ6KzPdzwip+SLtV712Qj/q2+sNshi9MNk032lQoQJZisrntkIA==", + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cocopa/-/cocopa-0.0.10.tgz", + "integrity": "sha512-MbBG4k2p2alRlxFoxKfPFLIJZ9y5Mbm6Dc1wDl4jpw4yF4wNwbm2dBUTmeDWcAHbLvYj+HE3jIAB5mxZv0zbuA==", "requires": { "chalk": "^3.0.0", "commander": "^4.1.1", @@ -8599,6 +8599,11 @@ "mime-types": "~2.1.24" } }, + "typed-promisify": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/typed-promisify/-/typed-promisify-0.4.0.tgz", + "integrity": "sha1-reHT0yEwdnuk71OFFixyBpgQXQ8=" + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", diff --git a/package.json b/package.json index cb7de00f..42dfc7c2 100644 --- a/package.json +++ b/package.json @@ -604,7 +604,7 @@ }, "dependencies": { "body-parser": "^1.16.1", - "cocopa": "^0.0.9", + "cocopa": "^0.0.10", "compare-versions": "^3.4.0", "eventemitter2": "^4.1.0", "express": "^4.14.1", @@ -613,6 +613,7 @@ "impor": "^0.1.1", "node-usb-native": "^0.0.18", "properties": "^1.2.1", + "typed-promisify": "^0.4.0", "uuid": "^3.0.1", "vscode-extension-telemetry": "0.1.6", "winreg": "^1.2.3", diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index c3159e7d..e186bd29 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -368,7 +368,7 @@ export class ArduinoApp { const cleanup = async () => { if (cocopa) { - cocopa.conclude(); + await cocopa.conclude(); } if (buildMode === BuildMode.Upload || buildMode === BuildMode.UploadProgrammer) { UsbDetector.getInstance().resumeListening(); @@ -399,15 +399,21 @@ export class ArduinoApp { stdoutCallback, ).then(async () => { await cleanup(); + if (buildMode !== BuildMode.Analyze) { + const cmd = os.platform() === "darwin" + ? "Cmd + Alt + I" + : "Ctrl + Alt + I"; + arduinoChannel.info(`To rebuild your IntelliSense configuration run "${cmd}"`); + } arduinoChannel.end(`${buildMode} sketch '${dc.sketch}'${os.EOL}`); success = true; }, async (reason) => { await cleanup(); - const msg = reason.code ? - `Exit with code=${reason.code}` : - reason.message ? - reason.message : - JSON.stringify(reason); + const msg = reason.code + ? `Exit with code=${reason.code}` + : reason.message + ? reason.message + : JSON.stringify(reason); arduinoChannel.error(`${buildMode} sketch '${dc.sketch}': ${msg}${os.EOL}`); }); diff --git a/src/arduino/intellisense.ts b/src/arduino/intellisense.ts index 76e44fde..3d40553e 100644 --- a/src/arduino/intellisense.ts +++ b/src/arduino/intellisense.ts @@ -2,7 +2,9 @@ // Licensed under the MIT license. import * as ccp from "cocopa"; +import * as fs from "fs"; import * as path from "path"; +import * as tp from "typed-promisify"; import * as constants from "../common/constants"; import { arduinoChannel } from "../common/outputChannel"; @@ -13,7 +15,7 @@ import { VscodeSettings } from "./vscodeSettings"; export interface ICoCoPaContext { callback: (s: string) => void; - conclude: () => void; + conclude: () => Promise; }; /** @@ -58,7 +60,7 @@ export function makeCompilerParserContext(dc: DeviceContext): ICoCoPaContext { const runner = new ccp.Runner(engines); // Set up the callback to be called after parsing - const _conclude = () => { + const _conclude = async () => { if (!runner.result) { arduinoChannel.warning("Failed to generate IntelliSense configuration."); return; @@ -67,6 +69,17 @@ export function makeCompilerParserContext(dc: DeviceContext): ICoCoPaContext { // Normalize compiler and include paths (resolve ".." and ".") runner.result.normalize(); + // Search for Arduino.h in the include paths - we need it for a + // forced include - users expect Arduino symbols to be available + // in main sketch without having to include the header explicitly + const ardHeader = await locateArduinoHeader(runner.result.includes); + const forcedIncludes = ardHeader + ? [ ardHeader ] + : undefined; + if (!ardHeader) { + arduinoChannel.warning("Unable to locate \"Arduino.h\" within IntelliSense include paths."); + } + // TODO: check what kind of result we've got: gcc or other architecture: // and instantiate content accordingly (to be implemented within cocopa) const content = new ccp.CCppPropertiesContentResult(runner.result, @@ -74,7 +87,8 @@ export function makeCompilerParserContext(dc: DeviceContext): ICoCoPaContext { ccp.CCppPropertiesISMode.Gcc_X64, ccp.CCppPropertiesCStandard.C11, // as of 1.8.11 arduino is on C++11 - ccp.CCppPropertiesCppStandard.Cpp11); + ccp.CCppPropertiesCppStandard.Cpp11, + forcedIncludes); try { const pPath = path.join(ArduinoWorkspace.rootPath, constants.CPP_CONFIG_FILE); const prop = new ccp.CCppProperties(); @@ -113,6 +127,49 @@ function makeCompilerParserEngines(dc: DeviceContext) { return [gccParserEngine]; } +/** + * Search directories recursively for a file. + * @param dir Directory where the search should begin. + * @param what The file we're looking for. + * @returns The path of the directory which contains the file else undefined. + */ +async function findDirContaining(dir: string, what: string): Promise { + const readdir = tp.promisify(fs.readdir); + const fsstat = tp.promisify(fs.stat); + + for (const entry of await readdir(dir)) { + const p = path.join(dir, entry); + const s = await fsstat(p); + if (s.isDirectory()) { + const result = await findDirContaining(p, what); + if (result) { + return result; + } + } else if (entry === what) { + return dir; + } + } + return undefined; +}; + +/** + * Tries to find the main Arduino header (i.e. Arduino.h) in the given include + * paths. + * @param includes Array containing all include paths in which we should look + * for Arduino.h + * @returns The full path of the main Arduino header. + */ +async function locateArduinoHeader(includes: string[]) { + const header = "Arduino.h"; + for (const i of includes) { + const result = await findDirContaining(i, header); + if (result) { + return path.join(result, header); + } + } + return undefined; +} + /** * Possible states of AnalysisManager's state machine. */