Add model editor view tracker

This is used for registering which model editor views are currently
active. This will be used to determine which view to send the "reveal
method" command to. It can also be used in the future to limit the
number of instances of the model editor that can be opened for a
database.

This uses the same pattern as variant analyses with a separate interface
for the view to avoid having circular dependencies.
This commit is contained in:
Koen Vlaswinkel
2023-10-02 13:35:00 +02:00
parent 8b8bacb718
commit 2872a2dcaf
5 changed files with 73 additions and 1 deletions

View File

@@ -20,12 +20,14 @@ import { setUpPack } from "./model-editor-queries";
import { MethodModelingPanel } from "./method-modeling/method-modeling-panel";
import { ModelingStore } from "./modeling-store";
import { showResolvableLocation } from "../databases/local-databases/locations";
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
export class ModelEditorModule extends DisposableObject {
private readonly queryStorageDir: string;
private readonly modelingStore: ModelingStore;
private readonly editorViewTracker: ModelEditorViewTracker<ModelEditorView>;
private readonly methodsUsagePanel: MethodsUsagePanel;
private readonly methodModelingPanel: MethodModelingPanel;
@@ -39,6 +41,7 @@ export class ModelEditorModule extends DisposableObject {
super();
this.queryStorageDir = join(baseQueryStorageDir, "model-editor-results");
this.modelingStore = new ModelingStore(app);
this.editorViewTracker = new ModelEditorViewTracker();
this.methodsUsagePanel = this.push(
new MethodsUsagePanel(this.modelingStore, cliServer),
);
@@ -148,6 +151,7 @@ export class ModelEditorModule extends DisposableObject {
const view = new ModelEditorView(
this.app,
this.modelingStore,
this.editorViewTracker,
this.databaseManager,
this.cliServer,
this.queryRunner,

View File

@@ -0,0 +1,37 @@
interface ModelEditorViewInterface {
databaseUri: string;
}
export class ModelEditorViewTracker<
T extends ModelEditorViewInterface = ModelEditorViewInterface,
> {
private readonly views = new Map<string, T[]>();
constructor() {}
public registerView(view: T): void {
const databaseUri = view.databaseUri;
if (!this.views.has(databaseUri)) {
this.views.set(databaseUri, []);
}
this.views.get(databaseUri)?.push(view);
}
public unregisterView(view: T): void {
const views = this.views.get(view.databaseUri);
if (!views) {
return;
}
const index = views.indexOf(view);
if (index !== -1) {
views.splice(index, 1);
}
}
public getViews(databaseUri: string): T[] {
return this.views.get(databaseUri) ?? [];
}
}

View File

@@ -42,6 +42,7 @@ import { getLanguageDisplayName } from "../common/query-language";
import { AutoModeler } from "./auto-modeler";
import { telemetryListener } from "../common/vscode/telemetry";
import { ModelingStore } from "./modeling-store";
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
export class ModelEditorView extends AbstractWebview<
ToModelEditorMessage,
@@ -52,6 +53,7 @@ export class ModelEditorView extends AbstractWebview<
public constructor(
protected readonly app: App,
private readonly modelingStore: ModelingStore,
private readonly viewTracker: ModelEditorViewTracker<ModelEditorView>,
private readonly databaseManager: DatabaseManager,
private readonly cliServer: CodeQLCliServer,
private readonly queryRunner: QueryRunner,
@@ -66,6 +68,8 @@ export class ModelEditorView extends AbstractWebview<
this.modelingStore.initializeStateForDb(databaseItem);
this.registerToModelingStoreEvents();
this.viewTracker.registerView(this);
this.autoModeler = new AutoModeler(
app,
cliServer,
@@ -181,7 +185,7 @@ export class ModelEditorView extends AbstractWebview<
}
protected onPanelDispose(): void {
// Nothing to do here
this.viewTracker.unregisterView(this);
}
protected async onMessage(msg: FromModelEditorMessage): Promise<void> {
@@ -338,6 +342,10 @@ export class ModelEditorView extends AbstractWebview<
]);
}
public get databaseUri(): string {
return this.databaseItem.databaseUri.toString();
}
private async setViewState(): Promise<void> {
const showLlmButton =
this.databaseItem.language === "java" && showLlmGeneration();
@@ -497,6 +505,7 @@ export class ModelEditorView extends AbstractWebview<
const view = new ModelEditorView(
this.app,
this.modelingStore,
this.viewTracker,
this.databaseManager,
this.cliServer,
this.queryRunner,

View File

@@ -0,0 +1,19 @@
import { mockedObject } from "../../vscode-tests/utils/mocking.helpers";
import { ModelEditorViewTracker } from "../../../src/model-editor/model-editor-view-tracker";
import { ModelEditorView } from "../../../src/model-editor/model-editor-view";
export function createMockModelEditorViewTracker({
registerView = jest.fn(),
unregisterView = jest.fn(),
getViews = jest.fn(),
}: {
registerView?: ModelEditorViewTracker["registerView"];
unregisterView?: ModelEditorViewTracker["unregisterView"];
getViews?: ModelEditorViewTracker["getViews"];
} = {}): ModelEditorViewTracker<ModelEditorView> {
return mockedObject<ModelEditorViewTracker<ModelEditorView>>({
registerView,
unregisterView,
getViews,
});
}

View File

@@ -9,10 +9,12 @@ import { mockEmptyDatabaseManager } from "../query-testing/test-runner-helpers";
import { QueryRunner } from "../../../../src/query-server";
import { ExtensionPack } from "../../../../src/model-editor/shared/extension-pack";
import { createMockModelingStore } from "../../../__mocks__/model-editor/modelingStoreMock";
import { createMockModelEditorViewTracker } from "../../../__mocks__/model-editor/modelEditorViewTrackerMock";
describe("ModelEditorView", () => {
const app = createMockApp({});
const modelingStore = createMockModelingStore();
const viewTracker = createMockModelEditorViewTracker();
const databaseManager = mockEmptyDatabaseManager();
const cliServer = mockedObject<CodeQLCliServer>({});
const queryRunner = mockedObject<QueryRunner>({});
@@ -38,6 +40,7 @@ describe("ModelEditorView", () => {
view = new ModelEditorView(
app,
modelingStore,
viewTracker,
databaseManager,
cliServer,
queryRunner,