Skip to content

Commit

Permalink
Add secondary tool
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Aug 13, 2024
1 parent 2663591 commit cc21c0c
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 152 deletions.
176 changes: 124 additions & 52 deletions playground/src/red_knot/Chrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,28 @@ import {
} from "react";
import Header from "../shared/Header";
import { useTheme } from "../shared/theme";
import { default as Editor } from "./Editor";
import initRedKnot, {
Workspace,
FileHandle,
Settings,
TargetVersion,
FileHandle,
Workspace,
} from "./red_knot_wasm";
import { loader } from "@monaco-editor/react";
import { setupMonaco } from "../shared/setupMonaco";
import { Panel, PanelGroup } from "react-resizable-panels";
import { Files } from "./Files";
import { persist, persistLocal, restore } from "./persist";
import { ErrorMessage } from "../shared/ErrorMessage";
import SecondarySideBar from "./SecondarySideBar";
import { SecondaryTool } from "./SecondaryPanel";
import Editor from "./Editor";
import { HorizontalResizeHandle } from "../shared/ResizeHandle";
import SecondaryPanel, { SecondaryPanelResult } from "../ruff/SecondaryPanel";

interface CheckResult {
diagnostics: string[];
error: string | null;
secondary: SecondaryPanelResult;
}

export default function Chrome() {
Expand All @@ -39,6 +44,9 @@ export default function Chrome() {
revision: 0,
selected: null,
});
const [secondaryTool, setSecondaryTool] = useState<SecondaryTool | null>(
null,
);

const [version, setVersion] = useState("");
const [theme, setTheme] = useTheme();
Expand Down Expand Up @@ -151,15 +159,20 @@ export default function Chrome() {
[workspace, files.handles, files.contents],
);

const fileName = useMemo(() => {
if (files.selected == null) {
return "";
}
const handleSecondaryToolSelected = useCallback(
(tool: SecondaryTool | null) => {
setSecondaryTool((secondaryTool) => {
if (tool === secondaryTool) {
return null;
}

return files.index.find(({ id }) => id === files.selected)?.name ?? "";
}, [files.selected, files.index]);
return tool;
});
},
[],
);

const checkResult = useCheckResult(files, workspace);
const checkResult = useCheckResult(files, workspace, secondaryTool);

return (
<main className="flex flex-col h-full bg-ayu-background dark:bg-ayu-background-dark">
Expand All @@ -171,51 +184,69 @@ export default function Chrome() {
onShare={handleShare}
/>

<div className="flex grow">
<PanelGroup direction="horizontal" autoSaveId="main">
{workspace != null && files.selected != null ? (
{workspace != null && files.selected != null ? (
<>
<Files
files={files.index}
theme={theme}
selected={files.selected}
onAdd={handleFileAdded}
onRename={handleFileRenamed}
onSelected={handleFileClicked}
onRemove={handleFileRemoved}
/>
<PanelGroup direction="horizontal" autoSaveId="main">
<Panel
id="main"
order={0}
className="flex flex-col gap-2"
minSize={10}
>
<Files
files={files.index}
<Editor
theme={theme}
selected={files.selected}
onAdd={handleFileAdded}
onRename={handleFileRenamed}
onSelected={handleFileClicked}
onRemove={handleFileRemoved}
visible={true}
source={files.contents[files.selected]}
onChange={handleSourceChanged}
diagnostics={checkResult.diagnostics}
/>

<div className="flex-grow">
<Editor
theme={theme}
content={files.contents[files.selected]}
onSourceChanged={handleSourceChanged}
fileDiagnostics={checkResult.diagnostics}
fileName={fileName}
/>
</div>
</Panel>
) : null}
</PanelGroup>

{checkResult.error ? (
<div
style={{
position: "fixed",
left: "10%",
right: "10%",
bottom: "10%",
}}
>
<ErrorMessage>{checkResult.error}</ErrorMessage>
</div>
) : null}
</div>
{secondaryTool != null && (
<>
<HorizontalResizeHandle />
<Panel
id="secondary-panel"
order={1}
className={"my-2"}
minSize={10}
>
<SecondaryPanel
theme={theme}
tool={secondaryTool}
result={checkResult.secondary}
/>
</Panel>
</>
)}
<SecondarySideBar
selected={secondaryTool}
onSelected={handleSecondaryToolSelected}
/>
</PanelGroup>
</>
) : null}

{checkResult.error ? (
<div
style={{
position: "fixed",
left: "10%",
right: "10%",
bottom: "10%",
}}
>
<ErrorMessage>{checkResult.error}</ErrorMessage>
</div>
) : null}
</main>
);
}
Expand Down Expand Up @@ -260,6 +291,7 @@ function usePersistLocally(files: FilesState): void {
function useCheckResult(
files: FilesState,
workspace: Workspace | null,
secondaryTool: SecondaryTool | null,
): CheckResult {
const deferredContent = useDeferredValue(
files.selected == null ? null : files.contents[files.selected],
Expand All @@ -274,6 +306,7 @@ function useCheckResult(
return {
diagnostics: [],
error: null,
secondary: null,
};
}

Expand All @@ -283,19 +316,53 @@ function useCheckResult(

try {
const diagnostics = workspace.checkFile(currentHandle);

let secondary: SecondaryPanelResult = null;

try {
switch (secondaryTool) {
case "AST":
secondary = {
status: "ok",
content: workspace.parsed(currentHandle),
};
break;

case "Tokens":
secondary = {
status: "ok",
content: workspace.tokens(currentHandle),
};
break;
}
} catch (error: unknown) {
secondary = {
status: "error",
error: error instanceof Error ? error.message : error + "",
};
}

return {
diagnostics,
error: null,
secondary,
};
} catch (e) {
console.error(e);

return {
diagnostics: [],
error: (e as Error).message,
secondary: null,
};
}
}, [deferredContent, workspace, files.selected, files.handles]);
}, [
deferredContent,
workspace,
files.selected,
files.handles,
secondaryTool,
]);
}

