[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, Position,
Uri, Uri,
} from "monaco-editor"; } from "monaco-editor";
import { RefObject, useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import { Theme } from "shared"; import { Theme } from "shared";
import { import {
Severity, Severity,
@ -53,34 +53,25 @@ export default function Editor({
onMount, onMount,
onFileOpened, onFileOpened,
}: Props) { }: Props) {
const disposable = useRef<{ const serverRef = useRef<PlaygroundServer | null>(null);
typeDefinition: IDisposable;
editorOpener: IDisposable;
hover: IDisposable;
} | null>(null);
const playgroundState = useRef<PlaygroundServerProps>({
monaco: null,
files,
workspace,
onFileOpened,
});
playgroundState.current = { if (serverRef.current != null) {
monaco: playgroundState.current.monaco, serverRef.current.update({
files, files,
workspace, workspace,
onFileOpened, onFileOpened,
}; });
}
// Update the diagnostics in the editor. // Update the diagnostics in the editor.
useEffect(() => { useEffect(() => {
const monaco = playgroundState.current.monaco; const server = serverRef.current;
if (monaco == null) { if (server == null) {
return; return;
} }
updateMarkers(monaco, diagnostics); server.updateDiagnostics(diagnostics);
}, [diagnostics]); }, [diagnostics]);
const handleChange = useCallback( const handleChange = useCallback(
@ -92,38 +83,29 @@ export default function Editor({
useEffect(() => { useEffect(() => {
return () => { return () => {
disposable.current?.typeDefinition.dispose(); const server = serverRef.current;
disposable.current?.editorOpener.dispose();
disposable.current?.hover.dispose(); if (server != null) {
server.dispose();
}
}; };
}, []); }, []);
const handleMount: OnMount = useCallback( const handleMount: OnMount = useCallback(
(editor, instance) => { (editor, instance) => {
updateMarkers(instance, diagnostics); const server = new PlaygroundServer(instance, {
workspace,
files,
onFileOpened,
});
const server = new PlaygroundServer(playgroundState); server.updateDiagnostics(diagnostics);
const typeDefinitionDisposable = serverRef.current = server;
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;
onMount(editor, instance); onMount(editor, instance);
}, },
[onMount, diagnostics], [files, onFileOpened, workspace, onMount, diagnostics],
); );
return ( 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 { interface PlaygroundServerProps {
monaco: Monaco | null;
workspace: Workspace; workspace: Workspace;
files: ReadonlyFiles; files: ReadonlyFiles;
onFileOpened: (file: FileId) => void; onFileOpened: (file: FileId) => void;
} }
@ -203,7 +142,77 @@ class PlaygroundServer
editor.ICodeEditorOpener, editor.ICodeEditorOpener,
languages.HoverProvider 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( provideHover(
model: editor.ITextModel, model: editor.ITextModel,
@ -213,14 +222,14 @@ class PlaygroundServer
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
context?: languages.HoverContext<languages.Hover> | undefined, context?: languages.HoverContext<languages.Hover> | undefined,
): languages.ProviderResult<languages.Hover> { ): 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) { if (selectedFile == null) {
return; return;
} }
const selectedHandle = this.props.current.files.handles[selectedFile]; const selectedHandle = this.props.files.handles[selectedFile];
if (selectedHandle == null) { if (selectedHandle == null) {
return; return;
@ -247,14 +256,14 @@ class PlaygroundServer
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
_: CancellationToken, _: CancellationToken,
): languages.ProviderResult<languages.Definition | languages.LocationLink[]> { ): 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) { if (selectedFile == null) {
return; return;
} }
const selectedHandle = this.props.current.files.handles[selectedFile]; const selectedHandle = this.props.files.handles[selectedFile];
if (selectedHandle == null) { if (selectedHandle == null) {
return; return;
@ -295,12 +304,7 @@ class PlaygroundServer
resource: Uri, resource: Uri,
selectionOrPosition?: IRange | IPosition, selectionOrPosition?: IRange | IPosition,
): boolean { ): boolean {
const files = this.props.current.files; const files = this.props.files;
const monaco = this.props.current.monaco;
if (monaco == null) {
return false;
}
const fileId = files.index.find((file) => { const fileId = files.index.find((file) => {
return Uri.file(file.name).toString() === resource.toString(); return Uri.file(file.name).toString() === resource.toString();
@ -312,11 +316,11 @@ class PlaygroundServer
const handle = files.handles[fileId]; const handle = files.handles[fileId];
let model = monaco.editor.getModel(resource); let model = this.monaco.editor.getModel(resource);
if (model == null) { if (model == null) {
const language = const language =
handle != null && isPythonFile(handle) ? "python" : undefined; handle != null && isPythonFile(handle) ? "python" : undefined;
model = monaco.editor.createModel( model = this.monaco.editor.createModel(
files.contents[fileId], files.contents[fileId],
language, language,
resource, resource,
@ -329,7 +333,7 @@ class PlaygroundServer
if (files.selected !== fileId) { if (files.selected !== fileId) {
source.setModel(model); source.setModel(model);
this.props.current.onFileOpened(fileId); this.props.onFileOpened(fileId);
} }
if (selectionOrPosition != null) { if (selectionOrPosition != null) {
@ -347,6 +351,12 @@ class PlaygroundServer
return true; return true;
} }
dispose() {
this.hoverDisposable.dispose();
this.editorOpenerDisposable.dispose();
this.typeDefinitionProviderDisposable.dispose();
}
} }
function knotRangeToIRange(range: KnotRange): IRange { function knotRangeToIRange(range: KnotRange): IRange {