Skip to content
This repository was archived by the owner on Dec 22, 2024. It is now read-only.

Commit 8840f51

Browse files
IntelliSense auto-analysis integration
* Reworked DeviceContext for fine grained event generation on settings change * Added dedicated settings classes which allows for the latter * Changed board manager and serial monitor to used the new fine grained events instead of the "something changed event" - which makes the code marginally more efficient * Implemented an analysis manager which takes care of analysis requests generated by settings change events and makes sure that analysis builds don't interfere with regular builds * Adapted the build infrastructure within ArduinoApp such that it allows for the above * Removed the global build guard and moved it to ArduinoApp (there is a corner case though for programmer selection within extension.ts which I marked with a TODO * Fixed some linting issues but did not lint most of the stuff committed here - this will be part of a later commit * I left notes here and there where I saw things which should/could be improved/changed * Removed the try/catch guards from the build invocations within extension.ts since exceptions are handled within the build function itself * Changed the project setting parameter for the IntelliSense setup to reflect its workings better
1 parent 6c8c11d commit 8840f51

14 files changed

+598
-377
lines changed

misc/arduinoValidator.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"type": "string",
3434
"minLength": 1
3535
},
36-
"disableIntelliSenseAutoGen": {
36+
"intelliSenseGen": {
3737
"description": "Disable/enable the automatic generation of the IntelliSense configuration file (c_cpp_properties.json) for this project (overrides the global setting). When set to \"global\" the global extension settings will be used.",
3838
"type": "string",
3939
"default": "global",

src/arduino/arduino.ts

Lines changed: 243 additions & 175 deletions
Large diffs are not rendered by default.

src/arduino/arduinoContentProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ export class ArduinoContentProvider implements vscode.TextDocumentContentProvide
208208
} else {
209209
try {
210210
// IS-REMOVE: to be removed completely when IntelliSense implementation is merged
211-
//await ArduinoContext.arduinoApp.addLibPath(req.body.libraryPath);
211+
// await ArduinoContext.arduinoApp.addLibPath(req.body.libraryPath);
212212
await ArduinoContext.arduinoApp.includeLibrary(req.body.libraryPath);
213213
return res.json({
214214
status: "OK",

src/arduino/boardManager.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ export class BoardManager {
7373
this._boardConfigStatusBar.show();
7474

7575
const dc = DeviceContext.getInstance();
76-
dc.onDidChange(() => {
76+
dc.onChangeBoard(() => {
77+
this.updateStatusBar();
78+
});
79+
dc.onChangeConfiguration(() => {
7780
this.updateStatusBar();
7881
});
7982
}
@@ -124,7 +127,7 @@ export class BoardManager {
124127
dc.configuration = this._currentBoard.customConfig;
125128
this._boardConfigStatusBar.text = targetBoard.name;
126129
// IS-REMOVE: to be removed completely when IntelliSense implementation is merged
127-
//this._arduinoApp.addLibPath(null);
130+
// this._arduinoApp.addLibPath(null);
128131

129132
this._onBoardTypeChanged.fire();
130133
}

src/arduino/intellisense.ts

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Copyright (c) Elektronik Workshop. All rights reserved.
2+
// Licensed under the MIT license.
3+
14
import * as ccp from "cocopa";
25
import * as path from "path";
36

@@ -23,7 +26,7 @@ export function isCompilerParserEnabled(dc?: DeviceContext) {
2326
dc = DeviceContext.getInstance();
2427
}
2528
const globalDisable = VscodeSettings.getInstance().disableIntelliSenseAutoGen;
26-
const projectSetting = dc.disableIntelliSenseAutoGen;
29+
const projectSetting = dc.intelliSenseGen;
2730
return projectSetting !== "disable" && !globalDisable ||
2831
projectSetting === "enable";
2932
}
@@ -93,6 +96,8 @@ function makeCompilerParserEngines(dc: DeviceContext) {
9396
const dotcpp = sketch.endsWith(".ino") ? ".cpp" : "";
9497
sketch = `-o\\s+\\S*${ccp.regExEscape(sketch)}${dotcpp}\\.o`;
9598

99+
// TODO: handle other architectures here
100+
96101
const matchPattern = [
97102
// make sure we're running g++
98103
/(?:^|-)g\+\+\s+/,
@@ -111,3 +116,192 @@ function makeCompilerParserEngines(dc: DeviceContext) {
111116
const gccParserEngine = new ccp.ParserGcc(matchPattern, dontMatchPattern);
112117
return [gccParserEngine];
113118
}
119+
120+
/**
121+
* Possible states of AnalysisManager's state machine.
122+
*/
123+
enum AnalysisState {
124+
/**
125+
* No analysis request pending.
126+
*/
127+
Idle = "idle",
128+
/**
129+
* Analysis request pending. Waiting for the time out to expire or for
130+
* another build to complete.
131+
*/
132+
Waiting = "waiting",
133+
/**
134+
* Analysis in progress.
135+
*/
136+
Analyzing = "analyzing",
137+
/**
138+
* Analysis in progress with yet another analysis request pending.
139+
* As soon as the current analysis completes the manager will directly
140+
* enter the Waiting state.
141+
*/
142+
AnalyzingWaiting = "analyzing and waiting",
143+
}
144+
145+
/**
146+
* Events (edges) which cause state changes within AnalysisManager.
147+
*/
148+
enum AnalysisEvent {
149+
/**
150+
* The only external event. Requests an analysis to be run.
151+
*/
152+
AnalysisRequest,
153+
/**
154+
* The internal wait timeout expired.
155+
*/
156+
WaitTimeout,
157+
/**
158+
* The current analysis build finished.
159+
*/
160+
AnalysisBuildDone,
161+
}
162+
163+
/**
164+
* This class manages analysis builds for the automatic IntelliSense
165+
* configuration synthesis. Its primary purposes are:
166+
*
167+
* * delaying analysis requests caused by DeviceContext setting change
168+
* events such that multiple subsequent requests don't cause
169+
* multiple analysis builds
170+
* * make sure that an analysis request is postponed when another build
171+
* is currently in progress
172+
*
173+
* TODO: initialization sequence: make sure events generated
174+
* during initialization are not lost
175+
*
176+
* TODO: check time of c_cpp_properties.json and compare it with
177+
* * arduino.json
178+
* * main sketch file
179+
* This way we can perhaps optimize this further. But be aware
180+
* that settings events fire before their corresponding values
181+
* are actually written to arduino.json -> time of arduino.json
182+
* is outdated if no countermeasure is taken.
183+
*/
184+
export class AnalysisManager {
185+
186+
/** The manager's state. */
187+
private _state: AnalysisState = AnalysisState.Idle;
188+
/** A callback used by the manager to query if the build backend is busy. */
189+
private _isBuilding: () => boolean;
190+
/** A callback used by the manager to initiate an analysis build. */
191+
private _doBuild: () => Promise<void>;
192+
/** Timeout for the timeouts/delays in milliseconds. */
193+
private _waitPeriodMs: number;
194+
/** The internal timer used to implement the above timeouts and delays. */
195+
private _timer: NodeJS.Timer;
196+
197+
/**
198+
* Constructor.
199+
* @param isBuilding Provide a callback which returns true if another build
200+
* is currently in progress.
201+
* @param doBuild Provide a callback which runs the analysis build.
202+
* @param waitPeriodMs The delay the manger should wait for potential new
203+
* analysis request. This delay is used as polling interval as well when
204+
* checking for ongoing builds.
205+
*/
206+
constructor(isBuilding:() => boolean,
207+
doBuild: () => Promise<void>,
208+
waitPeriodMs: number = 1000) {
209+
this._isBuilding = isBuilding;
210+
this._doBuild = doBuild;
211+
this._waitPeriodMs = waitPeriodMs;
212+
}
213+
214+
/**
215+
* File an analysis request.
216+
* The analysis will be delayed until no further requests are filed
217+
* within a wait period or until any build in progress has terminated.
218+
*/
219+
public async requestAnalysis() {
220+
console.log("requesting analysis");
221+
await this.update(AnalysisEvent.AnalysisRequest);
222+
}
223+
224+
/**
225+
* Update the manager's state machine.
226+
* @param event The event which will cause the state transition.
227+
*
228+
* Implementation note: asynchronous edge actions must be called after
229+
* setting the new state since they don't return immediately.
230+
*/
231+
private async update(event: AnalysisEvent) {
232+
233+
switch (this._state) {
234+
235+
case AnalysisState.Idle:
236+
if (event == AnalysisEvent.AnalysisRequest) {
237+
this._state = AnalysisState.Waiting;
238+
this.startWaitTimeout();
239+
}
240+
break;
241+
242+
case AnalysisState.Waiting:
243+
if (event == AnalysisEvent.AnalysisRequest) {
244+
// every new request restarts timer
245+
this.startWaitTimeout();
246+
} else if (event == AnalysisEvent.WaitTimeout) {
247+
if (this._isBuilding()) {
248+
// another build in progress, continue waiting
249+
this.startWaitTimeout();
250+
} else {
251+
// no other build in progress -> launch analysis
252+
this._state = AnalysisState.Analyzing;
253+
await this.startAnalysis();
254+
}
255+
}
256+
break;
257+
258+
case AnalysisState.Analyzing:
259+
if (event == AnalysisEvent.AnalysisBuildDone) {
260+
this._state = AnalysisState.Idle;
261+
} else if (event == AnalysisEvent.AnalysisRequest) {
262+
this._state = AnalysisState.AnalyzingWaiting;
263+
}
264+
break;
265+
266+
case AnalysisState.AnalyzingWaiting:
267+
if (event == AnalysisEvent.AnalysisBuildDone) {
268+
// emulate the transition from idle to waiting
269+
// (we don't care if this adds an additional
270+
// timeout - event driven analysis is not time-
271+
// critical)
272+
this._state = AnalysisState.Idle;
273+
await this.update(AnalysisEvent.AnalysisRequest);
274+
}
275+
break;
276+
}
277+
}
278+
279+
/**
280+
* Starts the wait timeout timer.
281+
* If it's already running, the current timer is stopped and restarted.
282+
* The timeout callback will then update the state machine.
283+
*/
284+
private startWaitTimeout() {
285+
if (this._timer) {
286+
clearTimeout(this._timer);
287+
}
288+
this._timer = setTimeout(() => {
289+
this.update(AnalysisEvent.WaitTimeout);
290+
this._timer = undefined;
291+
}, this._waitPeriodMs);
292+
}
293+
294+
/**
295+
* Starts the analysis build.
296+
* When done, the callback will update the state machine.
297+
*/
298+
private async startAnalysis() {
299+
await this._doBuild()
300+
.then(() => {
301+
this.update(AnalysisEvent.AnalysisBuildDone);
302+
})
303+
.catch((reason) => {
304+
this.update(AnalysisEvent.AnalysisBuildDone);
305+
});
306+
}
307+
}

src/arduinoContext.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class ArduinoContext {
1919
this._arduinoApp = value;
2020
}
2121

22+
// TODO EW: This is redundant: the board manager is already part of
23+
// the arduino app
2224
public get boardManager() {
2325
return this._boardManager;
2426
}
@@ -38,6 +40,10 @@ class ArduinoContext {
3840
return this._debuggerManager;
3941
}
4042

43+
// TODO EW: You don't have to initialize members to null
44+
// if they don't get a default value or aren't initialized
45+
// within a constructor they are "undefined" by default.
46+
// This makes comparing against null (above) superfluous.
4147
private _arduinoApp: ArduinoApp = null;
4248
private _debuggerManager: DebuggerManager = null;
4349
private _boardManager: BoardManager = null;

src/common/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ export function tryParseJSON(jsonString: string) {
267267
}
268268
} catch (ex) { }
269269

270-
return false;
270+
return undefined;
271271
}
272272

273273
export function isJunk(filename: string): boolean {

0 commit comments

Comments
 (0)