[red-knot] Simplify playground editor state (#17223)

## Summary
Reduce the number of `useRef`s in `Editor`
This commit is contained in:
Micha Reiser 2025-04-05 19:49:08 +02:00 committed by GitHub
parent 1c652e6b98
commit 4571c5f56f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 112 additions and 102 deletions

View File

@ -14,7 +14,7 @@ import {
Position,
Uri,
} from "monaco-editor";
import { RefObject, useCallback, useEffect, useRef } from "react";
import { useCallback, useEffect, useRef } from "react";
import { Theme } from "shared";
import {
Severity,
@ -53,34 +53,25 @@ export default function Editor({
onMount,
onFileOpened,
}: Props) {
const disposable = useRef<{
typeDefinition: IDisposable;
editorOpener: IDisposable;
hover: IDisposable;
} | null>(null);
const playgroundState = useRef<PlaygroundServerProps>({
monaco: null,
files,
workspace,
onFileOpened,
});
const serverRef = useRef<PlaygroundServer | null>(null);
playgroundState.current = {
monaco: playgroundState.current.monaco,
files,
workspace,
onFileOpened,
};
if (serverRef.current != null) {
serverRef.current.update({
files,
workspace,
onFileOpened,
});
}
// Update the diagnostics in the editor.
useEffect(() => {
const monaco = playgroundState.current.monaco;
const server = serverRef.current;
if (monaco == null) {
if (server == null) {
return;
}
updateMarkers(monaco, diagnostics);
server.updateDiagnostics(diagnostics);
}, [diagnostics]);
const handleChange = useCallback(
@ -92,38 +83,29 @@ export default function Editor({
useEffect(() => {
return () => {
disposable.current?.typeDefinition.dispose();
disposable.current?.editorOpener.dispose();
disposable.current?.hover.dispose();
const server = serverRef.current;
if (server != null) {
server.dispose();
}
};
}, []);
const handleMount: OnMount = useCallback(
(editor, instance) => {
updateMarkers(instance, diagnostics);
const server = new PlaygroundServer(instance, {
workspace,
files,
onFileOpened,
});
const server = new PlaygroundServer(playgroundState);
const typeDefinitionDisposable =
instance.languages.registerTypeDefinitionProvider("python", server);
const hoverDisposable = instance.languages.registerHoverProvider(
"python",
server,
);
const editorOpenerDisposable =
instance.editor.registerEditorOpener(server);
disposable.current = {
typeDefinition: typeDefinitionDisposable,
editorOpener: editorOpenerDisposable,
hover: hoverDisposable,
};
playgroundState.current.monaco = instance;
server.updateDiagnostics(diagnostics);
serverRef.current = server;
onMount(editor, instance);
},
[onMount, diagnostics],
[files, onFileOpened, workspace, onMount, diagnostics],
);
return (
@ -148,52 +130,9 @@ export default function Editor({
);
}
function updateMarkers(monaco: Monaco, diagnostics: Array<Diagnostic>) {
const editor = monaco.editor;
const model = editor?.getModels()[0];
if (!model) {
return;
}
editor.setModelMarkers(
model,
"owner",
diagnostics.map((diagnostic) => {
const mapSeverity = (severity: Severity) => {
switch (severity) {
case Severity.Info:
return MarkerSeverity.Info;
case Severity.Warning:
return MarkerSeverity.Warning;
case Severity.Error:
return MarkerSeverity.Error;
case Severity.Fatal:
return MarkerSeverity.Error;
}
};
const range = diagnostic.range;
return {
code: diagnostic.id,
startLineNumber: range?.start?.line ?? 0,
startColumn: range?.start?.column ?? 0,
endLineNumber: range?.end?.line ?? 0,
endColumn: range?.end?.column ?? 0,
message: diagnostic.message,
severity: mapSeverity(diagnostic.severity),
tags: [],
};
}),
);
}
interface PlaygroundServerProps {
monaco: Monaco | null;
workspace: Workspace;
files: ReadonlyFiles;
onFileOpened: (file: FileId) => void;
}
@ -203,7 +142,77 @@ class PlaygroundServer
editor.ICodeEditorOpener,
languages.HoverProvider
{
constructor(private props: RefObject<PlaygroundServerProps>) {}
private typeDefinitionProviderDisposable: IDisposable;
private editorOpenerDisposable: IDisposable;
private hoverDisposable: IDisposable;
constructor(
private monaco: Monaco,
private props: PlaygroundServerProps,
) {
this.typeDefinitionProviderDisposable =
monaco.languages.registerTypeDefinitionProvider("python", this);
this.hoverDisposable = monaco.languages.registerHoverProvider(
"python",
this,
);
this.editorOpenerDisposable = monaco.editor.registerEditorOpener(this);
}
update(props: PlaygroundServerProps) {
this.props = props;
}
updateDiagnostics(diagnostics: Array<Diagnostic>) {
if (this.props.files.selected == null) {
return;
}
const handle = this.props.files.handles[this.props.files.selected];
if (handle == null) {
return;
}
const editor = this.monaco.editor;
const model = editor.getModel(Uri.parse(handle.path()));
if (model == null) {
return;
}
editor.setModelMarkers(
model,
"owner",
diagnostics.map((diagnostic) => {
const mapSeverity = (severity: Severity) => {
switch (severity) {
case Severity.Info:
return MarkerSeverity.Info;
case Severity.Warning:
return MarkerSeverity.Warning;
case Severity.Error:
return MarkerSeverity.Error;
case Severity.Fatal:
return MarkerSeverity.Error;
}
};
const range = diagnostic.range;
return {
code: diagnostic.id,
startLineNumber: range?.start?.line ?? 0,
startColumn: range?.start?.column ?? 0,
endLineNumber: range?.end?.line ?? 0,
endColumn: range?.end?.column ?? 0,
message: diagnostic.message,
severity: mapSeverity(diagnostic.severity),
tags: [],
};
}),
);
}
provideHover(
model: editor.ITextModel,
@ -213,14 +222,14 @@ class PlaygroundServer
// eslint-disable-next-line @typescript-eslint/no-unused-vars
context?: languages.HoverContext<languages.Hover> | undefined,
): languages.ProviderResult<languages.Hover> {
const workspace = this.props.current.workspace;
const workspace = this.props.workspace;
const selectedFile = this.props.current.files.selected;
const selectedFile = this.props.files.selected;
if (selectedFile == null) {
return;
}
const selectedHandle = this.props.current.files.handles[selectedFile];
const selectedHandle = this.props.files.handles[selectedFile];
if (selectedHandle == null) {
return;
@ -247,14 +256,14 @@ class PlaygroundServer
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_: CancellationToken,
): languages.ProviderResult<languages.Definition | languages.LocationLink[]> {
const workspace = this.props.current.workspace;
const workspace = this.props.workspace;
const selectedFile = this.props.current.files.selected;
const selectedFile = this.props.files.selected;
if (selectedFile == null) {
return;
}
const selectedHandle = this.props.current.files.handles[selectedFile];
const selectedHandle = this.props.files.handles[selectedFile];
if (selectedHandle == null) {
return;
@ -295,12 +304,7 @@ class PlaygroundServer
resource: Uri,
selectionOrPosition?: IRange | IPosition,
): boolean {
const files = this.props.current.files;
const monaco = this.props.current.monaco;
if (monaco == null) {
return false;
}
const files = this.props.files;
const fileId = files.index.find((file) => {
return Uri.file(file.name).toString() === resource.toString();
@ -312,11 +316,11 @@ class PlaygroundServer
const handle = files.handles[fileId];
let model = monaco.editor.getModel(resource);
let model = this.monaco.editor.getModel(resource);
if (model == null) {
const language =
handle != null && isPythonFile(handle) ? "python" : undefined;
model = monaco.editor.createModel(
model = this.monaco.editor.createModel(
files.contents[fileId],
language,
resource,
@ -329,7 +333,7 @@ class PlaygroundServer
if (files.selected !== fileId) {
source.setModel(model);
this.props.current.onFileOpened(fileId);
this.props.onFileOpened(fileId);
}
if (selectionOrPosition != null) {
@ -347,6 +351,12 @@ class PlaygroundServer
return true;
}
dispose() {
this.hoverDisposable.dispose();
this.editorOpenerDisposable.dispose();
this.typeDefinitionProviderDisposable.dispose();
}
}
function knotRangeToIRange(range: KnotRange): IRange {