Skip to content
Snippets Groups Projects
sidebar.ts 12.2 KiB
Newer Older
import * as vscode from "vscode";
import {
    TextDocument
} from "vscode";
import {
    NotificationType,
    ProgressType,
    TextDocumentIdentifier
} from "vscode-languageclient/node";
import { analysisResultsProvider, client, provider } from './extension';


let localToolNames: Array<string> | undefined;
let localDocumentVars: Array<{ uri: string, constants: Array<{ name: string, value: string }>, distributions: Array<{ name: string, value: string }> }> = [];
let localParameters: Array<{ toolName: string, parameters: Array<{ id: string, value: string, type: ParameterType, category: string }> }> = [];

//#region interfaces
interface ParameterDefinitions {
    parameterDefinitions: Array<ParameterDefinition>,
}
interface ParameterDefinition {
    id: string,
    name: string,
    description: string,
    category: string,
    type: ParameterType
    isOptional: boolean,
    defaultValue: string,
}

interface ParameterType {
    valueType: string,
    innerType: Array<ParameterType>,
    possibleValues: Array<string>,
}

interface ProgressIndication {
    message: string,
    progress: number
}

interface ResultNotification {
    progressToken: string,
    data: string
}
//#endregion

export function initializeTools() {
    client?.sendRequest<any>("modest/getTools").then(data => {
        localToolNames = data.availableTools;
            tools: localToolNames
export function getDocumentVars(document: TextDocument) {
    if (document.languageId === "modest") {
        if (document.uri) {
            let uri = document.uri.toString();
            let jsonObject = { "textDocument": TextDocumentIdentifier.create(uri) };
            client?.sendRequest<{ constants: Array<string>, distributions: Array<string> }>("modest/getDocumentVars", jsonObject).then(data => {
                const index = localDocumentVars.findIndex(x => x.uri === uri);
                const newConstants = data.constants.map(constant => {
                const newDistributions = data.distributions.map(distribution => {
                    return { name: distribution, value: "" };
                });
                    localDocumentVars.push({ "uri": uri, constants: newConstants, distributions: newDistributions });
                    localDocumentVars[index].constants = newConstants;
                    localDocumentVars[index].distributions = newDistributions;
                    type: "updateDocumentVars",
                    documentVars: localDocumentVars,
                    "uri": uri
                });
            });
        }
    }
}

function getParameters(toolName: string) {
    let jsonObject = { "toolName": toolName };
    client?.sendRequest<ParameterDefinitions>("modest/getParameters", jsonObject).then(data => {
        const index = localParameters.findIndex(x => x.toolName === toolName);
        const newParameters = data.parameterDefinitions.map(parameter => {
            return { id: parameter.id, value: parameter.defaultValue, type: parameter.type, category: parameter.category };
            localParameters.push({ toolName: toolName, parameters: newParameters });
            localParameters[index].parameters = newParameters;
            parameters: localParameters,
            toolName: toolName
        });
    });
}

function runTool(uri: string, toolName: string, constants: { name: string; value: string; }[], suppliedParameters: { id: string; value: string; }[]) {
    const toolIndex = localParameters.findIndex(x => x.toolName === toolName);
    let serverParameters: Array<{ id: string, value: string }> = [];
    if (toolIndex !== -1) {
        for (const parameter of suppliedParameters) {
            const parameterIndex = localParameters[toolIndex].parameters.findIndex(x => x.id === parameter.id);
                if (localParameters[toolIndex].parameters[parameterIndex].value !== parameter.value) {
                    serverParameters.push(parameter);
                }
            }
        }
    }

    let jsonObject = {
        textDocument: TextDocumentIdentifier.create(uri),
        toolName: toolName,
        constants: constants,
        parameters: serverParameters,
        progressToken: uri + toolName + constants + serverParameters + Date.now()
    };
    vscode.window.activeTextEditor?.document.save();
    vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: "Running " + toolName }, async (progress, token) => {
        await new Promise<null>(async (resolve, _) => {
            let progressHandler = client?.onProgress(new ProgressType<ProgressIndication>(), jsonObject.progressToken, indication => {
                progress.report({ message: indication.message, increment: indication.progress * 100 });
            });
            let resultHandler = client?.onNotification(new NotificationType<ResultNotification>("modest/toolResult"), data => {
                if (data.progressToken === jsonObject.progressToken) {
                    if (data.data && data.data !== "") {
                        try {
                            analysisResultsProvider.setJsonString(data.data);
                    progressHandler?.dispose();
                    resultHandler?.dispose();
                    resolve(null);
                }
            });
            if (client === null) {
                vscode.window.showErrorMessage("Server not ready yet, try again later");
                resolve(null);
            } else {
                await client?.sendRequest<string>("modest/runTool", jsonObject, token);
            }
        });
    });
}
export class ModestSidebarProvider implements vscode.WebviewViewProvider {
    public static readonly viewType = "modest.modestSidebar";

    private _view?: vscode.WebviewView;

    constructor(private readonly _extensionUri: vscode.Uri) { }

    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],
        };

        webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
        webviewView.webview.onDidReceiveMessage(data => {
            console.log(data);
            switch (data.type) {
                case 'init': {
                    if (localToolNames) {
                            tools: localToolNames
                    if (localDocumentVars) {
                            type: "updateDocumentVars",
                            documentVars: localDocumentVars
                    if (data.toolName !== "") {
                        getParameters(data.toolName);
                    }
                    if (vscode.window.activeTextEditor) {
                        getDocumentVars(vscode.window.activeTextEditor.document);
                    }
                    break;
                }
                case 'runTool': {
                    runTool(data.uri, data.toolName, data.constants, data.parameters);
                    break;
                }
            }
        });
    }

    /**
     * sendMessage
     * @param {any} message
     */
    public sendMessage(message: any) {
        this._view?.show(true);
        this._view?.webview?.postMessage(message);
    }

    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")
        );

        // 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 styleCodicons = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css'));
        const fontCodicons = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'node_modules', 'vscode-codicons', 'dist', 'codicon.ttf'));

        // Use a nonce to only allow a specific script to be run.
        const nonce = getNonce();

        return `<!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${fontCodicons}; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <link href="${styleResetUri}" rel="stylesheet">
            <link href="${styleVSCodeUri}" rel="stylesheet">
            <link href="${styleMainUri}" rel="stylesheet">
            <link href="${styleCodicons}" rel="stylesheet">
            <title>Modest run dialog</title>
        </head>
        <body>
            <h3>Select tool</h3>
            <div id="run-box">
                <select class="tools-dropdown" id="tools"> </select>
                <button id="run-button"><i class="codicon codicon-play"></i></button>
            </div>

            <div class="pane-view">
                <div class="split-view-view">
                    <div class="pane vertical">
                        <div class="pane-header">
                            <div class="codicon codicon-chevron-down"></div>
                            <h3 class="title" title="Open Editors">Open constants</h3>
                        </div>
                        <div class="pane-body">
                            <ul class="option-list" id="constants">There are no undefined constants.</ul>
                        </div>
                    </div>
                    <div class="pane vertical">
                        <div class="pane-header">
                            <div class="codicon codicon-chevron-down"></div>
                            <h3 class="title" title="Open Editors">Distributions</h3>
                        </div>
                        <div class="pane-body">
                            <ul class="option-list" id="distributions">There are no distributions.</ul>
                        </div>
                    </div>
                    <div class="pane vertical">
                        <div class="pane-header">
                            <div class="codicon codicon-chevron-down"></div>
                            <h3 class="title" title="Open Editors">Parameters</h3>
                        </div>
                        <div class="pane-body">
                            <ul class="option-list" id="parameters">There are no parameters.</ul>
                        </div>
                    </div>
            </div>

            <script nonce="${nonce}" src="${scriptUri}"></script>
        </body>
        </html>
        `;

        function getNonce() {
            let text = "";
            const possible =
                "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            for (let i = 0; i < 32; i++) {
                text += possible.charAt(
                    Math.floor(Math.random() * possible.length)
                );
            }
            return text;
        }
    }
}