Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
extension.ts 10.12 KiB
// tslint:disable
"use strict";

import {
    workspace,
    ExtensionContext,
    TextDocument,
    CancellationToken,
    ProviderResult,
} from "vscode";
import * as vscode from "vscode";
import {
    DocumentFormattingParams,
    DocumentFormattingRegistrationOptions,
    DocumentFormattingRequest,
    FormattingOptions,
    LanguageClient,
    LanguageClientOptions,
    ServerOptions,
    TextEdit,
    Trace,
    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 * 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");

    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)

    // The server is implemented in node
    //let serverExe = "D:\\Desktop\\designproject\\modest-toolset\\Binaries\\Release\\win-x64\\Modest.LanguageServer.exe";
    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,
            args: ["startlspserver"],
            transport: TransportKind.stdio,
        },
        // debug: { command: serverExe, args: ['-lsp', '-d'] }
        debug: {
            command: "dotnet",
            transport: TransportKind.stdio,
            args: [serverExe.replace(".exe", "") + ".dll", "startlspserver"], // Hacky
            runtime: "",
        },
    };

    // Options to control the language client
    let clientOptions: LanguageClientOptions = {
        // Register the server for plain text documents
        documentSelector: [
            {
                pattern: "**/*.modest",
            },
        ],
        progressOnInitialization: true,
        //synchronize: {
        // 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,
        clientOptions
    );
    client.registerProposedFeatures();
    client.trace = Trace.Verbose;
    return client;
}

export function activate(context: ExtensionContext) {
    client = createClient();
    if (client) {
        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);
    }

    context.subscriptions.push(ModestCommands.simCommand);
    context.subscriptions.push(ModestCommands.addParameters);

    vscode.workspace.onDidChangeConfiguration((a) => {
        if (a.affectsConfiguration("modest.executableLocation")) {
            if (client) {
                client.stop();
                client = undefined;
            }
            client = createClient();
            if (client) {
                let langClient = client.start();
                context.subscriptions.push(langClient);
            }
        }
    });

    provider = new ModestSidebarProvider(context.extensionUri);
    analysisResultsProvider = new AnalysisResultsProvider();

    vscode.window.createTreeView("analysisResults", {
        treeDataProvider: analysisResultsProvider,
    });

    context.subscriptions.push(
        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";

    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) => {
            switch (data.type) {
                case "colorSelected": {
                    vscode.window.activeTextEditor?.insertSnippet(
                        new vscode.SnippetString(`#${data.value}`)
                    );
                    break;
                }
            }
        });
    }

    /**
     * sendMessage
     */
    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")
        );

        // 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">
				<!--
					Use a content security policy to only allow loading images from https or from our extension directory,
					and only allow scripts that have a specific nonce.
				-->
				<meta http-equiv="Content-Security-Policy" content="default-src 'none'; 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">

				<title>Modest run dialog</title>
			</head>
			<body>
                <h3>Undefined constants</h3>
				<ul class="option-list" id="undefined-constants"> </ul>
                <h3>Defined constants</h3>
                <ul class="option-list" id="defined-constants"> </ul>
                <h3>Options</h3>
                <ul class="option-list" id="options"> </ul>
				<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;
        }
    }
}