export type FileId = number;
Expand Down Expand Up @@ -444,15 +511,20 @@ function serializeFiles(files: FilesState): {
files: { [name: string]: string };
current: string;
} | null {
if (files.selected == null) {
return null;
}

const serializedFiles = Object.create(null);
let selected = null;

for (const { id, name } of files.index) {
serializedFiles[name] = files.contents[id];

if (files.selected === id) {
selected = name;
}
}

if (selected == null) {
return null;
}

return { files: serializedFiles, current: files.index[files.selected].name };
return { files: serializedFiles, current: selected };
}
84 changes: 67 additions & 17 deletions playground/src/red_knot/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,81 @@
/**
* Editor for the Python source code.
*/

import Moncao, { BeforeMount, Monaco } from "@monaco-editor/react";
import { MarkerSeverity } from "monaco-editor";
import { useCallback, useEffect, useRef } from "react";
import { Theme } from "../shared/theme";
import PythonEditor from "./PythonEditor";

type Props = {
fileName: string;
content: string;
visible: boolean;
source: string;
diagnostics: string[];
theme: Theme;
fileDiagnostics: string[];

onSourceChanged(source: string): void;
onChange(content: string): void;
};

export default function Editor({
content,
fileName,
fileDiagnostics,
visible,
source,
theme,
onSourceChanged,
diagnostics,
onChange,
}: Props) {
const fileType = fileName.endsWith(".toml") ? "toml" : "py";
const monacoRef = useRef<Monaco | null>(null);
const monaco = monacoRef.current;

useEffect(() => {
const editor = monaco?.editor;
const model = editor?.getModels()[0];
if (!editor || !model) {
return;
}

editor.setModelMarkers(
model,
"owner",
diagnostics.map((diagnostic) => ({
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 1,
message: diagnostic,
severity: MarkerSeverity.Error,
tags: [],
})),
);
}, [diagnostics, monaco]);

const handleChange = useCallback(
(value: string | undefined) => {
onChange(value ?? "");
},
[onChange],
);

const handleMount: BeforeMount = useCallback(
(instance) => (monacoRef.current = instance),
[],
);

return (
<PythonEditor
visible={fileType === "py"}
source={fileType === "py" ? content : ""}
theme={theme}
diagnostics={fileDiagnostics}
onChange={onSourceChanged}
<Moncao
beforeMount={handleMount}
options={{
fixedOverflowWidgets: true,
readOnly: false,
minimap: { enabled: false },
fontSize: 14,
roundedSelection: false,
scrollBeyondLastLine: false,
contextmenu: false,
}}
language={"python"}
wrapperProps={visible ? {} : { style: { display: "none" } }}
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
value={source}
onChange={handleChange}
/>
);
}
2 changes: 1 addition & 1 deletion playground/src/red_knot/Files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface Props {
// The file names
files: ReadonlyArray<{ id: FileId; name: string }>;
theme: Theme;
selected: FileId | null;
selected: FileId;

onAdd(name: string): void;
onRemove(id: FileId): void;
Expand Down
Loading

0 comments on commit cc21c0c

Please sign in to comment.