Skip to content

Commit c5517e0

Browse files
authored
CodeQL model editor: Add support for free text input in type models (#3217)
1 parent 8c0a8e0 commit c5517e0

4 files changed

Lines changed: 93 additions & 5 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useEffect, useRef } from "react";
2+
3+
/**
4+
* Call the callback after the value has not changed for a certain amount of time.
5+
* @param value
6+
* @param callback
7+
* @param delay
8+
*/
9+
export function useDebounceCallback<T>(
10+
value: T,
11+
callback: (value: T) => void,
12+
delay?: number,
13+
) {
14+
const callbackRef = useRef<(value: T) => void>(callback);
15+
16+
useEffect(() => {
17+
callbackRef.current = callback;
18+
}, [callback]);
19+
20+
useEffect(() => {
21+
const timer = setTimeout(() => callbackRef.current(value), delay || 500);
22+
23+
return () => {
24+
clearTimeout(timer);
25+
};
26+
}, [value, delay]);
27+
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import {
77
modeledMethodSupportsInput,
88
} from "../../model-editor/modeled-method";
99
import type { Method } from "../../model-editor/method";
10-
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
1110
import type { QueryLanguage } from "../../common/query-language";
1211
import { getModelsAsDataLanguage } from "../../model-editor/languages";
1312
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
1413
import { InputDropdown } from "./InputDropdown";
14+
import { ModelTypeTextbox } from "./ModelTypeTextbox";
1515

1616
type Props = {
1717
language: QueryLanguage;
@@ -67,7 +67,14 @@ export const ModelInputDropdown = ({
6767
: undefined;
6868

6969
if (modeledMethod?.type === "type") {
70-
return <ReadonlyDropdown value={modeledMethod.path} aria-label="Path" />;
70+
return (
71+
<ModelTypeTextbox
72+
modeledMethod={modeledMethod}
73+
typeInfo="path"
74+
onChange={onChange}
75+
aria-label="Path"
76+
/>
77+
);
7178
}
7279

7380
const modelAccepted = isModelAccepted(modeledMethod, modelingStatus);

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import {
77
modeledMethodSupportsOutput,
88
} from "../../model-editor/modeled-method";
99
import type { Method } from "../../model-editor/method";
10-
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
1110
import { getModelsAsDataLanguage } from "../../model-editor/languages";
1211
import type { QueryLanguage } from "../../common/query-language";
1312
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
1413
import { InputDropdown } from "./InputDropdown";
14+
import { ModelTypeTextbox } from "./ModelTypeTextbox";
1515

1616
type Props = {
1717
language: QueryLanguage;
@@ -69,8 +69,10 @@ export const ModelOutputDropdown = ({
6969

7070
if (modeledMethod?.type === "type") {
7171
return (
72-
<ReadonlyDropdown
73-
value={modeledMethod.relatedTypeName}
72+
<ModelTypeTextbox
73+
modeledMethod={modeledMethod}
74+
typeInfo="relatedTypeName"
75+
onChange={onChange}
7476
aria-label="Related type name"
7577
/>
7678
);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { ChangeEvent } from "react";
2+
import { useCallback, useEffect, useState } from "react";
3+
import type {
4+
ModeledMethod,
5+
TypeModeledMethod,
6+
} from "../../model-editor/modeled-method";
7+
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react";
8+
import { useDebounceCallback } from "../common/useDebounceCallback";
9+
10+
type Props = {
11+
modeledMethod: TypeModeledMethod;
12+
typeInfo: "path" | "relatedTypeName";
13+
onChange: (modeledMethod: ModeledMethod) => void;
14+
15+
"aria-label"?: string;
16+
};
17+
18+
export const ModelTypeTextbox = ({
19+
modeledMethod,
20+
typeInfo,
21+
onChange,
22+
...props
23+
}: Props): JSX.Element => {
24+
const [value, setValue] = useState<string | undefined>(
25+
modeledMethod[typeInfo],
26+
);
27+
28+
useEffect(() => {
29+
setValue(modeledMethod[typeInfo]);
30+
}, [modeledMethod, typeInfo]);
31+
32+
const handleChange = useCallback((e: ChangeEvent<HTMLSelectElement>) => {
33+
const target = e.target as HTMLSelectElement;
34+
35+
setValue(target.value);
36+
}, []);
37+
38+
// Debounce the callback to avoid updating the model too often.
39+
// Not doing this results in a lot of lag when typing.
40+
useDebounceCallback(
41+
value,
42+
(newValue: string | undefined) => {
43+
onChange({
44+
...modeledMethod,
45+
[typeInfo]: newValue ?? "",
46+
});
47+
},
48+
500,
49+
);
50+
51+
return <VSCodeTextField value={value} onInput={handleChange} {...props} />;
52+
};

0 commit comments

Comments
 (0)