Red Knot Playground (#12681)

## Summary

This PR adds a playground for Red Knot

[Screencast from 2024-08-14
10-33-54.webm](https://github.com/user-attachments/assets/ae81d85f-74a3-4ba6-bb61-4a871b622f05)

Sharing does work 😆 I just forgot to start wrangler. 


It supports:

* Multiple files
* Showing the AST
* Showing the tokens
* Sharing
* Persistence to local storage

Future extensions:

* Configuration support: The `pyproject.toml` would *just* be another
file.
* Showing type information on hover

## Blockers

~~Salsa uses `catch_unwind` to break cycles, which Red Knot uses
extensively when inferring types in the standard library.
However, WASM (at least `wasm32-unknown-unknown`) doesn't support
`catch_unwind` today, so the playground always crashes when the type
inference encounters a cycle.~~

~~I created a discussion in the [salsa
zulip](https://salsa.zulipchat.com/#narrow/stream/333573-salsa-3.2E0/topic/WASM.20support)
to see if it would be possible to **not** use catch unwind to break
cycles.~~

~~[Rust tracking issue for WASM catch unwind
support](https://github.com/rust-lang/rust/issues/118168)~~

~~I tried to build the WASM with the nightly compiler option but ran
into problems because wasm-bindgen doesn't support WASM-exceptions. We
could try to write the binding code by hand.~~

~~Another alternative is to use `wasm32-unknown-emscripten` but it's
rather painful to build~~
This commit is contained in:
Micha Reiser 2025-03-18 17:17:11 +01:00 committed by GitHub
parent dcf31c9348
commit c027979851
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 2347 additions and 491 deletions

View file

@ -0,0 +1,72 @@
import { type ReactNode } from "react";
import classNames from "classnames";
type SideBarProps = {
children: ReactNode;
position: "right" | "left";
};
export default function SideBar({ children, position }: SideBarProps) {
return (
<ul
className={classNames(
"w-12 flex-initial flex flex-col items-stretch bg-galaxy border-gray-200",
position === "left" ? "border-r" : "border-l",
)}
>
{children}
</ul>
);
}
export interface SideBarEntryProps {
title: string;
selected: boolean;
children: ReactNode;
position: "left" | "right";
onClick?(): void;
}
export function SideBarEntry({
title,
onClick,
children,
selected,
position,
}: SideBarEntryProps) {
return (
<li
aria-label={title}
onClick={onClick}
role="button"
className={`group py-4 px-2 relative flex items-center justify-center flex-col fill-white text-white ${
selected ? "opacity-100" : "opacity-50 hover:opacity-100"
}`}
>
{children}
{selected && (
<span className="absolute start-0 inset-y-0 bg-white w-0.5"></span>
)}
<Tooltip position={position}>{title}</Tooltip>
</li>
);
}
interface TooltipProps {
children: ReactNode;
position: "left" | "right";
}
function Tooltip({ children, position }: TooltipProps) {
return (
<span
className={`z-10 absolute rounded dark:border-[1px] dark:border-white bg-space dark:bg-white px-2 py-1 hidden text-xs text-white dark:text-black group-hover:flex whitespace-nowrap ${
position === "right" ? "right-[52px]" : "left-[52px]"
}`}
>
{children}
</span>
);
}