-
Notifications
You must be signed in to change notification settings - Fork 1
/
extension.js
executable file
·519 lines (456 loc) · 17.6 KB
/
extension.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
const { stdout } = require('process');
const vscode = require('vscode');
const fs = require("fs");
const path = require('path');
const os = require ('os');
const extension = vscode.extensions.getExtension("HaoWu-BastienTurco.Cyclone");
var lib_path = "";
var ext_path = "";
const { getTraceDirPath, getTraceFilePath, getCurrFilePath, getDotFilePath, getPngFilePath} = require("./utils/paths");
const {highlightErrors, displayWarningAndGenerationError, disposeHighlights} = require("./utils/errors")
const {modifyForPngTrace, rollbackFile} = require("./utils/editFile");
const {checkJavaVersion, checkOS, showNotification, sleep, checkGraphviz} = require("./utils/misc");
var cmd_ver='';
var cmd_java_ver='java -jar cyclone.jar --version';
var isWindows = checkOS() === "Windows"
var cmd_cyclone='';
// Both cmd are changed for windows in initialize()
var rmCmd = "rm ";
var rmDirCmd = "rm -r ";
const cancelCommandId = "cyclone.cancelCheck";
var customCancellationToken = null;
var myStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
var keepChild = false; // Global var needed for cancellation token
const charToReplace = isWindows ? /["`]/g : /["`$]/g
function activate(context) {
initialize();
let out = vscode.window.createOutputChannel("Cyclone");
registerCycloneCheck(context,out);
registerCycloneInfo(context,out);
registerCycloneShowTrace(context, out);
registerCycloneShowDotTrace(context, out);
registerCycloneShowTraceGraphic(context, out);
registerCycloneCleanTrace(context, out);
registerCycloneCleanAllTrace(context, out);
registerCycloneSettings(context, out);
registerCycloneExamples(context, out);
registerCycloneSwitchTraceMode(context, out);
initTraceStatusBar(context.subscriptions);
}
function registerCycloneCheck(context,out){
let disposable = vscode.commands.registerCommand('cyclone.check', function () {
// Prevent 2 checks from being run at the same time.
vscode.commands.executeCommand(cancelCommandId, false);
var exec = require('child_process').exec, child;
let currentFilePath = getCurrFilePath();
let currentDirPath = path.dirname(currentFilePath);
let pngTraceWanted = vscode.workspace.getConfiguration().get("Trace.generateGraphicTrace");
if (pngTraceWanted){
checkGraphviz();
// Modify the file to add png output option
if (!modifyForPngTrace(currentFilePath)){
// If no option-trace is false, disable png generation
pngTraceWanted = false;
}
}
const editor = vscode.window.activeTextEditor;
if(currentDirPath === '') { // Display error and don't check the spec
vscode.window.showErrorMessage("Working folder not found, please open a folder and try again." );
return;
}
if (checkOS() === 'Windows') {
currentDirPath = "/d "+ currentDirPath; // Need to specify /d to change hard drive if needed
}
child = exec('cd "'+currentDirPath+ '" && java "-Djava.library.path=' + lib_path + '" -jar "' + ext_path + '" --nocolor "' + currentFilePath + '"',
{
timeout: vscode.workspace.getConfiguration().get("check.Timeout")*1000, // Convert into s
killSignal:"SIGTERM" // Needed to distinguish with user cancellation
},
function (error, stdout, stderr){
// Remove added line before getting error pos
pngTraceWanted ? rollbackFile(currentFilePath) : '';
out.clear();
let date = new Date();
out.appendLine(`[${date.toLocaleString()}]: `+stdout);
out.appendLine(stderr);
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
// Remove all highlights
disposeHighlights();
// Handle warning and generation errors
displayWarningAndGenerationError(stdout, stderr);
if (stderr !== ''){
// Apply highlights if needed
highlightErrors(stderr, editor);
// Remove the timer and show warning msg
vscode.commands.executeCommand(cancelCommandId, true);
vscode.window.showErrorMessage("An error occurred, abort checking.");
}
if(error !== null){
console.log('exec error: ' + error);
// Specify the error message according to the problem
if (error.killed){
// Each killSignal is linked to a certain problem/user cancellation/time out
switch (error.signal) {
case "SIGTERM":
vscode.commands.executeCommand(cancelCommandId, false) // False because child has already been killed
vscode.window.showWarningMessage("Check was timed out.");
break;
case "SIGKILL":
vscode.window.showInformationMessage("Check was successfully cancelled.");
break;
default:
break;
}
} else if (stderr ==='' ){ // Do not show error twice
vscode.window.showErrorMessage("Check finished with an error.");
}
return;
}
if(error === null && stderr === ''){
// Removes the timer
vscode.commands.executeCommand(cancelCommandId, true);
if (stdout.includes("No Path found.") || stdout.includes("No Counter-example found.")){
showNotification("No path was generated for this spec." + (pngTraceWanted ? " Image based trace won't be generated." : ""), 4000);
pngTraceWanted = false;
}
if (pngTraceWanted){
let dotFilePath = getDotFilePath();
let pngFilePath = dotFilePath.slice(0, dotFilePath.length - 3) + "png"
// Error when generating dot file or No path was found
if (!fs.existsSync(formatBackSlash(dotFilePath))){
vscode.window.showWarningMessage("Dot trace not found.");
return;
}
exec(`cd "${currentDirPath}" && dot -Tpng "${dotFilePath}" -o "${pngFilePath}"`,
function (error, stdout, stderr){
out.appendLine(`[${date.toLocaleString()}]: `+stdout);
out.appendLine(stderr);
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
});
}
showNotification("Check finished. Details in Output -> Cyclone", 5000);
}
});
showCheckNotification(child);
});
context.subscriptions.push(disposable);
}
function registerCycloneInfo(context, out){
let disposable = vscode.commands.registerCommand('cyclone.version', function () {
var exec = require('child_process').exec, child;
child = exec(cmd_ver,
function (error, stdout, stderr){
out.clear();
let date = new Date();
out.appendLine(`[${date.toLocaleString()}]: `+stdout);
out.appendLine(stderr);
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if(error !== null){
console.log('exec error: ' + error);
}
});
});
context.subscriptions.push(disposable);
}
function registerCycloneShowTrace(context, out){
let disposable = vscode.commands.registerCommand('cyclone.trace', function () {
let traceFilePath = getTraceFilePath();
if (fs.existsSync(formatBackSlash(traceFilePath))) {
let date = new Date();
const openPath = vscode.Uri.file(formatBackSlash(traceFilePath));
vscode.workspace.openTextDocument(openPath).then(doc => {
vscode.window.showTextDocument(doc, {
viewColumn: vscode.ViewColumn.Beside // Open file in a split view
});
});
out.appendLine(`[${date.toLocaleString()}]: ${path.basename(traceFilePath)} opened.\n`);
} else {
vscode.window.showErrorMessage("Trace file not found, please ensure that 'Graphic Trace' setting is disabled and check the specification before showing trace." );
}
});
context.subscriptions.push(disposable);
}
function registerCycloneShowDotTrace(context, out){
let disposable = vscode.commands.registerCommand('cyclone.dotTrace', function () {
let dotFilePath = getDotFilePath();
if (fs.existsSync(formatBackSlash(dotFilePath))) {
let date = new Date();
const openPath = vscode.Uri.file(formatBackSlash(dotFilePath));
vscode.workspace.openTextDocument(openPath).then(doc => {
vscode.window.showTextDocument(doc, {
viewColumn: vscode.ViewColumn.Beside // Open file in a split view
});
});
out.appendLine(`[${date.toLocaleString()}]: ${path.basename(dotFilePath)} opened.\n`);
} else {
vscode.window.showErrorMessage('Dot file not found, please ensure that you generated a dot trace with \'option-output="dot"\' and checked the specification before showing trace.' );
}
});
context.subscriptions.push(disposable);
}
function registerCycloneShowTraceGraphic(context, out){
let disposable = vscode.commands.registerCommand('cyclone.pngTrace', function () {
let pngFilePath = getPngFilePath();
if (fs.existsSync(formatBackSlash(pngFilePath))) {
let date = new Date();
const openPath = vscode.Uri.file(formatBackSlash(pngFilePath));
vscode.commands.executeCommand('vscode.open', openPath, vscode.ViewColumn.Beside);
out.appendLine(`[${date.toLocaleString()}]: ${path.basename(pngFilePath)} opened.\n`);
} else {
vscode.window.showErrorMessage("Trace file not found, please ensure that 'Graphic Trace' setting is activated and check the specification before showing trace." );
}
});
context.subscriptions.push(disposable);
}
function registerCycloneCleanTrace(context, out){
let disposable = vscode.commands.registerCommand('cyclone.clean', function () {
let fileTraces = [];
let err = "";
let didDelete = false;
// Need to remove all 3 kind of trace files
fileTraces.push(getTraceFilePath());
fileTraces.push(getPngFilePath());
fileTraces.push(getDotFilePath());
var exec = require('child_process').exec, child;
// Try to delete each file
for (let i = 0; i < fileTraces.length; i++){
if (fs.existsSync(formatBackSlash(fileTraces[i]))) {
didDelete = true;
child = exec(`${rmCmd} "${fileTraces[i]}"`, function (error, stdout, stderr){
if(error !== null){
err += stderr + "\n";
console.log('exec error: ' + error);
}
});
}
}
let date = new Date();
if (!didDelete) {
vscode.window.showInformationMessage("There was no trace file to clean." );
return;
}
if (err !== ""){
out.appendLine(`[${date.toLocaleString()}]: `+err);
} else {
out.appendLine(`[${date.toLocaleString()}]: Trace files successfully deleted.`);
}
});
context.subscriptions.push(disposable);
}
function registerCycloneCleanAllTrace(context, out){
let disposable = vscode.commands.registerCommand('cyclone.cleanAll', function () {
let traceDir = getTraceDirPath();
if (fs.existsSync(formatBackSlash((traceDir)))) {
vscode.window.showInformationMessage(`Do you really want to delete repertory ${traceDir}?`, "Yes", "No")
.then(answer => {
if (answer === "Yes") {
var exec = require('child_process').exec, child;
let date = new Date();
child = exec(`${rmDirCmd} "${traceDir}"`,
function (error, stdout, stderr){
out.appendLine("["+date.toLocaleString()+"]: "+stderr);
if(error !== null){
console.log('exec error: ' + error);
} else {
out.appendLine(`[${date.toLocaleString()}]: All traces were successfully deleted.`);
}
});
}
})
} else {
showNotification("There was no trace folder to delete.")
}
});
context.subscriptions.push(disposable);
}
function registerCycloneSettings(context, out){
let disposable = vscode.commands.registerCommand('cyclone.settings', function() {
vscode.commands.executeCommand( 'workbench.action.openSettings', 'Cyclone' )
});
context.subscriptions.push(disposable);
}
function registerCycloneExamples(context, out){
let disposable = vscode.commands.registerCommand('cyclone.examples', function() {
let choiceList = []; // The list displayed to the user
let pathList = []; // The path corresponding to the choices
let exampleDir = path.join(lib_path,'examples'); // Absolute path to provided examples
let itemPath='';
fs.readdirSync(formatBackSlash((exampleDir))).forEach(item => {
// Remove 'trace/' folder from accessible directory
if (item === "trace"){
return;
}
itemPath = path.join(exampleDir, item); // Get absolute path of file
if (fs.statSync(formatBackSlash((itemPath))).isDirectory()){
choiceList.push({
label: path.basename(item),
description: `Go to ${path.basename(item)} examples.`
})
pathList.push(itemPath);
} else {
// Do not display files that aren't cyclone file
if (itemPath.slice(itemPath.length - 8) !== (".cyclone")){
return;
}
choiceList.push({
label: path.basename(item),
description: `Load ${path.basename(item)} example.`
})
pathList.push(itemPath);
}
});
vscode.window.showQuickPick(choiceList).then(selection => {
// the user canceled the selection
if (!selection) {
return;
}
let selectedPath = pathList[choiceList.indexOf(selection)];
if (fs.statSync(formatBackSlash(selectedPath)).isDirectory()){
// Show quickPicks for selected folder
quickPickDir(selectedPath);
return;
}
loadFile(selectedPath);
});
});
context.subscriptions.push(disposable);
}
function registerCycloneSwitchTraceMode(context, out){
let disposable = vscode.commands.registerCommand('cyclone.switchTraceMode', function() {
let config = vscode.workspace.getConfiguration();
let newMode = !config.get("Trace.generateGraphicTrace");
config.update("Trace.generateGraphicTrace", newMode, true);
showNotification(newMode ? "Switched to graphic trace" : "Switched to text trace", 1500);
});
context.subscriptions.push(disposable);
}
function initialize(){
vscode.commands.registerCommand(cancelCommandId, (finishedSuccessfully) => {
if (customCancellationToken) {
// If the check finished without any errors, there is no need to kill the process
keepChild = finishedSuccessfully;
customCancellationToken.cancel();
// If user asked for graphic trace generation, then we need to remove the prepended line
if (vscode.workspace.getConfiguration().get("Trace.generateGraphicTrace")){
rollbackFile(getCurrFilePath());
}
}
})
let sys=checkOS();
// ARM and Intel need different libraries
if (sys === 'MacOSARM'){
lib_path = path.join(extension.extensionPath, "CycloneARM").replaceAll(charToReplace, "\\$&");
} else {
lib_path = path.join(extension.extensionPath, "Cyclone").replaceAll(charToReplace, "\\$&");
}
ext_path = path.join(lib_path, "cyclone.jar");
checkJavaVersion();
var exec = require('child_process').exec;
if (sys=='Linux'){
cmd_ver=`cd "${lib_path}" && export LD_LIBRARY_PATH=. && ${cmd_java_ver}`;
let p1=exec (`export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"${lib_path}"`,(error,stdout,stderr)=>{
if (error){
console.error(`error: ${error.message}`);
return;
}
if (stderr){
console.error(`stderr: ${stderr}`);
return;
}
});
return;
}
if (sys=='MacOSARM' || sys=='MacOSIntel'){
cmd_ver=`cd "${lib_path}" && export DYLD_LIBRARY_PATH=. && ${cmd_java_ver}`;
exec ('export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:"'+lib_path+'"',(error,stdout,stderr)=>{
if (error){
console.error(`error: ${error.message}`);
return;
}
if (stderr){
console.error(`stderr: ${stderr}`);
return;
}
});
return;
}
rmCmd = "del ";
rmDirCmd = "rmdir /s /q ";
cmd_ver=`cd "${lib_path}" && ${cmd_java_ver}`;
}
/**
* Add a status bar for trace extension
* @param {*} subscriptions
*/
function initTraceStatusBar(subscriptions) {
subscriptions.push(myStatusBarItem);
// register listener to make sure the status bar
// item always up-to-date
subscriptions.push(vscode.workspace.onDidChangeConfiguration(updateStatusBarItem));
// update status bar item once at start
updateStatusBarItem();
}
/**
* Function that will update the content of the trace status bar
*/
function updateStatusBarItem() {
let graphicTraceActivated = vscode.workspace.getConfiguration().get("Trace.generateGraphicTrace")
if (graphicTraceActivated) {
myStatusBarItem.text = `$(symbol-misc) Graphic trace`;
checkGraphviz(); // Ensure that Graphviz is installed, else display warning
myStatusBarItem.show();
} else {
myStatusBarItem.text = `$(list-selection) Text trace`;
myStatusBarItem.show();
}
}
/**
* Show a notification that present a timer and a cancel button
* @param {import("child_process").ChildProcess} child
*/
// Based on a code written by Elio Struyf
async function showCheckNotification(child ) {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Check progress",
cancellable: false // We're using a custom cancellation token
}, async (progress, token) => {
return new Promise((async (resolve) => {
customCancellationToken = new vscode.CancellationTokenSource();
customCancellationToken.token.onCancellationRequested(() => {
customCancellationToken?.dispose();
customCancellationToken = null;
// Only kill if timed out. Allow us to click launch button twice without interfering
if (!keepChild){
child.kill("SIGKILL");
}
resolve(null);
return;
});
const seconds = 7200;
for (let i = 1; i < seconds; i++) {
// Increment is summed up with the previous value
progress.report({ increment: seconds, message: `Running... (${i}s) [Cancel](command:${cancelCommandId})` })
await sleep(1000);
}
resolve(null);
}));
});
}
function formatBackSlash(content){
if (isWindows){
return content
}
return content.replaceAll("\\","")
}
function deactivate() {}
module.exports = {
activate,
deactivate,
extension
}
// quickPicks.js require the export of extension var to work
const {quickPickDir, loadFile} = require("./utils/quickPicks");