Skip to content

Commit

Permalink
Playground: Fix errors not shown on page load (#13262)
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser authored Sep 9, 2024
1 parent e1603e3 commit 955dc88
Showing 1 changed file with 127 additions and 72 deletions.
199 changes: 127 additions & 72 deletions playground/src/Editor/SourceEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@
* Editor for the Python source code.
*/

import Editor, { BeforeMount, Monaco } from "@monaco-editor/react";
import { MarkerSeverity, MarkerTag } from "monaco-editor";
import Editor, { Monaco, OnMount } from "@monaco-editor/react";
import {
editor,
IDisposable,
languages,
MarkerSeverity,
MarkerTag,
Range,
} from "monaco-editor";
import { useCallback, useEffect, useRef } from "react";
import { Diagnostic } from "../pkg";
import { Theme } from "./theme";
import CodeActionProvider = languages.CodeActionProvider;

type MonacoEditorState = {
monaco: Monaco;
codeActionProvider: RuffCodeActionProvider;
disposeCodeActionProvider: IDisposable;
};

export default function SourceEditor({
visible,
Expand All @@ -21,80 +35,32 @@ export default function SourceEditor({
theme: Theme;
onChange: (pythonSource: string) => void;
}) {
const monacoRef = useRef<Monaco | null>(null);
const monaco = monacoRef.current;
const monacoRef = useRef<MonacoEditorState | null>(null);

// Update the diagnostics in the editor.
useEffect(() => {
const editor = monaco?.editor;
const model = editor?.getModels()[0];
if (!editor || !model) {
const editorState = monacoRef.current;

if (editorState == null) {
return;
}

editor.setModelMarkers(
model,
"owner",
diagnostics.map((diagnostic) => ({
startLineNumber: diagnostic.location.row,
startColumn: diagnostic.location.column,
endLineNumber: diagnostic.end_location.row,
endColumn: diagnostic.end_location.column,
message: diagnostic.code
? `${diagnostic.code}: ${diagnostic.message}`
: diagnostic.message,
severity: MarkerSeverity.Error,
tags:
diagnostic.code === "F401" || diagnostic.code === "F841"
? [MarkerTag.Unnecessary]
: [],
})),
);

const codeActionProvider = monaco?.languages.registerCodeActionProvider(
"python",
{
provideCodeActions: function (model, position) {
const actions = diagnostics
.filter((check) => position.startLineNumber === check.location.row)
.filter(({ fix }) => fix)
.map((check) => ({
title: check.fix
? check.fix.message
? `${check.code}: ${check.fix.message}`
: `Fix ${check.code}`
: "Fix",
id: `fix-${check.code}`,
kind: "quickfix",
edit: check.fix
? {
edits: check.fix.edits.map((edit) => ({
resource: model.uri,
versionId: model.getVersionId(),
textEdit: {
range: {
startLineNumber: edit.location.row,
startColumn: edit.location.column,
endLineNumber: edit.end_location.row,
endColumn: edit.end_location.column,
},
text: edit.content || "",
},
})),
}
: undefined,
}));
return {
actions,
dispose: () => {},
};
},
},
);
editorState.codeActionProvider.diagnostics = diagnostics;

updateMarkers(editorState.monaco, diagnostics);
}, [diagnostics]);

// Dispose the code action provider on unmount.
useEffect(() => {
const disposeActionProvider = monacoRef.current?.disposeCodeActionProvider;
if (disposeActionProvider == null) {
return;
}

return () => {
codeActionProvider?.dispose();
disposeActionProvider.dispose();
};
}, [diagnostics, monaco]);
}, []);

const handleChange = useCallback(
(value: string | undefined) => {
Expand All @@ -103,14 +69,30 @@ export default function SourceEditor({
[onChange],
);

const handleMount: BeforeMount = useCallback(
(instance) => (monacoRef.current = instance),
[],
const handleMount: OnMount = useCallback(
(_editor, instance) => {
const ruffActionsProvider = new RuffCodeActionProvider(diagnostics);
const disposeCodeActionProvider =
instance.languages.registerCodeActionProvider(
"python",
ruffActionsProvider,
);

updateMarkers(instance, diagnostics);

monacoRef.current = {
monaco: instance,
codeActionProvider: ruffActionsProvider,
disposeCodeActionProvider,
};
},

[diagnostics],
);

return (
<Editor
beforeMount={handleMount}
onMount={handleMount}
options={{
fixedOverflowWidgets: true,
readOnly: false,
Expand All @@ -128,3 +110,76 @@ export default function SourceEditor({
/>
);
}

class RuffCodeActionProvider implements CodeActionProvider {
constructor(public diagnostics: Array<Diagnostic>) {}

provideCodeActions(
model: editor.ITextModel,
range: Range,
): languages.ProviderResult<languages.CodeActionList> {
const actions = this.diagnostics
.filter((check) => range.startLineNumber === check.location.row)
.filter(({ fix }) => fix)
.map((check) => ({
title: check.fix
? check.fix.message
? `${check.code}: ${check.fix.message}`
: `Fix ${check.code}`
: "Fix",
id: `fix-${check.code}`,
kind: "quickfix",

edit: check.fix
? {
edits: check.fix.edits.map((edit) => ({
resource: model.uri,
versionId: model.getVersionId(),
textEdit: {
range: {
startLineNumber: edit.location.row,
startColumn: edit.location.column,
endLineNumber: edit.end_location.row,
endColumn: edit.end_location.column,
},
text: edit.content || "",
},
})),
}
: undefined,
}));

return {
actions,
dispose: () => {},
};
}
}

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) => ({
startLineNumber: diagnostic.location.row,
startColumn: diagnostic.location.column,
endLineNumber: diagnostic.end_location.row,
endColumn: diagnostic.end_location.column,
message: diagnostic.code
? `${diagnostic.code}: ${diagnostic.message}`
: diagnostic.message,
severity: MarkerSeverity.Error,
tags:
diagnostic.code === "F401" || diagnostic.code === "F841"
? [MarkerTag.Unnecessary]
: [],
})),
);
}

0 comments on commit 955dc88

Please sign in to comment.