Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions extensions/ql-vscode/src/common/interface-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,8 +598,7 @@ export type FromModelEditorMessage =
| SetModeledMethodMessage;

export type FromMethodModelingMessage =
| TelemetryMessage
| UnhandledErrorMessage
| CommonFromViewMessages
| SetModeledMethodMessage;

interface SetMethodMessage {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as vscode from "vscode";
import { Uri, WebviewViewProvider } from "vscode";
import { WebviewKind, WebviewMessage, getHtmlForWebview } from "./webview-html";
import { Disposable } from "../disposable-object";
import { App } from "../app";

export abstract class AbstractWebviewViewProvider<
ToMessage extends WebviewMessage,
FromMessage extends WebviewMessage,
> implements WebviewViewProvider
{
protected webviewView: vscode.WebviewView | undefined = undefined;
private disposables: Disposable[] = [];

constructor(
private readonly app: App,
private readonly webviewKind: WebviewKind,
) {}

/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
public resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [Uri.file(this.app.extensionPath)],
};

const html = getHtmlForWebview(
this.app,
webviewView.webview,
this.webviewKind,
{
allowInlineStyles: true,
allowWasmEval: false,
},
);

webviewView.webview.html = html;

this.webviewView = webviewView;

webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));
webviewView.onDidDispose(() => this.dispose());
}

protected get isShowingView() {
return this.webviewView?.visible ?? false;
}

protected async postMessage(msg: ToMessage): Promise<void> {
await this.webviewView?.webview.postMessage(msg);
}

protected dispose() {
while (this.disposables.length > 0) {
const disposable = this.disposables.pop()!;
disposable.dispose();
}

this.webviewView = undefined;
}

protected push<T extends Disposable>(obj: T): T {
if (obj !== undefined) {
this.disposables.push(obj);
}
return obj;
}

protected abstract onMessage(msg: FromMessage): Promise<void>;

/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
protected onWebViewLoaded(): void {
// Do nothing by default.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export class MethodModelingPanel extends DisposableObject {
super();

this.provider = new MethodModelingViewProvider(app, modelingStore);
this.push(this.provider);
this.push(
window.registerWebviewViewProvider(
MethodModelingViewProvider.viewType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,82 +1,51 @@
import * as vscode from "vscode";
import { Uri, WebviewViewProvider } from "vscode";
import { getHtmlForWebview } from "../../common/vscode/webview-html";
import { FromMethodModelingMessage } from "../../common/interface-types";
import {
FromMethodModelingMessage,
ToMethodModelingMessage,
} from "../../common/interface-types";
import { telemetryListener } from "../../common/vscode/telemetry";
import { showAndLogExceptionWithTelemetry } from "../../common/logging/notifications";
import { extLogger } from "../../common/logging/vscode/loggers";
import { App } from "../../common/app";
import { redactableError } from "../../common/errors";
import { Method } from "../method";
import { DisposableObject } from "../../common/disposable-object";
import { ModelingStore } from "../modeling-store";
import { AbstractWebviewViewProvider } from "../../common/vscode/abstract-webview-view-provider";

export class MethodModelingViewProvider
extends DisposableObject
implements WebviewViewProvider
{
export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
ToMethodModelingMessage,
FromMethodModelingMessage
> {
public static readonly viewType = "codeQLMethodModeling";

private webviewView: vscode.WebviewView | undefined = undefined;

private method: Method | undefined = undefined;

constructor(
private readonly app: App,
app: App,
private readonly modelingStore: ModelingStore,
) {
super();
super(app, "method-modeling");
}

/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
public resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [Uri.file(this.app.extensionPath)],
};

const html = getHtmlForWebview(
this.app,
webviewView.webview,
"method-modeling",
{
allowInlineStyles: true,
allowWasmEval: false,
},
);

webviewView.webview.html = html;

webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));

this.webviewView = webviewView;

this.setInitialState(webviewView);
protected override onWebViewLoaded(): void {
this.setInitialState();
this.registerToModelingStoreEvents();
}

public async setMethod(method: Method): Promise<void> {
this.method = method;

if (this.webviewView) {
await this.webviewView.webview.postMessage({
if (this.isShowingView) {
await this.postMessage({
t: "setMethod",
method,
});
}
}

private setInitialState(webviewView: vscode.WebviewView): void {
private setInitialState(): void {
const selectedMethod = this.modelingStore.getSelectedMethodDetails();
if (selectedMethod) {
void webviewView.webview.postMessage({
void this.postMessage({
t: "setSelectedMethod",
method: selectedMethod.method,
modeledMethod: selectedMethod.modeledMethod,
Expand All @@ -85,24 +54,18 @@ export class MethodModelingViewProvider
}
}

private async onMessage(msg: FromMethodModelingMessage): Promise<void> {
protected override async onMessage(
msg: FromMethodModelingMessage,
): Promise<void> {
switch (msg.t) {
case "setModeledMethod": {
const activeState = this.modelingStore.getStateForActiveDb();
if (!activeState) {
throw new Error("No active state found in modeling store");
}
this.modelingStore.updateModeledMethod(
activeState.databaseItem,
msg.method,
);
case "viewLoaded":
this.onWebViewLoaded();
break;
}

case "telemetry": {
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
}

case "unhandledError":
void showAndLogExceptionWithTelemetry(
extLogger,
Expand All @@ -112,6 +75,18 @@ export class MethodModelingViewProvider
)`Unhandled error in method modeling view: ${msg.error.message}`,
);
break;

case "setModeledMethod": {
const activeState = this.modelingStore.getStateForActiveDb();
if (!activeState) {
throw new Error("No active state found in modeling store");
}
this.modelingStore.updateModeledMethod(
activeState.databaseItem,
msg.method,
);
break;
}
}
}

Expand Down