Skip to content

Commit ac91287

Browse files
authored
Merge pull request #2896 from github/koesie10/reveal-in-editor
Add reveal in editor button to method modeling panel
2 parents add7a25 + 487a753 commit ac91287

21 files changed

+345
-98
lines changed

extensions/ql-vscode/src/common/interface-types.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,12 +575,18 @@ interface SetModeledMethodMessage {
575575
method: ModeledMethod;
576576
}
577577

578+
interface RevealMethodMessage {
579+
t: "revealMethod";
580+
method: Method;
581+
}
582+
578583
export type ToModelEditorMessage =
579584
| SetExtensionPackStateMessage
580585
| SetMethodsMessage
581586
| SetModeledMethodsMessage
582587
| SetModifiedMethodsMessage
583-
| SetInProgressMethodsMessage;
588+
| SetInProgressMethodsMessage
589+
| RevealMethodMessage;
584590

585591
export type FromModelEditorMessage =
586592
| ViewLoadedMsg
@@ -597,9 +603,15 @@ export type FromModelEditorMessage =
597603
| HideModeledMethodsMessage
598604
| SetModeledMethodMessage;
599605

606+
interface RevealInEditorMessage {
607+
t: "revealInModelEditor";
608+
method: Method;
609+
}
610+
600611
export type FromMethodModelingMessage =
601612
| CommonFromViewMessages
602-
| SetModeledMethodMessage;
613+
| SetModeledMethodMessage
614+
| RevealInEditorMessage;
603615

