Skip to content
Snippets Groups Projects
Commit 3695e3bc authored by Sytze de Witte's avatar Sytze de Witte
Browse files

Added Analysis results view in panel

parent 8c3661ab
No related branches found
No related tags found
No related merge requests found
File added
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black" width="18px" height="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
\ No newline at end of file
......@@ -24,8 +24,55 @@
{
"command": "modest.add-parameters",
"title": "Add parameters"
},
{
"command": "analysisResults.copyValue",
"title": "Copy value",
"icon": {
"light": "media/light/copy.svg",
"dark": "media/dark/copy.svg"
}
},
{
"command": "analysisResults.copyItem",
"title": "Copy item"
},
{
"command": "analysisResults.load",
"title": "Load analysis results"
},
{
"command": "analysisResults.export",
"title": "Export to JSON file"
}
],
"menus":{
"view/title": [
{
"command": "analysisResults.load",
"when": "view == analysisResults"
},
{
"command": "analysisResults.export",
"when": "view == analysisResults"
}
],
"view/item/context": [
{
"command": "analysisResults.copyValue",
"when": "view == analysisResults",
"group": "inline"
},
{
"command": "analysisResults.copyValue",
"when": "view == analysisResults"
},
{
"command": "analysisResults.copyItem",
"when": "view == analysisResults"
}
]
},
"commandPalette": [
{
"command": "modest.simulate",
......@@ -83,6 +130,13 @@
"title": "Modest",
"icon": "media/modest-icon-simple.svg"
}
],
"panel": [
{
"id": "modest-panel",
"title": "Modest",
"icon": "media/modest-icon-simple.svg"
}
]
},
"views": {
......@@ -94,8 +148,20 @@
"icon": "media/modest-icon-simple.svg",
"contextualTitle": "Modest"
}
],
"modest-panel": [
{
"id": "analysisResults",
"name": "Analysis Results"
}
]
},
"viewsWelcome": [
{
"view": "analysisResults",
"contents": "Model analysis results will be shown here."
}
],
"snippets": [
{
"language": "modest",
......@@ -106,7 +172,8 @@
"activationEvents": [
"onCommand:modest.simulate",
"onLanguage:modest",
"onView:modestSidebar"
"onView:modestSidebar",
"onView:analysisResults"
],
"main": "./out/extension.js",
"scripts": {
......@@ -133,4 +200,4 @@
"dependencies": {
"vscode-languageclient": "^7.0.0"
}
}
\ No newline at end of file
}
import * as vscode from "vscode";
import * as fs from "fs";
import * as path from "path";
import { LogTraceNotification } from "vscode-languageclient";
export class AnalysisResultsProvider
implements vscode.TreeDataProvider<Result> {
private _onDidChangeTreeData: vscode.EventEmitter<
Result | undefined | null | void
> = new vscode.EventEmitter<Result | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<
Result | undefined | null | void
> = this._onDidChangeTreeData.event;
private jsonPath: string;
private jsonObject: JSON | null;
constructor() {
this.jsonPath = "";
this.jsonObject = null;
}
refresh(): void {
this._onDidChangeTreeData.fire();
}
getTreeItem(element: Result): vscode.TreeItem {
return element;
}
getChildren(element?: Result): Thenable<Result[]> {
if (element) {
var res: Result[] = [];
if (element.getValues()) {
res = res.concat(element.getValues());
}
if (element.getData()) {
res = res.concat(element.getData());
}
return Promise.resolve(res);
} else {
if (this.pathExists(this.jsonPath)) {
return Promise.resolve(this.getResultsFromJSON(this.jsonPath));
} else {
return Promise.resolve([]);
}
}
}
setJsonPath(newPath: string) {
this.jsonPath = newPath;
this.refresh();
}
getResultsFromJSON(jsonPath: string) {
var jsonFile = fs.readFileSync(jsonPath, "utf-8");
var jsonObject = JSON.parse(jsonFile.trim());
this.jsonObject = jsonObject;
const data = jsonObject["data"];
return this.dataToResult(data);
}
dataToResult(data: JSON[]) {
var results: Result[] = [];
data.forEach((element: { [x: string]: any }) => {
var label = "";
if (element["group"]) {
label = element["group"];
} else if (element["property"]) {
label = element["property"];
}
var value = element["value"] ? String(element["value"]) : "";
var values = element["values"]
? this.valuesToResults(element["values"])
: [];
var data = element["data"]
? this.dataToResult(element["data"])
: [];
results.push(
new Result(
label,
value,
values,
data,
vscode.TreeItemCollapsibleState.Collapsed
)
);
});
return results;
}
valuesToResults(values: JSON[]) {
var results: Result[] = [];
values.forEach((v: { [x: string]: any }) => {
var label = String(v["name"]);
var unit = v["unit"] ? " " + String(v["unit"]) : "";
var value = v["value"] + unit;
results.push(
new Result(
label,
value,
[],
[],
vscode.TreeItemCollapsibleState.None
)
);
});
return results;
}
getJsonObject() {
return this.jsonObject;
}
private pathExists(p: string): boolean {
try {
fs.accessSync(p);
} catch (err) {
return false;
}
return true;
}
}
export class Result extends vscode.TreeItem {
constructor(
public readonly label: string,
private value: string,
private values: Result[],
private data: Result[],
public readonly collapsibleState: vscode.TreeItemCollapsibleState
) {
super(label, collapsibleState);
this.tooltip = `${this.label}: ${this.value}`;
this.description = this.value;
}
getLabel() {
return this.label;
}
getValue() {
return this.value;
}
getValues() {
return this.values;
}
getData() {
return this.data;
}
/*
iconPath = {
light: path.join(__filename, '..', '..', 'resources', 'light', 'dependency.svg'),
dark: path.join(__filename, '..', '..', 'resources', 'dark', 'dependency.svg')
};
*/
}
// tslint:disable
"use strict";
import { workspace, ExtensionContext, TextDocument, CancellationToken, ProviderResult } from 'vscode';
import * as vscode from 'vscode';
import {
workspace,
ExtensionContext,
TextDocument,
CancellationToken,
ProviderResult,
} from "vscode";
import * as vscode from "vscode";
import {
DocumentFormattingParams,
DocumentFormattingRegistrationOptions,
......@@ -13,25 +19,36 @@ import {
ServerOptions,
TextEdit,
Trace,
TransportKind
} from 'vscode-languageclient/node';
TransportKind,
} from "vscode-languageclient/node";
import { ModestCommands } from "./commands";
import { AnalysisResultsProvider, Result } from "./analysisResults";
import * as path from "path";
import { fstat, existsSync } from 'fs';
import { fstat, existsSync } from "fs";
import * as fs from "fs";
let client: LanguageClient | undefined;
export let provider: ModestSidebarProvider;
export let analysisResultsProvider: AnalysisResultsProvider;
export function modestExecutable(): string | undefined {
let executable: string | undefined =
vscode.workspace.getConfiguration("modest").get("executableLocation");
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 => {
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");
vscode.commands.executeCommand(
"workbench.action.openSettings",
"modest.executableLocation"
);
}
});
return;
......@@ -39,7 +56,7 @@ export function modestExecutable(): string | undefined {
return executable;
}
function createClient(): LanguageClient | undefined{
function createClient(): LanguageClient | undefined {
// TODO: Check if path exists for a better user experience(so it doesn't spam the user with errors)
// The server is implemented in node
......@@ -51,11 +68,17 @@ function createClient(): LanguageClient | undefined{
}
if (!existsSync(serverExe)) {
vscode.window.showErrorMessage(
"The specified modest executable could not be found", "Go to settings")
.then(result => {
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");
vscode.commands.executeCommand(
"workbench.action.openSettings",
"modest.executableLocation"
);
}
});
return;
......@@ -67,14 +90,14 @@ function createClient(): LanguageClient | undefined{
// run: { command: serverExe, args: ['-lsp', '-d'] },
run: {
command: serverExe,
args: ['startlspserver'],
args: ["startlspserver"],
transport: TransportKind.stdio,
},
// debug: { command: serverExe, args: ['-lsp', '-d'] }
debug: {
command: "dotnet",
transport: TransportKind.stdio,
args: [serverExe.replace(".exe", "") + ".dll", 'startlspserver'], // Hacky
args: [serverExe.replace(".exe", "") + ".dll", "startlspserver"], // Hacky
runtime: "",
},
};
......@@ -95,19 +118,18 @@ function createClient(): LanguageClient | undefined{
//},
};
// Create the language client and start the client.
const client = new LanguageClient(
"ModestExtension",
"Modest Extension",
serverOptions,
clientOptions);
clientOptions
);
client.registerProposedFeatures();
client.trace = Trace.Verbose;
return client;
}
export function activate(context: ExtensionContext) {
client = createClient();
if (client) {
......@@ -120,7 +142,7 @@ export function activate(context: ExtensionContext) {
context.subscriptions.push(ModestCommands.simCommand);
context.subscriptions.push(ModestCommands.addParameters);
vscode.workspace.onDidChangeConfiguration(a => {
vscode.workspace.onDidChangeConfiguration((a) => {
if (a.affectsConfiguration("modest.executableLocation")) {
if (client) {
client.stop();
......@@ -135,49 +157,103 @@ export function activate(context: ExtensionContext) {
});
provider = new ModestSidebarProvider(context.extensionUri);
analysisResultsProvider = new AnalysisResultsProvider();
vscode.window.createTreeView("analysisResults", {
treeDataProvider: analysisResultsProvider,
});
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(ModestSidebarProvider.viewType, provider));
vscode.window.registerWebviewViewProvider(
ModestSidebarProvider.viewType,
provider
)
);
vscode.commands.registerCommand(
"analysisResults.copyValue",
(res: Result) => copyToClipboard(res.getValue())
);
vscode.commands.registerCommand(
"analysisResults.copyItem",
(res: Result) => copyToClipboard(`${res.getLabel()}: ${res.getValue()}`)
);
vscode.commands.registerCommand(
"analysisResults.load",
() => loadResults(analysisResultsProvider)
);
vscode.commands.registerCommand(
"analysisResults.export",
() => exportResults(analysisResultsProvider)
);
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
return client.stop();
}
function copyToClipboard(text: string) {
vscode.env.clipboard.writeText(text);
vscode.window.showInformationMessage(`Copied ${text} to clipboard.`);
}
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()}.`);
} else {
vscode.window.showInformationMessage(`Could not open file.`);
}
});
}
function exportResults(provider: AnalysisResultsProvider) {
vscode.window.showSaveDialog().then(f => {
if (f) {
fs.writeFileSync(f.path, JSON.stringify(provider.getJsonObject()));
}
});
}
class ModestSidebarProvider implements vscode.WebviewViewProvider {
public static readonly viewType = 'modest.modestSidebar';
public static readonly viewType = "modest.modestSidebar";
private _view?: vscode.WebviewView;
constructor(
private readonly _extensionUri: vscode.Uri,
) { }
constructor(private readonly _extensionUri: vscode.Uri) {}
resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext<unknown>, token: vscode.CancellationToken): void | Thenable<void> {
resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext<unknown>,
token: vscode.CancellationToken
): void | Thenable<void> {
this._view = webviewView;
webviewView.webview.options = {
// Allow scripts in the webview
enableScripts: true,
localResourceRoots: [
this._extensionUri
]
localResourceRoots: [this._extensionUri],
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
webviewView.webview.onDidReceiveMessage(data => {
webviewView.webview.onDidReceiveMessage((data) => {
switch (data.type) {
case 'colorSelected':
{
vscode.window.activeTextEditor?.insertSnippet(new vscode.SnippetString(`#${data.value}`));
break;
}
case "colorSelected": {
vscode.window.activeTextEditor?.insertSnippet(
new vscode.SnippetString(`#${data.value}`)
);
break;
}
}
});
}
......@@ -192,17 +268,24 @@ class ModestSidebarProvider implements vscode.WebviewViewProvider {
private _getHtmlForWebview(webview: vscode.Webview) {
// Get the local path to main script run in the webview, then convert it to a uri we can use in the webview.
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js'));
const scriptUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "main.js")
);
// Do the same for the stylesheet.
const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'reset.css'));
const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'vscode.css'));
const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'main.css'));
const styleResetUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "reset.css")
);
const styleVSCodeUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vscode.css")
);
const styleMainUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "main.css")
);
// Use a nonce to only allow a specific script to be run.
const nonce = getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
......@@ -231,10 +314,13 @@ class ModestSidebarProvider implements vscode.WebviewViewProvider {
</html>`;
function getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
text += possible.charAt(
Math.floor(Math.random() * possible.length)
);
}
return text;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment