Newer
Older

s1995588
committed
import * as fs from "fs";
import { existsSync } from "fs";
import * as vscode from "vscode";

s1995588
committed
ExtensionContext
LanguageClient,
LanguageClientOptions,
NotificationType,

s1995588
committed
TransportKind
} from "vscode-languageclient/node";
import { AnalysisResultsProvider, Result } from "./analysisResults";

Stekelenburg, A.V. (Alexander, Student )
committed
import { DotViewer } from "./dotViewer";
import { getDocumentVars, initializeTools, ModestSidebarProvider } from "./sidebar";
export let client: LanguageClient | undefined;
export let provider: ModestSidebarProvider;
export let analysisResultsProvider: AnalysisResultsProvider;
export let analysisResultsCompProvider: AnalysisResultsProvider;
export let treeView: vscode.TreeView<Result>;

s1995588
committed
export let extensionUri: vscode.Uri;

Stekelenburg, A.V. (Alexander, Student )
committed
export let clientReady: boolean = false;
export let disposalQueue: Array<vscode.Disposable> = [];
export let resultHandlers: Array<(notification: ResultNotification) => void> = [];
export let activePlotView: vscode.Webview;
export function modestExecutable(): string | undefined {
let executable: string | undefined = vscode.workspace
.getConfiguration("modest")
.get("executableLocation");
if (executable === undefined || executable === "") {
vscode.window
.showErrorMessage(
"It looks like you don't have the modest executable located yet.",
"Go to settings"
)
.then((result) => {
if (result !== undefined) {
vscode.commands.executeCommand(
"workbench.action.openSettings",
"modest.executableLocation"
);
}
});
return;
}
return executable;
}
function createClient(): LanguageClient | undefined {
// TODO: Check if path exists for a better user experience(so it doesn't spam the user with errors)
// Client (re)starts so cancel all running progress bars
while (disposalQueue.length > 0) {
disposalQueue.pop()?.dispose();
}
// The server is implemented in node
let serverExe = modestExecutable();
if (serverExe === undefined) {
return;
}
if (!existsSync(serverExe)) {
vscode.window
.showErrorMessage(
"The specified modest executable could not be found",
"Go to settings"
)
.then((result) => {
if (result !== undefined) {
vscode.commands.executeCommand(
"workbench.action.openSettings",
"modest.executableLocation"
);
}
});
return;
}
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
let serverOptions: ServerOptions = {
// run: { command: serverExe, args: ['-lsp', '-d'] },
run: {
command: serverExe,
transport: TransportKind.stdio,
},
// debug: { command: serverExe, args: ['-lsp', '-d'] }
debug: {
command: "dotnet",
transport: TransportKind.stdio,
args: [serverExe.replace(".exe", "") + ".dll", "startlspserver"], // Hacky
// Options to control the language client
let clientOptions: LanguageClientOptions = {
// Register the server for plain text documents
documentSelector: [
{
pattern: "**/*.modest",
},
progressOnInitialization: true,
// Synchronize the setting section 'languageServerExample' to the server
// configurationSection: "languageServerExample",
// fileEvents: workspace.createFileSystemWatcher("**/*.modest"),
//},
// Create the language client and start the client.
const client = new LanguageClient(
"ModestExtension",
"Modest Extension",
serverOptions,
client.registerProposedFeatures();

Stekelenburg, A.V. (Alexander, Student )
committed
client.onReady().then(() => {
initializeTools();
clientReady = true;
client.onNotification(new NotificationType<ResultNotification>("modest/result"), result => {
resultHandlers.forEach(handler => {
handler(result);
});
});

Stekelenburg, A.V. (Alexander, Student )
committed
});
return client;
}
export function activate(context: ExtensionContext) {
client = createClient();
if (client) {
client.onDidChangeState(e => {
if (e.newState === State.Stopped) {
while (disposalQueue.length > 0) {

Stekelenburg, A.V. (Alexander, Student )
committed
try {
disposalQueue.pop()?.dispose();
} catch {
}
}
}
}, null, context.subscriptions);
let langClient = client.start();
// Push the disposable to the context's subscriptions so that the
// client can be deactivated on extension deactivation
context.subscriptions.push(langClient);
}

Stekelenburg, A.V. (Alexander, Student )
committed
context.subscriptions.push(DotViewer.viewDot);
context.subscriptions.push(DotViewer.viewDotSameWindow);
context.subscriptions.push(DotViewer.dotZoomIn);
context.subscriptions.push(DotViewer.dotZoomOut);
context.subscriptions.push(DotViewer.resetDotPosition);
context.subscriptions.push(DotViewer.dotSaveSvg);
context.subscriptions.push(DotViewer.dotCopySvg);
context.subscriptions.push(DotViewer.dotSaveDot);
context.subscriptions.push(DotViewer.dotCopyDot);

s1995588
committed
extensionUri = context.extensionUri;

s1995588
committed
//#region listeners
vscode.workspace.onDidChangeConfiguration((a) => {
if (a.affectsConfiguration("modest.executableLocation")) {

Stekelenburg, A.V. (Alexander, Student )
committed
clientReady = false;
if (client) {
client.stop();
client = undefined;
}
client = createClient();
if (client) {
client.onDidChangeState(e => {
if (e.newState === State.Stopped) {
while (disposalQueue.length > 0) {
disposalQueue.pop()?.dispose();
}
}
}, null, context.subscriptions);
let langClient = client.start();
context.subscriptions.push(langClient);
}
}
});
vscode.window.onDidChangeActiveTextEditor(textEditor => {
getDocumentVars(textEditor.document);
vscode.workspace.onDidSaveTextDocument(textEditor => {
getDocumentVars(textEditor);

s1995588
committed
//#endregion
provider = new ModestSidebarProvider(context.extensionUri);
analysisResultsProvider = new AnalysisResultsProvider();
analysisResultsCompProvider = new AnalysisResultsProvider();
treeView = vscode.window.createTreeView("analysisResults", {
treeDataProvider: analysisResultsProvider
vscode.window.createTreeView("analysisResultsCompView", {
treeDataProvider: analysisResultsCompProvider,
});
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
ModestSidebarProvider.viewType,
provider
)
);

s1995588
committed
//#region register commands
context.subscriptions.push(vscode.commands.registerCommand(
"analysisResults.copyValue",
(res: Result) => copyToClipboard(res.getValue())
));
context.subscriptions.push(vscode.commands.registerCommand(
(res: Result) => copyItemToClipboard(res)
));
context.subscriptions.push(vscode.commands.registerCommand(
(res: Result) => openInEditor(analysisResultsProvider, res)
));
context.subscriptions.push(vscode.commands.registerCommand(
(res: Result) => highlight(res)
));
context.subscriptions.push(vscode.commands.registerCommand(
context.subscriptions.push(vscode.commands.registerCommand(
"analysisResults.load",
() => loadResults(analysisResultsProvider)
));
context.subscriptions.push(vscode.commands.registerCommand(
"analysisResultsCompView.load",
() => loadResults(analysisResultsCompProvider)
context.subscriptions.push(vscode.commands.registerCommand(
() => exportResults(analysisResultsProvider)
));
context.subscriptions.push(vscode.commands.registerCommand(
() => exportResults(analysisResultsCompProvider)
context.subscriptions.push(vscode.commands.registerCommand(
"analysisResults.openFileInEditor",
() => openFileInEditor(analysisResultsProvider)
));
context.subscriptions.push(vscode.commands.registerCommand(
"analysisResultsCompView.openFileInEditor",
() => openFileInEditor(analysisResultsCompProvider)
context.subscriptions.push(vscode.commands.registerCommand(
"analysisResults.clear",
() => clearView(analysisResultsProvider)
));
context.subscriptions.push(vscode.commands.registerCommand(
"analysisResultsCompView.clearCompView",
() => clearView(analysisResultsCompProvider)
context.subscriptions.push(vscode.commands.registerCommand(
"analysisResults.openInCompView",
() => analysisResultsCompProvider.setJsonObject(analysisResultsProvider.getJsonObject())
));
context.subscriptions.push(vscode.commands.registerCommand(
"modest.savePlot",
() => savePlot()
));

s1995588
committed
//#endregion
export function deactivate(): Thenable<void> | undefined {
if (!client) {

s1995588
committed
//#region analysis results functions
function copyToClipboard(text: string) {
vscode.env.clipboard.writeText(text);
vscode.window.showInformationMessage(`Copied ${text} to clipboard.`);
}
function copyItemToClipboard(res: Result) {
if (res.getValue() !== "") {
return copyToClipboard(`${res.getLabel()}: ${res.getValue()}`);
} else {
return copyToClipboard(res.getLabel());
}
function loadResults(provider: AnalysisResultsProvider) {
vscode.window.showOpenDialog().then(fileUri => {
if (fileUri) {
var fpath = fileUri[0].fsPath;
provider.setJsonPath(fpath);
vscode.window.showInformationMessage(`Opened ${fpath.split("/").pop()}.`);
}
});
function exportResults(prov: AnalysisResultsProvider) {
if (prov.getJsonObject()) {
const defaultUri = vscode.workspace.workspaceFolders
? vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, `/analysis/${prov.getModestFile()}-results.json`)
: undefined;
vscode.window.showSaveDialog({ defaultUri: defaultUri }).then(f => {
if (f) {
fs.writeFileSync(f.fsPath, JSON.stringify(prov.getJsonObject()));
}
});
} else {
vscode.window.showErrorMessage("There are no analysis results to be exported.");
}
function openInEditor(prov: AnalysisResultsProvider, res: Result) {
if (vscode.workspace.workspaceFolders !== undefined) {
let wsPath = vscode.workspace.workspaceFolders[0].uri.fsPath;
const date = new Date().toISOString().split('.')[0];
const parent = res.getParentName() ? res.getParentName() + "-" : "";
const newPath = `${wsPath}/analysis/${prov.getModestFile()}-${parent}${res.getLabel()}.txt`;
var newText = res.getValue();
if (res.getIsPlottable()) {
const regex = new RegExp('(\\)|\\.),', 'gm');
newText = res.getValue().replace(regex, "$1,\n");
}
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
if (pathExists(newPath)) {
vscode.workspace.openTextDocument(newPath).then(async document => {
var overwrite = await vscode.window.showInformationMessage(`This value already has been saved as a text file.`,
"Open existing file", "Create new file", "Overwrite file");
if (overwrite === "Overwrite file") {
const edit = new vscode.WorkspaceEdit();
edit.replace(vscode.Uri.parse(newPath), new vscode.Range(
new vscode.Position(0, 0),
document.positionAt(document.getText().length - 1)),
newText
);
const success = await vscode.workspace.applyEdit(edit);
if (success) {
vscode.window.showTextDocument(document);
}
} else if (overwrite === "Open existing file") {
vscode.window.showTextDocument(document);
} else if (overwrite === "Create new file") {
const newFile = vscode.Uri.parse(`untitled:${newPath.replace(".txt", "")}-${date}.txt`);
vscode.workspace.openTextDocument(newFile).then(async document => {
const edit = new vscode.WorkspaceEdit();
edit.insert(newFile, new vscode.Position(0, 0), newText);
const success = await vscode.workspace.applyEdit(edit);
if (success) {
vscode.window.showTextDocument(document);
}
});
}
});
} else {
const newFile = vscode.Uri.parse("untitled:" + newPath);
vscode.workspace.openTextDocument(newFile).then(async document => {
const edit = new vscode.WorkspaceEdit();
edit.insert(newFile, new vscode.Position(0, 0), newText);
const success = await vscode.workspace.applyEdit(edit);
if (success) {
vscode.window.showTextDocument(document);
}
});
}
}
function openFileInEditor(prov: AnalysisResultsProvider) {
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
if (prov.getJsonObject()) {
if (vscode.workspace.workspaceFolders !== undefined) {
let wsPath = vscode.workspace.workspaceFolders[0].uri.fsPath;
const date = new Date().toISOString().split('.')[0];
const newPath = `${wsPath}/analysis/${prov.getModestFile()}-results.json`;
const newText = JSON.stringify(prov.getJsonObject());
if (pathExists(newPath)) {
vscode.workspace.openTextDocument(newPath).then(async document => {
var overwrite = await vscode.window.showInformationMessage(`${newPath.split("/").pop()} already exists.`,
"Open existing file", "Create new file", "Overwrite file");
if (overwrite === "Overwrite file") {
const edit = new vscode.WorkspaceEdit();
edit.replace(vscode.Uri.parse(newPath), new vscode.Range(
new vscode.Position(0, 0),
document.positionAt(document.getText().length - 1)),
newText
);
const success = await vscode.workspace.applyEdit(edit);
if (success) {
vscode.window.showTextDocument(document);
vscode.commands.executeCommand("editor.action.formatDocument", document.uri);
}
} else if (overwrite === "Open existing file") {
vscode.window.showTextDocument(document);
} else if (overwrite === "Create new file") {
const newFile = vscode.Uri.parse(`untitled:${newPath.replace(".json", "")}-${date}.json`);
vscode.workspace.openTextDocument(newFile).then(async document => {
const edit = new vscode.WorkspaceEdit();
edit.insert(newFile, new vscode.Position(0, 0), newText);
const success = await vscode.workspace.applyEdit(edit);
if (success) {
vscode.window.showTextDocument(document);
vscode.commands.executeCommand("editor.action.formatDocument", document.uri);
}
});
}
});
} else {
const newFile = vscode.Uri.parse("untitled:" + newPath);
vscode.workspace.openTextDocument(newFile).then(async document => {
const edit = new vscode.WorkspaceEdit();
edit.insert(newFile, new vscode.Position(0, 0), newText);
const success = await vscode.workspace.applyEdit(edit);
if (success) {
vscode.window.showTextDocument(document);
vscode.commands.executeCommand("editor.action.formatDocument", document.uri);
}
});
}
}
} else {
vscode.window.showErrorMessage("There are no analysis results to be opened in an editor.");
}
async function clearView(prov: AnalysisResultsProvider) {
if (prov.getJsonObject()) {
const jsonObject = prov.getJsonObject();
prov.setJsonObject(null);
var choice = await vscode.window.showInformationMessage("Analysis results view has been cleared.", "Undo");
if (choice === "Undo") {
prov.setJsonObject(jsonObject);
}
}
function highlight(res: Result) {
res.highlight();
analysisResultsProvider.refresh();
analysisResultsCompProvider.refresh();
}
function savePlot() {
activePlotView.postMessage({ command: 'savePlot' });
}
const panel = vscode.window.createWebviewPanel(
'plotGraph',
`Plot of ${res.getLabel()} of ${res.getParentName()}`,
vscode.ViewColumn.One,
{
enableScripts: true,
localResourceRoots: [context.extensionUri]
activePlotView = panel.webview;
panel.onDidDispose(() => {
vscode.commands.executeCommand('setContext', 'modest:plotViewFocused', false);
});
panel.onDidChangeViewState(
e => {
vscode.commands.executeCommand('setContext', 'modest:plotViewFocused', e.webviewPanel.active);
if (e.webviewPanel.active) {
activePlotView = e.webviewPanel.webview;
}
}
);
vscode.commands.executeCommand('setContext', 'modest:plotViewFocused', panel.active);
var dataPoints: {x: number, y: number}[] = [];
const value = res.getValue();
const regex = /\((\d+\.?d*),.(\d+\.?\d*)\)/gm;
let matches = (value.match(regex) || []).map(e => [e.replace(regex, '$1'), e.replace(regex, '$2')]);
matches.forEach((element) => {
dataPoints.push({x: Number(element[0]), y: Number(element[1])});
});
const chart = panel.webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, 'node_modules', 'chart.js', 'dist', 'Chart.js'));
const styleResetUri = panel.webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, 'media', 'reset.css'));
const styleVSCodeUri = panel.webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, 'media', 'vscode.css'));
const nonce = getNonce();
<head>
<link href="${styleResetUri}" rel="stylesheet">
<link href="${styleVSCodeUri}" rel="stylesheet">
</head>
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
<canvas id="myChart"></canvas>
<script nonce="${nonce}" src="${chart}"></script>
<script nonce="${nonce}">
var htmlStyle = document.documentElement.style;
var fgColor = htmlStyle.getPropertyValue("--vscode-editor-foreground");
var darkFgColor = htmlStyle.getPropertyValue("--vscode-sideBar-background");
var bgColor = htmlStyle.getPropertyValue("--vscode-editor-background");
Chart.defaults.global.defaultFontColor = fgColor;
Chart.defaults.scale.gridLines.color = fgColor;
Chart.plugins.register({
beforeDraw: function(chartInstance) {
var ctx = chartInstance.chart.ctx;
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, chartInstance.chart.width, chartInstance.chart.height);
}
});
var ctx = document.getElementById('myChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
label: '${res.getParentName()}',
data: [${dataPoints.map(elem => `{x:${elem.x},y:${elem.y}}`)}],
backgroundColor: 'rgba(0, 152, 255, 1)',
pointRadius: 4,
borderColor: 'rgba(0, 114, 191, 1)',
borderWidth: 1,
fill: false,
showLine: true,
tension: 0
}]
},
options: {
scales: {
xAxes: [{
display: true,
gridLines: {
color: darkFgColor,
zeroLineColor: fgColor
}
}],
yAxes: [{
display: true,
gridLines: {
color: darkFgColor,
zeroLineColor: fgColor
}
}]
},
bezierCurve: false,
animation: {
onComplete: done
}
}
});
const vscode = acquireVsCodeApi();
var image;
function done(){
image = document.getElementById('myChart').toDataURL('image/png');
}
function save(){
if (image) {
vscode.postMessage({
window.addEventListener('message', event => {
const message = event.data;
switch (message.command) {
case 'savePlot':
save()
break;
}
});
panel.webview.onDidReceiveMessage(
message => {
switch (message.command) {
var data = message.url.replace(/^data:image\/\w+;base64,/, "");
var buf = new Buffer(data, 'base64');
saveImage(res, buf);
return;
}
},
undefined,
context.subscriptions
);
}
function saveImage(res: Result, url: Buffer) {
const defaultUri = vscode.workspace.workspaceFolders
? vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, `/analysis/${res.getParentName()}-${res.getLabel()}.png`)
: undefined;
vscode.window.showSaveDialog({ defaultUri: defaultUri }).then(f => {
if (f) {
fs.writeFileSync(f.fsPath, url);
}
});
try {
fs.accessSync(p);
} catch (err) {
return false;