604616
interface SetMethodMessage {
605617
t: "setMethod";

extensions/ql-vscode/src/model-editor/method-modeling/method-modeling-panel.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@ import { DisposableObject } from "../../common/disposable-object";
44
import { MethodModelingViewProvider } from "./method-modeling-view-provider";
55
import { Method } from "../method";
66
import { ModelingStore } from "../modeling-store";
7+
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
78

89
export class MethodModelingPanel extends DisposableObject {
910
private readonly provider: MethodModelingViewProvider;
1011

11-
constructor(app: App, modelingStore: ModelingStore) {
12+
constructor(
13+
app: App,
14+
modelingStore: ModelingStore,
15+
editorViewTracker: ModelEditorViewTracker,
16+
) {
1217
super();
1318

14-
this.provider = new MethodModelingViewProvider(app, modelingStore);
19+
this.provider = new MethodModelingViewProvider(
20+
app,
21+
modelingStore,
22+
editorViewTracker,
23+
);
1524
this.push(
1625
window.registerWebviewViewProvider(
1726
MethodModelingViewProvider.viewType,

extensions/ql-vscode/src/model-editor/method-modeling/method-modeling-view-provider.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import { extLogger } from "../../common/logging/vscode/loggers";
88
import { App } from "../../common/app";
99
import { redactableError } from "../../common/errors";
1010
import { Method } from "../method";
11-
import { ModelingStore } from "../modeling-store";
11+
import { DbModelingState, ModelingStore } from "../modeling-store";
1212
import { AbstractWebviewViewProvider } from "../../common/vscode/abstract-webview-view-provider";
13+
import { assertNever } from "../../common/helpers-pure";
14+
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
1315

1416
export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
1517
ToMethodModelingMessage,
@@ -22,6 +24,7 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
2224
constructor(
2325
app: App,
2426
private readonly modelingStore: ModelingStore,
27+
private readonly editorViewTracker: ModelEditorViewTracker,
2528
) {
2629
super(app, "method-modeling");
2730
}
@@ -77,17 +80,43 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
7780
break;
7881

7982
case "setModeledMethod": {
80-
const activeState = this.modelingStore.getStateForActiveDb();
81-
if (!activeState) {
82-
throw new Error("No active state found in modeling store");
83-
}
83+
const activeState = this.ensureActiveState();
84+
8485
this.modelingStore.updateModeledMethod(
8586
activeState.databaseItem,
8687
msg.method,
8788
);
8889
break;
8990
}
91+
case "revealInModelEditor":
92+
await this.revealInModelEditor(msg.method);
93+
94+
break;
95+
default:
96+
assertNever(msg);
97+
}
98+
}
99+
100+
private async revealInModelEditor(method: Method): Promise<void> {
101+
const activeState = this.ensureActiveState();
102+
103+
const views = this.editorViewTracker.getViews(
104+
activeState.databaseItem.databaseUri.toString(),
105+
);
106+
if (views.length === 0) {
107+
return;
90108
}
109+
110+
await Promise.all(views.map((view) => view.revealMethod(method)));
111+
}
112+
113+
private ensureActiveState(): DbModelingState {
114+
const activeState = this.modelingStore.getStateForActiveDb();
115+
if (!activeState) {
116+
throw new Error("No active state found in modeling store");
117+
}
118+
119+
return activeState;
91120
}
92121

93122
private registerToModelingStoreEvents(): void {

extensions/ql-vscode/src/model-editor/model-editor-module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import { setUpPack } from "./model-editor-queries";
2020
import { MethodModelingPanel } from "./method-modeling/method-modeling-panel";
2121
import { ModelingStore } from "./modeling-store";
2222
import { showResolvableLocation } from "../databases/local-databases/locations";
23+
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
2324

2425
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
2526

2627
export class ModelEditorModule extends DisposableObject {
2728
private readonly queryStorageDir: string;
2829
private readonly modelingStore: ModelingStore;
30+
private readonly editorViewTracker: ModelEditorViewTracker<ModelEditorView>;
2931
private readonly methodsUsagePanel: MethodsUsagePanel;
3032
private readonly methodModelingPanel: MethodModelingPanel;
3133

@@ -39,11 +41,12 @@ export class ModelEditorModule extends DisposableObject {
3941
super();
4042
this.queryStorageDir = join(baseQueryStorageDir, "model-editor-results");
4143
this.modelingStore = new ModelingStore(app);
44+
this.editorViewTracker = new ModelEditorViewTracker();
4245
this.methodsUsagePanel = this.push(
4346
new MethodsUsagePanel(this.modelingStore, cliServer),
4447
);
4548
this.methodModelingPanel = this.push(
46-
new MethodModelingPanel(app, this.modelingStore),
49+
new MethodModelingPanel(app, this.modelingStore, this.editorViewTracker),
4750
);
4851

4952
this.registerToModelingStoreEvents();
@@ -148,6 +151,7 @@ export class ModelEditorModule extends DisposableObject {
148151
const view = new ModelEditorView(
149152
this.app,
150153
this.modelingStore,
154+
this.editorViewTracker,
151155
this.databaseManager,
152156
this.cliServer,
153157
this.queryRunner,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Method } from "./method";
2+
3+
interface ModelEditorViewInterface {
4+
databaseUri: string;
5+
6+
revealMethod(method: Method): Promise<void>;
7+
}
8+
9+
export class ModelEditorViewTracker<
10+
T extends ModelEditorViewInterface = ModelEditorViewInterface,
11+
> {
12+
private readonly views = new Map<string, T[]>();
13+
14+
constructor() {}
15+
16+
public registerView(view: T): void {
17+
const databaseUri = view.databaseUri;
18+
19+
if (!this.views.has(databaseUri)) {
20+
this.views.set(databaseUri, []);
21+
}
22+
23+
this.views.get(databaseUri)?.push(view);
24+
}
25+
26+
public unregisterView(view: T): void {
27+
const views = this.views.get(view.databaseUri);
28+
if (!views) {
29+
return;
30+
}
31+
32+
const index = views.indexOf(view);
33+
if (index !== -1) {
34+
views.splice(index, 1);
35+
}
36+
}
37+
38+
public getViews(databaseUri: string): T[] {
39+
return this.views.get(databaseUri) ?? [];
40+
}
41+
}

extensions/ql-vscode/src/model-editor/model-editor-view.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { getLanguageDisplayName } from "../common/query-language";
4242
import { AutoModeler } from "./auto-modeler";
4343
import { telemetryListener } from "../common/vscode/telemetry";
4444
import { ModelingStore } from "./modeling-store";
45+
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
4546

4647
export class ModelEditorView extends AbstractWebview<
4748
ToModelEditorMessage,
@@ -52,6 +53,7 @@ export class ModelEditorView extends AbstractWebview<
5253
public constructor(
5354
protected readonly app: App,
5455
private readonly modelingStore: ModelingStore,
56+
private readonly viewTracker: ModelEditorViewTracker<ModelEditorView>,
5557
private readonly databaseManager: DatabaseManager,
5658
private readonly cliServer: CodeQLCliServer,
5759
private readonly queryRunner: QueryRunner,
@@ -66,6 +68,8 @@ export class ModelEditorView extends AbstractWebview<
6668
this.modelingStore.initializeStateForDb(databaseItem);
6769
this.registerToModelingStoreEvents();
6870

71+
this.viewTracker.registerView(this);
72+
6973
this.autoModeler = new AutoModeler(
7074
app,
7175
cliServer,
@@ -181,7 +185,7 @@ export class ModelEditorView extends AbstractWebview<
181185
}
182186

183187
protected onPanelDispose(): void {
184-
// Nothing to do here
188+
this.viewTracker.unregisterView(this);
185189
}
186190

187191
protected async onMessage(msg: FromModelEditorMessage): Promise<void> {
@@ -338,6 +342,19 @@ export class ModelEditorView extends AbstractWebview<
338342
]);
339343
}
340344

345+
public get databaseUri(): string {
346+
return this.databaseItem.databaseUri.toString();
347+
}
348+
349+
public async revealMethod(method: Method): Promise<void> {
350+
this.panel?.reveal();
351+
352+
await this.postMessage({
353+
t: "revealMethod",
354+
method,
355+
});
356+
}
357+
341358
private async setViewState(): Promise<void> {
342359
const showLlmButton =
343360
this.databaseItem.language === "java" && showLlmGeneration();
@@ -497,6 +514,7 @@ export class ModelEditorView extends AbstractWebview<
497514
const view = new ModelEditorView(
498515
this.app,
499516
this.modelingStore,
517+
this.viewTracker,
500518
this.databaseManager,
501519
this.cliServer,
502520
this.queryRunner,

extensions/ql-vscode/src/model-editor/modeling-store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Method, Usage } from "./method";
66
import { ModeledMethod } from "./modeled-method";
77
import { INITIAL_HIDE_MODELED_METHODS_VALUE } from "./shared/hide-modeled-methods";
88

9-
interface DbModelingState {
9+
export interface DbModelingState {
1010
databaseItem: DatabaseItem;
1111
methods: Method[];
1212
hideModeledMethods: boolean;

extensions/ql-vscode/src/view/method-modeling/MethodModeling.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { MethodName } from "../model-editor/MethodName";
77
import { ModeledMethod } from "../../model-editor/modeled-method";
88
import { MethodModelingInputs } from "./MethodModelingInputs";
99
import { VSCodeTag } from "@vscode/webview-ui-toolkit/react";
10+
import { ReviewInEditorButton } from "./ReviewInEditorButton";
1011

1112
const Container = styled.div`
1213
padding: 0.3rem;
@@ -64,6 +65,7 @@ export const MethodModeling = ({
6465
modeledMethod={modeledMethod}
6566
onChange={onChange}
6667
/>
68+
<ReviewInEditorButton method={method} />
6769
</Container>
6870
);
6971
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as React from "react";
2+
import { useCallback } from "react";
3+
import { styled } from "styled-components";
4+
import { vscode } from "../vscode-api";
5+
import TextButton from "../common/TextButton";
6+
import { Method } from "../../model-editor/method";
7+
8+
const Button = styled(TextButton)`
9+
margin-top: 0.5rem;
10+
`;
11+
12+
type Props = {
13+
method: Method;
14+
};
15+
16+
export const ReviewInEditorButton = ({ method }: Props) => {
17+
const handleClick = useCallback(() => {
18+
vscode.postMessage({
19+
t: "revealInModelEditor",
20+
method,
21+
});
22+
}, [method]);
23+
24+
return <Button onClick={handleClick}>Review in editor</Button>;
25+
};

extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from "react";
2-
import { useCallback, useMemo, useState } from "react";
2+
import { useCallback, useEffect, useMemo, useState } from "react";
33
import { styled } from "styled-components";
44
import { Method } from "../../model-editor/method";
55
import { ModeledMethod } from "../../model-editor/modeled-method";
@@ -76,6 +76,7 @@ export type LibraryRowProps = {
7676
inProgressMethods: InProgressMethods;
7777
viewState: ModelEditorViewState;
7878
hideModeledMethods: boolean;
79+
revealedMethodSignature: string | null;
7980
onChange: (modeledMethod: ModeledMethod) => void;
8081
onSaveModelClick: (
8182
methods: Method[],
@@ -100,6 +101,7 @@ export const LibraryRow = ({
100101
inProgressMethods,
101102
viewState,
102103
hideModeledMethods,
104+
revealedMethodSignature,
103105
onChange,
104106
onSaveModelClick,
105107
onGenerateFromLlmClick,
@@ -117,6 +119,14 @@ export const LibraryRow = ({
117119
setExpanded((oldIsExpanded) => !oldIsExpanded);
118120
}, []);
119121

122+
useEffect(() => {
123+
// If any of the methods in this group is the one that should be revealed, we should expand
124+
// this group so the method can highlight itself.
125+
if (methods.some((m) => m.signature === revealedMethodSignature)) {
126+
setExpanded(true);
127+
}
128+
}, [methods, revealedMethodSignature]);
129+
120130
const handleModelWithAI = useCallback(
121131
async (e: React.MouseEvent) => {
122132
onGenerateFromLlmClick(title, methods, modeledMethods);
@@ -227,6 +237,7 @@ export const LibraryRow = ({
227237
inProgressMethods={inProgressMethods}
228238
mode={viewState.mode}
229239
hideModeledMethods={hideModeledMethods}
240+
revealedMethodSignature={revealedMethodSignature}
230241
onChange={onChange}
231242
/>
232243
<SectionDivider />

0 commit comments

Comments
 (0)