mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 14:52:01 +00:00
Re-style the Ruff playground (#1438)
This commit is contained in:
parent
057414ddd4
commit
acf0b82f19
28 changed files with 2218 additions and 398 deletions
|
@ -2725,7 +2725,7 @@ Whether to use Google-style or Numpy-style conventions when detecting
|
|||
docstring sections. By default, conventions will be inferred from
|
||||
the available sections.
|
||||
|
||||
**Default value**: `"convention"`
|
||||
**Default value**: `None`
|
||||
|
||||
**Type**: `Convention`
|
||||
|
||||
|
|
|
@ -8,3 +8,7 @@ In-browser playground for Ruff. Available [https://ruff.pages.dev/](https://ruff
|
|||
root directory.
|
||||
- Install TypeScript dependencies with: `npm install`.
|
||||
- Start the development server with: `npm run dev`.
|
||||
|
||||
## Implementation
|
||||
|
||||
Design based on [Tailwind Play](https://play.tailwindcss.com/). Themed with [`ayu`](https://github.com/dempfi/ayu).
|
||||
|
|
|
@ -13,17 +13,10 @@
|
|||
rel="icon"
|
||||
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛠️</text></svg>"
|
||||
/>
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div style="display: flex; position: fixed; right: 16px; top: 16px">
|
||||
<a href="https://GitHub.com/charliermarsh/ruff"
|
||||
><img
|
||||
src="https://img.shields.io/github/stars/charliermarsh/ruff.svg?style=social&label=GitHub&maxAge=2592000&?logoWidth=100"
|
||||
alt="GitHub stars"
|
||||
style="width: 120px"
|
||||
/></a>
|
||||
</div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
915
playground/package-lock.json
generated
915
playground/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"classnames": "^2.3.2",
|
||||
"lz-string": "^1.4.4",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"react": "^18.2.0",
|
||||
|
@ -25,13 +26,16 @@
|
|||
"@typescript-eslint/eslint-plugin": "^5.47.1",
|
||||
"@typescript-eslint/parser": "^5.47.1",
|
||||
"@vitejs/plugin-react-swc": "^3.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.30.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"postcss": "^8.4.20",
|
||||
"prettier": "^2.8.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
|
|
6
playground/postcss.config.cjs
Normal file
6
playground/postcss.config.cjs
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
|
@ -1,200 +0,0 @@
|
|||
import lzstring from "lz-string";
|
||||
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||
import { MarkerSeverity } from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
|
||||
import init, { Check, check } from "./pkg/ruff.js";
|
||||
import { AVAILABLE_OPTIONS } from "./ruff_options";
|
||||
import { Config, getDefaultConfig, toRuffConfig } from "./config";
|
||||
import { Options } from "./Options";
|
||||
|
||||
const DEFAULT_SOURCE =
|
||||
"# Define a function that takes an integer n and returns the nth number in the Fibonacci\n" +
|
||||
"# sequence.\n" +
|
||||
"def fibonacci(n):\n" +
|
||||
" if n == 0:\n" +
|
||||
" return 0\n" +
|
||||
" elif n == 1:\n" +
|
||||
" return 1\n" +
|
||||
" else:\n" +
|
||||
" return fibonacci(n-1) + fibonacci(n-2)\n" +
|
||||
"\n" +
|
||||
"# Use a for loop to generate and print the first 10 numbers in the Fibonacci sequence.\n" +
|
||||
"for i in range(10):\n" +
|
||||
" print(fibonacci(i))\n" +
|
||||
"\n" +
|
||||
"# Output:\n" +
|
||||
"# 0\n" +
|
||||
"# 1\n" +
|
||||
"# 1\n" +
|
||||
"# 2\n" +
|
||||
"# 3\n" +
|
||||
"# 5\n" +
|
||||
"# 8\n" +
|
||||
"# 13\n" +
|
||||
"# 21\n" +
|
||||
"# 34\n";
|
||||
|
||||
function restoreConfigAndSource(): [Config, string] {
|
||||
const value = lzstring.decompressFromEncodedURIComponent(
|
||||
window.location.hash.slice(1)
|
||||
);
|
||||
let config = {};
|
||||
let source = DEFAULT_SOURCE;
|
||||
|
||||
if (value) {
|
||||
const parts = value.split("$$$");
|
||||
config = JSON.parse(parts[0]);
|
||||
source = parts[1];
|
||||
}
|
||||
|
||||
return [config, source];
|
||||
}
|
||||
|
||||
function persistConfigAndSource(config: Config, source: string) {
|
||||
window.location.hash = lzstring.compressToEncodedURIComponent(
|
||||
JSON.stringify(config) + "$$$" + source
|
||||
);
|
||||
}
|
||||
|
||||
const defaultConfig = getDefaultConfig(AVAILABLE_OPTIONS);
|
||||
|
||||
export default function App() {
|
||||
const monaco = useMonaco();
|
||||
const [initialized, setInitialized] = useState<boolean>(false);
|
||||
const [config, setConfig] = useState<Config | null>(null);
|
||||
const [source, setSource] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
init().then(() => setInitialized(true));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (source == null && config == null && monaco) {
|
||||
const [config, source] = restoreConfigAndSource();
|
||||
setConfig(config);
|
||||
setSource(source);
|
||||
}
|
||||
}, [monaco, source, config]);
|
||||
|
||||
useEffect(() => {
|
||||
if (config != null && source != null) {
|
||||
persistConfigAndSource(config, source);
|
||||
}
|
||||
}, [config, source]);
|
||||
|
||||
useEffect(() => {
|
||||
const editor = monaco?.editor;
|
||||
const model = editor?.getModels()[0];
|
||||
if (!editor || !model || !initialized || source == null || config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let checks: Check[];
|
||||
try {
|
||||
checks = check(source, toRuffConfig(config));
|
||||
setError(null);
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
return;
|
||||
}
|
||||
|
||||
editor.setModelMarkers(
|
||||
model,
|
||||
"owner",
|
||||
checks.map((check) => ({
|
||||
startLineNumber: check.location.row,
|
||||
startColumn: check.location.column + 1,
|
||||
endLineNumber: check.end_location.row,
|
||||
endColumn: check.end_location.column + 1,
|
||||
message: `${check.code}: ${check.message}`,
|
||||
severity: MarkerSeverity.Error,
|
||||
}))
|
||||
);
|
||||
|
||||
const codeActionProvider = monaco?.languages.registerCodeActionProvider(
|
||||
"python",
|
||||
{
|
||||
// @ts-expect-error: The type definition is wrong.
|
||||
provideCodeActions: function (model, position) {
|
||||
const actions = checks
|
||||
.filter((check) => position.startLineNumber === check.location.row)
|
||||
.filter((check) => check.fix)
|
||||
.map((check) => ({
|
||||
title: `Fix ${check.code}`,
|
||||
id: `fix-${check.code}`,
|
||||
kind: "quickfix",
|
||||
edit: check.fix
|
||||
? {
|
||||
edits: [
|
||||
{
|
||||
resource: model.uri,
|
||||
versionId: model.getVersionId(),
|
||||
edit: {
|
||||
range: {
|
||||
startLineNumber: check.fix.location.row,
|
||||
startColumn: check.fix.location.column + 1,
|
||||
endLineNumber: check.fix.end_location.row,
|
||||
endColumn: check.fix.end_location.column + 1,
|
||||
},
|
||||
text: check.fix.content,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
}));
|
||||
return { actions, dispose: () => {} };
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
codeActionProvider?.dispose();
|
||||
};
|
||||
}, [config, source, monaco, initialized]);
|
||||
|
||||
const handleEditorChange = useCallback(
|
||||
(value: string | undefined) => {
|
||||
setSource(value || "");
|
||||
},
|
||||
[setSource]
|
||||
);
|
||||
|
||||
const handleOptionChange = useCallback(
|
||||
(groupName: string, fieldName: string, value: string) => {
|
||||
const group = Object.assign({}, (config || {})[groupName]);
|
||||
if (value === defaultConfig[groupName][fieldName] || value === "") {
|
||||
delete group[fieldName];
|
||||
} else {
|
||||
group[fieldName] = value;
|
||||
}
|
||||
|
||||
setConfig({
|
||||
...config,
|
||||
[groupName]: group,
|
||||
});
|
||||
},
|
||||
[config]
|
||||
);
|
||||
|
||||
return (
|
||||
<div id="app">
|
||||
<Options
|
||||
config={config}
|
||||
defaultConfig={defaultConfig}
|
||||
onChange={handleOptionChange}
|
||||
/>
|
||||
<Editor
|
||||
options={{ readOnly: false, minimap: { enabled: false } }}
|
||||
wrapperProps={{ className: "editor" }}
|
||||
defaultLanguage="python"
|
||||
value={source || ""}
|
||||
theme={"light"}
|
||||
onChange={handleEditorChange}
|
||||
/>
|
||||
{error && <div id="error">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
137
playground/src/Editor/Editor.tsx
Normal file
137
playground/src/Editor/Editor.tsx
Normal file
|
@ -0,0 +1,137 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { persist, restore } from "./config";
|
||||
import { DEFAULT_CONFIG_SOURCE, DEFAULT_PYTHON_SOURCE } from "../constants";
|
||||
import { ErrorMessage } from "./ErrorMessage";
|
||||
import Header from "./Header";
|
||||
import init, { check, current_version, Check } from "../pkg";
|
||||
import SettingsEditor from "./SettingsEditor";
|
||||
import SourceEditor from "./SourceEditor";
|
||||
import Themes from "./Themes";
|
||||
|
||||
type Tab = "Source" | "Settings";
|
||||
|
||||
export default function Editor() {
|
||||
const [initialized, setInitialized] = useState<boolean>(false);
|
||||
const [version, setVersion] = useState<string | null>(null);
|
||||
const [tab, setTab] = useState<Tab>("Source");
|
||||
const [edit, setEdit] = useState<number>(0);
|
||||
const [configSource, setConfigSource] = useState<string | null>(null);
|
||||
const [pythonSource, setPythonSource] = useState<string | null>(null);
|
||||
const [checks, setChecks] = useState<Check[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
init().then(() => setInitialized(true));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized || configSource == null || pythonSource == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let config: any;
|
||||
let checks: Check[];
|
||||
|
||||
try {
|
||||
config = JSON.parse(configSource);
|
||||
} catch (e) {
|
||||
setChecks([]);
|
||||
setError((e as Error).message);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
checks = check(pythonSource, config);
|
||||
} catch (e) {
|
||||
setError(e as string);
|
||||
return;
|
||||
}
|
||||
|
||||
setError(null);
|
||||
setChecks(checks);
|
||||
}, [initialized, configSource, pythonSource]);
|
||||
|
||||
useEffect(() => {
|
||||
if (configSource == null || pythonSource == null) {
|
||||
const payload = restore();
|
||||
if (payload) {
|
||||
const [configSource, pythonSource] = payload;
|
||||
setConfigSource(configSource);
|
||||
setPythonSource(pythonSource);
|
||||
} else {
|
||||
setConfigSource(DEFAULT_CONFIG_SOURCE);
|
||||
setPythonSource(DEFAULT_PYTHON_SOURCE);
|
||||
}
|
||||
}
|
||||
}, [configSource, pythonSource]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVersion(current_version());
|
||||
}, [initialized]);
|
||||
|
||||
const handleShare = useCallback(() => {
|
||||
if (!initialized || configSource == null || pythonSource == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
persist(configSource, pythonSource);
|
||||
}, [initialized, configSource, pythonSource]);
|
||||
|
||||
const handlePythonSourceChange = useCallback((pythonSource: string) => {
|
||||
setEdit((edit) => edit + 1);
|
||||
setPythonSource(pythonSource);
|
||||
}, []);
|
||||
|
||||
const handleConfigSourceChange = useCallback((configSource: string) => {
|
||||
setEdit((edit) => edit + 1);
|
||||
setConfigSource(configSource);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className={"h-full w-full flex flex-auto"}>
|
||||
<Header
|
||||
edit={edit}
|
||||
version={version}
|
||||
tab={tab}
|
||||
onChange={setTab}
|
||||
onShare={initialized ? handleShare : undefined}
|
||||
/>
|
||||
|
||||
<Themes />
|
||||
|
||||
<div className={"mt-12 relative flex-auto"}>
|
||||
{initialized && configSource != null && pythonSource != null ? (
|
||||
<>
|
||||
<SourceEditor
|
||||
visible={tab === "Source"}
|
||||
source={pythonSource}
|
||||
checks={checks}
|
||||
onChange={handlePythonSourceChange}
|
||||
/>
|
||||
<SettingsEditor
|
||||
visible={tab === "Settings"}
|
||||
source={configSource}
|
||||
onChange={handleConfigSourceChange}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
{error && tab === "Source" ? (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
left: "10%",
|
||||
right: "10%",
|
||||
bottom: "10%",
|
||||
}}
|
||||
>
|
||||
<ErrorMessage>{error}</ErrorMessage>
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
}
|
26
playground/src/Editor/ErrorMessage.tsx
Normal file
26
playground/src/Editor/ErrorMessage.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
function truncate(str: string, length: number) {
|
||||
if (str.length > length) {
|
||||
return str.slice(0, length) + "...";
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
export function ErrorMessage({ children }: { children: string }) {
|
||||
return (
|
||||
<div
|
||||
className="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4"
|
||||
role="alert"
|
||||
>
|
||||
<p className="font-bold">Error</p>
|
||||
<p className="block sm:inline">
|
||||
{truncate(
|
||||
children.startsWith("Error: ")
|
||||
? children.slice("Error: ".length)
|
||||
: children,
|
||||
120
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
73
playground/src/Editor/Header.tsx
Normal file
73
playground/src/Editor/Header.tsx
Normal file
|
@ -0,0 +1,73 @@
|
|||
import classNames from "classnames";
|
||||
import ShareButton from "./ShareButton";
|
||||
import VersionTag from "./VersionTag";
|
||||
|
||||
export type Tab = "Source" | "Settings";
|
||||
|
||||
export default function Header({
|
||||
edit,
|
||||
version,
|
||||
tab,
|
||||
onChange,
|
||||
onShare,
|
||||
}: {
|
||||
edit: number;
|
||||
version: string | null;
|
||||
tab: Tab;
|
||||
onChange: (tab: Tab) => void;
|
||||
onShare?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="w-full flex items-center justify-between flex-none pl-5 sm:pl-6 pr-4 lg:pr-6 absolute z-10 top-0 left-0 -mb-px antialiased border-b border-gray-200 dark:border-gray-800"
|
||||
style={{ background: "#f8f9fa" }}
|
||||
>
|
||||
<div className="flex space-x-5">
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
"relative flex py-3 text-sm leading-6 font-semibold focus:outline-none",
|
||||
tab === "Source"
|
||||
? "text-ayu"
|
||||
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white"
|
||||
)}
|
||||
onClick={() => onChange("Source")}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
"absolute bottom-0 inset-x-0 bg-ayu h-0.5 rounded-full transition-opacity duration-150",
|
||||
tab === "Source" ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
Source
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
"relative flex py-3 text-sm leading-6 font-semibold focus:outline-none",
|
||||
tab === "Settings"
|
||||
? "text-ayu"
|
||||
: "text-gray-700 hover:text-gray-900 focus:text-gray-900 dark:text-gray-300 dark:hover:text-white"
|
||||
)}
|
||||
onClick={() => onChange("Settings")}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
"absolute bottom-0 inset-x-0 bg-ayu h-0.5 rounded-full transition-opacity duration-150",
|
||||
tab === "Settings" ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
Settings
|
||||
</button>
|
||||
{version ? (
|
||||
<div className={"flex items-center"}>
|
||||
<VersionTag>v{version}</VersionTag>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={"hidden sm:flex items-center min-w-0"}>
|
||||
<ShareButton key={edit} onShare={onShare} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
54
playground/src/Editor/SettingsEditor.tsx
Normal file
54
playground/src/Editor/SettingsEditor.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Editor for the settings JSON.
|
||||
*/
|
||||
|
||||
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import schema from "../../../ruff.schema.json";
|
||||
|
||||
export default function SettingsEditor({
|
||||
visible,
|
||||
source,
|
||||
onChange,
|
||||
}: {
|
||||
visible: boolean;
|
||||
source: string;
|
||||
onChange: (source: string) => void;
|
||||
}) {
|
||||
const monaco = useMonaco();
|
||||
|
||||
useEffect(() => {
|
||||
monaco?.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
schemas: [
|
||||
{
|
||||
uri: "https://raw.githubusercontent.com/charliermarsh/ruff/main/ruff.schema.json",
|
||||
fileMatch: ["*"],
|
||||
schema,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, [monaco]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string | undefined) => {
|
||||
onChange(value ?? "");
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
return (
|
||||
<Editor
|
||||
options={{
|
||||
readOnly: false,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 14,
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
}}
|
||||
wrapperProps={visible ? {} : { style: { display: "none" } }}
|
||||
language={"json"}
|
||||
value={source}
|
||||
theme={"Ayu-Light"}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
53
playground/src/Editor/ShareButton.tsx
Normal file
53
playground/src/Editor/ShareButton.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function ShareButton({ onShare }: { onShare?: () => void }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (copied) {
|
||||
const timeout = setTimeout(() => setCopied(false), 2000);
|
||||
return () => clearTimeout(timeout);
|
||||
}
|
||||
}, [copied]);
|
||||
|
||||
return copied ? (
|
||||
<button
|
||||
type="button"
|
||||
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 cursor-auto text-ayu shadow-copied dark:bg-ayu/10"
|
||||
>
|
||||
<span
|
||||
className="absolute inset-0 flex items-center justify-center invisible"
|
||||
aria-hidden="true"
|
||||
>
|
||||
Share
|
||||
</span>
|
||||
<span className="" aria-hidden="false">
|
||||
Copied!
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="relative flex-none rounded-md text-sm font-semibold leading-6 py-1.5 px-3 enabled:hover:bg-ayu/80 bg-ayu text-white shadow-sm dark:shadow-highlight/20 disabled:opacity-50"
|
||||
disabled={!onShare || copied}
|
||||
onClick={
|
||||
onShare
|
||||
? () => {
|
||||
setCopied(true);
|
||||
onShare();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
aria-hidden="false"
|
||||
>
|
||||
Share
|
||||
</span>
|
||||
<span className="invisible" aria-hidden="true">
|
||||
Copied!
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
114
playground/src/Editor/SourceEditor.tsx
Normal file
114
playground/src/Editor/SourceEditor.tsx
Normal file
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* Editor for the Python source code.
|
||||
*/
|
||||
|
||||
import Editor, { useMonaco } from "@monaco-editor/react";
|
||||
import { MarkerSeverity, MarkerTag } from "monaco-editor";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { Check } from "../pkg";
|
||||
|
||||
export type Mode = "JSON" | "Python";
|
||||
|
||||
export default function SourceEditor({
|
||||
visible,
|
||||
source,
|
||||
checks,
|
||||
onChange,
|
||||
}: {
|
||||
visible: boolean;
|
||||
source: string;
|
||||
checks: Check[];
|
||||
onChange: (pythonSource: string) => void;
|
||||
}) {
|
||||
const monaco = useMonaco();
|
||||
|
||||
useEffect(() => {
|
||||
const editor = monaco?.editor;
|
||||
const model = editor?.getModels()[0];
|
||||
if (!editor || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.setModelMarkers(
|
||||
model,
|
||||
"owner",
|
||||
checks.map((check) => ({
|
||||
startLineNumber: check.location.row,
|
||||
startColumn: check.location.column + 1,
|
||||
endLineNumber: check.end_location.row,
|
||||
endColumn: check.end_location.column + 1,
|
||||
message: `${check.code}: ${check.message}`,
|
||||
severity: MarkerSeverity.Error,
|
||||
tags:
|
||||
check.code === "F401" || check.code === "F841"
|
||||
? [MarkerTag.Unnecessary]
|
||||
: [],
|
||||
}))
|
||||
);
|
||||
|
||||
const codeActionProvider = monaco?.languages.registerCodeActionProvider(
|
||||
"python",
|
||||
{
|
||||
// @ts-expect-error: The type definition is wrong.
|
||||
provideCodeActions: function (model, position) {
|
||||
const actions = checks
|
||||
.filter((check) => position.startLineNumber === check.location.row)
|
||||
.filter((check) => check.fix)
|
||||
.map((check) => ({
|
||||
title: `Fix ${check.code}`,
|
||||
id: `fix-${check.code}`,
|
||||
kind: "quickfix",
|
||||
edit: check.fix
|
||||
? {
|
||||
edits: [
|
||||
{
|
||||
resource: model.uri,
|
||||
versionId: model.getVersionId(),
|
||||
edit: {
|
||||
range: {
|
||||
startLineNumber: check.fix.location.row,
|
||||
startColumn: check.fix.location.column + 1,
|
||||
endLineNumber: check.fix.end_location.row,
|
||||
endColumn: check.fix.end_location.column + 1,
|
||||
},
|
||||
text: check.fix.content,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
}));
|
||||
return { actions, dispose: () => {} };
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
codeActionProvider?.dispose();
|
||||
};
|
||||
}, [checks, monaco]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string | undefined) => {
|
||||
onChange(value ?? "");
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<Editor
|
||||
options={{
|
||||
readOnly: false,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 14,
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
}}
|
||||
wrapperProps={visible ? {} : { style: { display: "none" } }}
|
||||
theme={"Ayu-Light"}
|
||||
language={"python"}
|
||||
value={source}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
645
playground/src/Editor/Themes.tsx
Normal file
645
playground/src/Editor/Themes.tsx
Normal file
|
@ -0,0 +1,645 @@
|
|||
import { useMonaco } from "@monaco-editor/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function Themes() {
|
||||
const monaco = useMonaco();
|
||||
|
||||
useEffect(() => {
|
||||
// Generated via `monaco-vscode-textmate-theme-converter`.
|
||||
// See: https://github.com/ayu-theme/vscode-ayu/blob/91839e8a9dfa78d61e58dbcf9b52272a01fee66a/ayu-light.json.
|
||||
monaco?.editor.defineTheme("Ayu-Light", {
|
||||
inherit: false,
|
||||
base: "vs-dark",
|
||||
colors: {
|
||||
focusBorder: "#ffaa33b3",
|
||||
foreground: "#8a9199",
|
||||
"widget.shadow": "#00000026",
|
||||
"selection.background": "#035bd626",
|
||||
"icon.foreground": "#8a9199",
|
||||
errorForeground: "#e65050",
|
||||
descriptionForeground: "#8a9199",
|
||||
"textBlockQuote.background": "#f3f4f5",
|
||||
"textLink.foreground": "#ffaa33",
|
||||
"textLink.activeForeground": "#ffaa33",
|
||||
"textPreformat.foreground": "#5c6166",
|
||||
"button.background": "#ffaa33",
|
||||
"button.foreground": "#f8f9fa",
|
||||
"button.hoverBackground": "#f9a52e",
|
||||
"button.secondaryBackground": "#8a919933",
|
||||
"button.secondaryForeground": "#5c6166",
|
||||
"button.secondaryHoverBackground": "#8a919980",
|
||||
"dropdown.background": "#fcfcfc",
|
||||
"dropdown.foreground": "#8a9199",
|
||||
"dropdown.border": "#8a919945",
|
||||
"input.background": "#fcfcfc",
|
||||
"input.border": "#8a919945",
|
||||
"input.foreground": "#5c6166",
|
||||
"input.placeholderForeground": "#8a919980",
|
||||
"inputOption.activeBorder": "#f4a0284d",
|
||||
"inputOption.activeBackground": "#ffaa3333",
|
||||
"inputOption.activeForeground": "#f4a028",
|
||||
"inputValidation.errorBackground": "#fcfcfc",
|
||||
"inputValidation.errorBorder": "#e65050",
|
||||
"inputValidation.infoBackground": "#f8f9fa",
|
||||
"inputValidation.infoBorder": "#55b4d4",
|
||||
"inputValidation.warningBackground": "#f8f9fa",
|
||||
"inputValidation.warningBorder": "#f2ae49",
|
||||
"scrollbar.shadow": "#6b7d8f00",
|
||||
"scrollbarSlider.background": "#8a919966",
|
||||
"scrollbarSlider.hoverBackground": "#8a919999",
|
||||
"scrollbarSlider.activeBackground": "#8a9199b3",
|
||||
"badge.background": "#ffaa3333",
|
||||
"badge.foreground": "#f4a028",
|
||||
"progressBar.background": "#ffaa33",
|
||||
"list.activeSelectionBackground": "#56728f1f",
|
||||
"list.activeSelectionForeground": "#5c6166",
|
||||
"list.focusBackground": "#56728f1f",
|
||||
"list.focusForeground": "#5c6166",
|
||||
"list.focusOutline": "#56728f1f",
|
||||
"list.highlightForeground": "#ffaa33",
|
||||
"list.deemphasizedForeground": "#e65050",
|
||||
"list.hoverBackground": "#56728f1f",
|
||||
"list.inactiveSelectionBackground": "#6b7d8f1f",
|
||||
"list.inactiveSelectionForeground": "#8a9199",
|
||||
"list.invalidItemForeground": "#8a91994d",
|
||||
"list.errorForeground": "#e65050",
|
||||
"tree.indentGuidesStroke": "#8a919959",
|
||||
"listFilterWidget.background": "#f3f4f5",
|
||||
"listFilterWidget.outline": "#ffaa33",
|
||||
"listFilterWidget.noMatchesOutline": "#e65050",
|
||||
"list.filterMatchBackground": "#8f30efcc",
|
||||
"list.filterMatchBorder": "#9f40ffcc",
|
||||
"activityBar.background": "#f8f9fa",
|
||||
"activityBar.foreground": "#8a9199cc",
|
||||
"activityBar.inactiveForeground": "#8a919999",
|
||||
"activityBar.border": "#f8f9fa",
|
||||
"activityBar.activeBorder": "#ffaa33b3",
|
||||
"activityBarBadge.background": "#ffaa33",
|
||||
"activityBarBadge.foreground": "#f8f9fa",
|
||||
"sideBar.background": "#f8f9fa",
|
||||
"sideBar.border": "#f8f9fa",
|
||||
"sideBarTitle.foreground": "#8a9199",
|
||||
"sideBarSectionHeader.background": "#f8f9fa",
|
||||
"sideBarSectionHeader.foreground": "#8a9199",
|
||||
"sideBarSectionHeader.border": "#f8f9fa",
|
||||
"minimap.background": "#f8f9fa",
|
||||
"minimap.selectionHighlight": "#035bd626",
|
||||
"minimap.errorHighlight": "#e65050",
|
||||
"minimap.findMatchHighlight": "#9f40ff2b",
|
||||
"minimapGutter.addedBackground": "#6cbf43",
|
||||
"minimapGutter.modifiedBackground": "#478acc",
|
||||
"minimapGutter.deletedBackground": "#ff7383",
|
||||
"editorGroup.border": "#6b7d8f1f",
|
||||
"editorGroup.background": "#f3f4f5",
|
||||
"editorGroupHeader.noTabsBackground": "#f8f9fa",
|
||||
"editorGroupHeader.tabsBackground": "#f8f9fa",
|
||||
"editorGroupHeader.tabsBorder": "#f8f9fa",
|
||||
"tab.activeBackground": "#f8f9fa",
|
||||
"tab.activeForeground": "#5c6166",
|
||||
"tab.border": "#f8f9fa",
|
||||
"tab.activeBorder": "#ffaa33",
|
||||
"tab.unfocusedActiveBorder": "#8a9199",
|
||||
"tab.inactiveBackground": "#f8f9fa",
|
||||
"tab.inactiveForeground": "#8a9199",
|
||||
"tab.unfocusedActiveForeground": "#8a9199",
|
||||
"tab.unfocusedInactiveForeground": "#8a9199",
|
||||
"editor.background": "#f8f9fa",
|
||||
"editor.foreground": "#5c6166",
|
||||
"editorLineNumber.foreground": "#8a919966",
|
||||
"editorLineNumber.activeForeground": "#8a9199cc",
|
||||
"editorCursor.foreground": "#ffaa33",
|
||||
"editor.inactiveSelectionBackground": "#035bd612",
|
||||
"editor.selectionBackground": "#035bd626",
|
||||
"editor.selectionHighlightBackground": "#6cbf4326",
|
||||
"editor.selectionHighlightBorder": "#6cbf4300",
|
||||
"editor.wordHighlightBackground": "#478acc14",
|
||||
"editor.wordHighlightStrongBackground": "#6cbf4314",
|
||||
"editor.wordHighlightBorder": "#478acc80",
|
||||
"editor.wordHighlightStrongBorder": "#6cbf4380",
|
||||
"editor.findMatchBackground": "#9f40ff2b",
|
||||
"editor.findMatchBorder": "#9f40ff2b",
|
||||
"editor.findMatchHighlightBackground": "#9f40ffcc",
|
||||
"editor.findMatchHighlightBorder": "#8f30efcc",
|
||||
"editor.findRangeHighlightBackground": "#9f40ff40",
|
||||
"editor.rangeHighlightBackground": "#9f40ff33",
|
||||
"editor.lineHighlightBackground": "#8a91991a",
|
||||
"editorLink.activeForeground": "#ffaa33",
|
||||
"editorWhitespace.foreground": "#8a919966",
|
||||
"editorIndentGuide.background": "#8a91992e",
|
||||
"editorIndentGuide.activeBackground": "#8a919959",
|
||||
"editorRuler.foreground": "#8a91992e",
|
||||
"editorCodeLens.foreground": "#787b8099",
|
||||
"editorBracketMatch.background": "#8a91994d",
|
||||
"editorBracketMatch.border": "#8a91994d",
|
||||
"editor.snippetTabstopHighlightBackground": "#6cbf4333",
|
||||
"editorOverviewRuler.border": "#6b7d8f1f",
|
||||
"editorOverviewRuler.modifiedForeground": "#478acc",
|
||||
"editorOverviewRuler.addedForeground": "#6cbf43",
|
||||
"editorOverviewRuler.deletedForeground": "#ff7383",
|
||||
"editorOverviewRuler.errorForeground": "#e65050",
|
||||
"editorOverviewRuler.warningForeground": "#ffaa33",
|
||||
"editorOverviewRuler.bracketMatchForeground": "#8a9199b3",
|
||||
"editorOverviewRuler.wordHighlightForeground": "#478acc66",
|
||||
"editorOverviewRuler.wordHighlightStrongForeground": "#6cbf4366",
|
||||
"editorOverviewRuler.findMatchForeground": "#9f40ff2b",
|
||||
"editorError.foreground": "#e65050",
|
||||
"editorWarning.foreground": "#ffaa33",
|
||||
"editorGutter.modifiedBackground": "#478acccc",
|
||||
"editorGutter.addedBackground": "#6cbf43cc",
|
||||
"editorGutter.deletedBackground": "#ff7383cc",
|
||||
"diffEditor.insertedTextBackground": "#6cbf431f",
|
||||
"diffEditor.removedTextBackground": "#ff73831f",
|
||||
"diffEditor.diagonalFill": "#6b7d8f1f",
|
||||
"editorWidget.background": "#f3f4f5",
|
||||
"editorWidget.border": "#6b7d8f1f",
|
||||
"editorHoverWidget.background": "#f3f4f5",
|
||||
"editorHoverWidget.border": "#6b7d8f1f",
|
||||
"editorSuggestWidget.background": "#f3f4f5",
|
||||
"editorSuggestWidget.border": "#6b7d8f1f",
|
||||
"editorSuggestWidget.highlightForeground": "#ffaa33",
|
||||
"editorSuggestWidget.selectedBackground": "#56728f1f",
|
||||
"debugExceptionWidget.border": "#6b7d8f1f",
|
||||
"debugExceptionWidget.background": "#f3f4f5",
|
||||
"editorMarkerNavigation.background": "#f3f4f5",
|
||||
"peekView.border": "#56728f1f",
|
||||
"peekViewTitle.background": "#56728f1f",
|
||||
"peekViewTitleDescription.foreground": "#8a9199",
|
||||
"peekViewTitleLabel.foreground": "#5c6166",
|
||||
"peekViewEditor.background": "#f3f4f5",
|
||||
"peekViewEditor.matchHighlightBackground": "#9f40ffcc",
|
||||
"peekViewEditor.matchHighlightBorder": "#8f30efcc",
|
||||
"peekViewResult.background": "#f3f4f5",
|
||||
"peekViewResult.fileForeground": "#5c6166",
|
||||
"peekViewResult.lineForeground": "#8a9199",
|
||||
"peekViewResult.matchHighlightBackground": "#9f40ffcc",
|
||||
"peekViewResult.selectionBackground": "#56728f1f",
|
||||
"panel.background": "#f8f9fa",
|
||||
"panel.border": "#6b7d8f1f",
|
||||
"panelTitle.activeBorder": "#ffaa33",
|
||||
"panelTitle.activeForeground": "#5c6166",
|
||||
"panelTitle.inactiveForeground": "#8a9199",
|
||||
"statusBar.background": "#f8f9fa",
|
||||
"statusBar.foreground": "#8a9199",
|
||||
"statusBar.border": "#f8f9fa",
|
||||
"statusBar.debuggingBackground": "#ed9366",
|
||||
"statusBar.debuggingForeground": "#fcfcfc",
|
||||
"statusBar.noFolderBackground": "#f3f4f5",
|
||||
"statusBarItem.activeBackground": "#8a919933",
|
||||
"statusBarItem.hoverBackground": "#8a919933",
|
||||
"statusBarItem.prominentBackground": "#6b7d8f1f",
|
||||
"statusBarItem.prominentHoverBackground": "#00000030",
|
||||
"statusBarItem.remoteBackground": "#ffaa33",
|
||||
"statusBarItem.remoteForeground": "#fcfcfc",
|
||||
"titleBar.activeBackground": "#f8f9fa",
|
||||
"titleBar.activeForeground": "#5c6166",
|
||||
"titleBar.inactiveBackground": "#f8f9fa",
|
||||
"titleBar.inactiveForeground": "#8a9199",
|
||||
"titleBar.border": "#f8f9fa",
|
||||
"extensionButton.prominentForeground": "#fcfcfc",
|
||||
"extensionButton.prominentBackground": "#ffaa33",
|
||||
"extensionButton.prominentHoverBackground": "#f9a52e",
|
||||
"pickerGroup.border": "#6b7d8f1f",
|
||||
"pickerGroup.foreground": "#8a919980",
|
||||
"debugToolBar.background": "#f3f4f5",
|
||||
"debugIcon.breakpointForeground": "#ed9366",
|
||||
"debugIcon.breakpointDisabledForeground": "#ed936680",
|
||||
"debugConsoleInputIcon.foreground": "#ffaa33",
|
||||
"welcomePage.tileBackground": "#f8f9fa",
|
||||
"welcomePage.tileShadow": "#00000026",
|
||||
"welcomePage.progress.background": "#8a91991a",
|
||||
"welcomePage.buttonBackground": "#ffaa3366",
|
||||
"walkThrough.embeddedEditorBackground": "#f3f4f5",
|
||||
"gitDecoration.modifiedResourceForeground": "#478accb3",
|
||||
"gitDecoration.deletedResourceForeground": "#ff7383b3",
|
||||
"gitDecoration.untrackedResourceForeground": "#6cbf43b3",
|
||||
"gitDecoration.ignoredResourceForeground": "#8a919980",
|
||||
"gitDecoration.conflictingResourceForeground": "",
|
||||
"gitDecoration.submoduleResourceForeground": "#a37accb3",
|
||||
"settings.headerForeground": "#5c6166",
|
||||
"settings.modifiedItemIndicator": "#478acc",
|
||||
"keybindingLabel.background": "#8a91991a",
|
||||
"keybindingLabel.foreground": "#5c6166",
|
||||
"keybindingLabel.border": "#5c61661a",
|
||||
"keybindingLabel.bottomBorder": "#5c61661a",
|
||||
"terminal.background": "#f8f9fa",
|
||||
"terminal.foreground": "#5c6166",
|
||||
"terminal.ansiBlack": "#000000",
|
||||
"terminal.ansiRed": "#ea6c6d",
|
||||
"terminal.ansiGreen": "#6cbf43",
|
||||
"terminal.ansiYellow": "#eca944",
|
||||
"terminal.ansiBlue": "#3199e1",
|
||||
"terminal.ansiMagenta": "#9e75c7",
|
||||
"terminal.ansiCyan": "#46ba94",
|
||||
"terminal.ansiWhite": "#c7c7c7",
|
||||
"terminal.ansiBrightBlack": "#686868",
|
||||
"terminal.ansiBrightRed": "#f07171",
|
||||
"terminal.ansiBrightGreen": "#86b300",
|
||||
"terminal.ansiBrightYellow": "#f2ae49",
|
||||
"terminal.ansiBrightBlue": "#399ee6",
|
||||
"terminal.ansiBrightMagenta": "#a37acc",
|
||||
"terminal.ansiBrightCyan": "#4cbf99",
|
||||
"terminal.ansiBrightWhite": "#d1d1d1",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
fontStyle: "italic",
|
||||
foreground: "#787b8099",
|
||||
token: "comment",
|
||||
},
|
||||
{
|
||||
foreground: "#86b300",
|
||||
token: "string",
|
||||
},
|
||||
{
|
||||
foreground: "#86b300",
|
||||
token: "constant.other.symbol",
|
||||
},
|
||||
{
|
||||
foreground: "#4cbf99",
|
||||
token: "string.regexp",
|
||||
},
|
||||
{
|
||||
foreground: "#4cbf99",
|
||||
token: "constant.character",
|
||||
},
|
||||
{
|
||||
foreground: "#4cbf99",
|
||||
token: "constant.other",
|
||||
},
|
||||
{
|
||||
foreground: "#a37acc",
|
||||
token: "constant.numeric",
|
||||
},
|
||||
{
|
||||
foreground: "#a37acc",
|
||||
token: "constant.language",
|
||||
},
|
||||
{
|
||||
foreground: "#5c6166",
|
||||
token: "variable",
|
||||
},
|
||||
{
|
||||
foreground: "#5c6166",
|
||||
token: "variable.parameter.function-call",
|
||||
},
|
||||
{
|
||||
foreground: "#f07171",
|
||||
token: "variable.member",
|
||||
},
|
||||
{
|
||||
fontStyle: "italic",
|
||||
foreground: "#55b4d4",
|
||||
token: "variable.language",
|
||||
},
|
||||
{
|
||||
foreground: "#fa8d3e",
|
||||
token: "storage",
|
||||
},
|
||||
{
|
||||
foreground: "#fa8d3e",
|
||||
token: "keyword",
|
||||
},
|
||||
{
|
||||
foreground: "#ed9366",
|
||||
token: "keyword.operator",
|
||||
},
|
||||
{
|
||||
foreground: "#5c6166b3",
|
||||
token: "punctuation.separator",
|
||||
},
|
||||
{
|
||||
foreground: "#5c6166b3",
|
||||
token: "punctuation.terminator",
|
||||
},
|
||||
{
|
||||
foreground: "#5c6166",
|
||||
token: "punctuation.section",
|
||||
},
|
||||
{
|
||||
foreground: "#ed9366",
|
||||
token: "punctuation.accessor",
|
||||
},
|
||||
{
|
||||
foreground: "#fa8d3e",
|
||||
token: "punctuation.definition.template-expression",
|
||||
},
|
||||
{
|
||||
foreground: "#fa8d3e",
|
||||
token: "punctuation.section.embedded",
|
||||
},
|
||||
{
|
||||
foreground: "#5c6166",
|
||||
token: "meta.embedded",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "source.java storage.type",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "source.haskell storage.type",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "source.c storage.type",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d4",
|
||||
token: "entity.other.inherited-class",
|
||||
},
|
||||
{
|
||||
foreground: "#fa8d3e",
|
||||
token: "storage.type.function",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d4",
|
||||
token: "source.java storage.type.primitive",
|
||||
},
|
||||
{
|
||||
foreground: "#f2ae49",
|
||||
token: "entity.name.function",
|
||||
},
|
||||
{
|
||||
foreground: "#a37acc",
|
||||
token: "variable.parameter",
|
||||
},
|
||||
{
|
||||
foreground: "#a37acc",
|
||||
token: "meta.parameter",
|
||||
},
|
||||
{
|
||||
foreground: "#f2ae49",
|
||||
token: "variable.function",
|
||||
},
|
||||
{
|
||||
foreground: "#f2ae49",
|
||||
token: "variable.annotation",
|
||||
},
|
||||
{
|
||||
foreground: "#f2ae49",
|
||||
token: "meta.function-call.generic",
|
||||
},
|
||||
{
|
||||
foreground: "#f2ae49",
|
||||
token: "support.function.go",
|
||||
},
|
||||
{
|
||||
foreground: "#f07171",
|
||||
token: "support.function",
|
||||
},
|
||||
{
|
||||
foreground: "#f07171",
|
||||
token: "support.macro",
|
||||
},
|
||||
{
|
||||
foreground: "#86b300",
|
||||
token: "entity.name.import",
|
||||
},
|
||||
{
|
||||
foreground: "#86b300",
|
||||
token: "entity.name.package",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "entity.name",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d4",
|
||||
token: "entity.name.tag",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d4",
|
||||
token: "meta.tag.sgml",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "support.class.component",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d480",
|
||||
token: "punctuation.definition.tag.end",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d480",
|
||||
token: "punctuation.definition.tag.begin",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d480",
|
||||
token: "punctuation.definition.tag",
|
||||
},
|
||||
{
|
||||
foreground: "#f2ae49",
|
||||
token: "entity.other.attribute-name",
|
||||
},
|
||||
{
|
||||
fontStyle: "italic",
|
||||
foreground: "#ed9366",
|
||||
token: "support.constant",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d4",
|
||||
token: "support.type",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d4",
|
||||
token: "support.class",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d4",
|
||||
token: "source.go storage.type",
|
||||
},
|
||||
{
|
||||
foreground: "#e6ba7e",
|
||||
token: "meta.decorator variable.other",
|
||||
},
|
||||
{
|
||||
foreground: "#e6ba7e",
|
||||
token: "meta.decorator punctuation.decorator",
|
||||
},
|
||||
{
|
||||
foreground: "#e6ba7e",
|
||||
token: "storage.type.annotation",
|
||||
},
|
||||
{
|
||||
foreground: "#e65050",
|
||||
token: "invalid",
|
||||
},
|
||||
{
|
||||
foreground: "#c594c5",
|
||||
token: "meta.diff",
|
||||
},
|
||||
{
|
||||
foreground: "#c594c5",
|
||||
token: "meta.diff.header",
|
||||
},
|
||||
{
|
||||
foreground: "#f2ae49",
|
||||
token: "source.ruby variable.other.readwrite",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "source.css entity.name.tag",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "source.sass entity.name.tag",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "source.scss entity.name.tag",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "source.less entity.name.tag",
|
||||
},
|
||||
{
|
||||
foreground: "#399ee6",
|
||||
token: "source.stylus entity.name.tag",
|
||||
},
|
||||
{
|
||||
foreground: "#787b8099",
|
||||
token: "source.css support.type",
|
||||
},
|
||||
{
|
||||
foreground: "#787b8099",
|
||||
token: "source.sass support.type",
|
||||
},
|
||||
{
|
||||
foreground: "#787b8099",
|
||||
token: "source.scss support.type",
|
||||
},
|
||||
{
|
||||
foreground: "#787b8099",
|
||||
token: "source.less support.type",
|
||||
},
|
||||
{
|
||||
foreground: "#787b8099",
|
||||
token: "source.stylus support.type",
|
||||
},
|
||||
{
|
||||
fontStyle: "normal",
|
||||
foreground: "#55b4d4",
|
||||
token: "support.type.property-name",
|
||||
},
|
||||
{
|
||||
foreground: "#787b8099",
|
||||
token: "constant.numeric.line-number.find-in-files - match",
|
||||
},
|
||||
{
|
||||
foreground: "#fa8d3e",
|
||||
token: "constant.numeric.line-number.match",
|
||||
},
|
||||
{
|
||||
foreground: "#86b300",
|
||||
token: "entity.name.filename.find-in-files",
|
||||
},
|
||||
{
|
||||
foreground: "#e65050",
|
||||
token: "message.error",
|
||||
},
|
||||
{
|
||||
fontStyle: "bold",
|
||||
foreground: "#86b300",
|
||||
token: "markup.heading",
|
||||
},
|
||||
{
|
||||
fontStyle: "bold",
|
||||
foreground: "#86b300",
|
||||
token: "markup.heading entity.name",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d4",
|
||||
token: "markup.underline.link",
|
||||
},
|
||||
{
|
||||
foreground: "#55b4d4",
|
||||
token: "string.other.link",
|
||||
},
|
||||
{
|
||||
fontStyle: "italic",
|
||||
foreground: "#f07171",
|
||||
token: "markup.italic",
|
||||
},
|
||||
{
|
||||
fontStyle: "bold",
|
||||
foreground: "#f07171",
|
||||
token: "markup.bold",
|
||||
},
|
||||
{
|
||||
fontStyle: "bold italic",
|
||||
token: "markup.italic markup.bold",
|
||||
},
|
||||
{
|
||||
fontStyle: "bold italic",
|
||||
token: "markup.bold markup.italic",
|
||||
},
|
||||
{
|
||||
background: "#5c616605",
|
||||
token: "markup.raw",
|
||||
},
|
||||
{
|
||||
background: "#5c61660f",
|
||||
token: "markup.raw.inline",
|
||||
},
|
||||
{
|
||||
fontStyle: "bold",
|
||||
background: "#5c61660f",
|
||||
foreground: "#787b8099",
|
||||
token: "meta.separator",
|
||||
},
|
||||
{
|
||||
foreground: "#4cbf99",
|
||||
fontStyle: "italic",
|
||||
token: "markup.quote",
|
||||
},
|
||||
{
|
||||
foreground: "#f2ae49",
|
||||
token: "markup.list punctuation.definition.list.begin",
|
||||
},
|
||||
{
|
||||
foreground: "#6cbf43",
|
||||
token: "markup.inserted",
|
||||
},
|
||||
{
|
||||
foreground: "#478acc",
|
||||
token: "markup.changed",
|
||||
},
|
||||
{
|
||||
foreground: "#ff7383",
|
||||
token: "markup.deleted",
|
||||
},
|
||||
{
|
||||
foreground: "#e6ba7e",
|
||||
token: "markup.strike",
|
||||
},
|
||||
{
|
||||
background: "#5c61660f",
|
||||
foreground: "#55b4d4",
|
||||
token: "markup.table",
|
||||
},
|
||||
{
|
||||
foreground: "#ed9366",
|
||||
token: "text.html.markdown markup.inline.raw",
|
||||
},
|
||||
{
|
||||
background: "#787b8099",
|
||||
foreground: "#787b8099",
|
||||
token: "text.html.markdown meta.dummy.line-break",
|
||||
},
|
||||
{
|
||||
background: "#5c6166",
|
||||
foreground: "#787b8099",
|
||||
token: "punctuation.definition.markdown",
|
||||
},
|
||||
// Edits.
|
||||
{
|
||||
foreground: "#fa8d3e",
|
||||
token: "number",
|
||||
},
|
||||
],
|
||||
encodedTokensColors: [],
|
||||
});
|
||||
}, [monaco]);
|
||||
|
||||
return null;
|
||||
}
|
26
playground/src/Editor/VersionTag.tsx
Normal file
26
playground/src/Editor/VersionTag.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import classNames from "classnames";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export default function VersionTag({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"text-gray-500",
|
||||
"text-xs",
|
||||
"leading-5",
|
||||
"font-semibold",
|
||||
"bg-gray-400/10",
|
||||
"rounded-full",
|
||||
"py-1",
|
||||
"px-3",
|
||||
"flex",
|
||||
"items-center",
|
||||
"dark:bg-gray-800",
|
||||
"dark:text-gray-400",
|
||||
"dark:shadow-highlight/4"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
63
playground/src/Editor/config.ts
Normal file
63
playground/src/Editor/config.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import lzstring from "lz-string";
|
||||
import { OptionGroup } from "../ruff_options";
|
||||
|
||||
export type Config = { [K: string]: any };
|
||||
|
||||
/**
|
||||
* Parse an encoded value from the options export.
|
||||
*
|
||||
* TODO(charlie): Use JSON for the default values.
|
||||
*/
|
||||
function parse(value: any): any {
|
||||
if (value == "None") {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default configuration for the playground.
|
||||
*/
|
||||
export function defaultConfig(availableOptions: OptionGroup[]): Config {
|
||||
const config: Config = {};
|
||||
for (const group of availableOptions) {
|
||||
if (group.name == "globals") {
|
||||
for (const field of group.fields) {
|
||||
config[field.name] = parse(field.default);
|
||||
}
|
||||
} else {
|
||||
config[group.name] = {};
|
||||
for (const field of group.fields) {
|
||||
config[group.name][field.name] = parse(field.default);
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the configuration to a URL.
|
||||
*/
|
||||
export function persist(configSource: string, pythonSource: string) {
|
||||
window.location.hash = lzstring.compressToEncodedURIComponent(
|
||||
configSource + "$$$" + pythonSource
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the configuration from a URL.
|
||||
*/
|
||||
export function restore(): [string, string] | null {
|
||||
const value = lzstring.decompressFromEncodedURIComponent(
|
||||
window.location.hash.slice(1)
|
||||
);
|
||||
|
||||
if (value) {
|
||||
const parts = value.split("$$$");
|
||||
const configSource = parts[0];
|
||||
const pythonSource = parts[1];
|
||||
return [configSource, pythonSource];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
3
playground/src/Editor/index.tsx
Normal file
3
playground/src/Editor/index.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Editor from "./Editor";
|
||||
|
||||
export default Editor;
|
|
@ -1,72 +0,0 @@
|
|||
import { Config } from "./config";
|
||||
import { AVAILABLE_OPTIONS } from "./ruff_options";
|
||||
|
||||
function OptionEntry({
|
||||
config,
|
||||
defaultConfig,
|
||||
groupName,
|
||||
fieldName,
|
||||
onChange,
|
||||
}: {
|
||||
config: Config | null;
|
||||
defaultConfig: Config;
|
||||
groupName: string;
|
||||
fieldName: string;
|
||||
onChange: (groupName: string, fieldName: string, value: string) => void;
|
||||
}) {
|
||||
const value =
|
||||
config && config[groupName] && config[groupName][fieldName]
|
||||
? config[groupName][fieldName]
|
||||
: "";
|
||||
|
||||
return (
|
||||
<span>
|
||||
<label>
|
||||
{fieldName}
|
||||
<input
|
||||
value={value}
|
||||
placeholder={defaultConfig[groupName][fieldName]}
|
||||
type="text"
|
||||
onChange={(event) => {
|
||||
onChange(groupName, fieldName, event.target.value);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function Options({
|
||||
config,
|
||||
defaultConfig,
|
||||
onChange,
|
||||
}: {
|
||||
config: Config | null;
|
||||
defaultConfig: Config;
|
||||
onChange: (groupName: string, fieldName: string, value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="options">
|
||||
{AVAILABLE_OPTIONS.map((group) => (
|
||||
<details key={group.name}>
|
||||
<summary>{group.name}</summary>
|
||||
<div>
|
||||
<ul>
|
||||
{group.fields.map((field) => (
|
||||
<li key={field.name}>
|
||||
<OptionEntry
|
||||
config={config}
|
||||
defaultConfig={defaultConfig}
|
||||
groupName={group.name}
|
||||
fieldName={field.name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import { OptionGroup } from "./ruff_options";
|
||||
|
||||
export type Config = { [key: string]: { [key: string]: string } };
|
||||
|
||||
export function getDefaultConfig(availableOptions: OptionGroup[]): Config {
|
||||
const config: Config = {};
|
||||
availableOptions.forEach((group) => {
|
||||
config[group.name] = {};
|
||||
group.fields.forEach((f) => {
|
||||
config[group.name][f.name] = f.default;
|
||||
});
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the config in the application to something Ruff accepts.
|
||||
*
|
||||
* Application config is always nested one level. Ruff allows for some
|
||||
* top-level options.
|
||||
*
|
||||
* Any option value is parsed as JSON to convert it to a native JS object.
|
||||
* If that fails, e.g. while a user is typing, we let the application handle that
|
||||
* and show an error.
|
||||
*/
|
||||
export function toRuffConfig(config: Config): any {
|
||||
const convertValue = (value: string): any => {
|
||||
return value === "None" ? null : JSON.parse(value);
|
||||
};
|
||||
|
||||
const result: any = {};
|
||||
Object.keys(config).forEach((group_name) => {
|
||||
const fields = config[group_name];
|
||||
if (!fields || Object.keys(fields).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (group_name === "globals") {
|
||||
Object.keys(fields).forEach((field_name) => {
|
||||
result[field_name] = convertValue(fields[field_name]);
|
||||
});
|
||||
} else {
|
||||
result[group_name] = {};
|
||||
|
||||
Object.keys(fields).forEach((field_name) => {
|
||||
result[group_name][field_name] = convertValue(fields[field_name]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
40
playground/src/constants.ts
Normal file
40
playground/src/constants.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { defaultConfig } from "./Editor/config";
|
||||
import { AVAILABLE_OPTIONS } from "./ruff_options";
|
||||
|
||||
export const DEFAULT_PYTHON_SOURCE =
|
||||
"import os\n" +
|
||||
"\n" +
|
||||
"# Define a function that takes an integer n and returns the nth number in the Fibonacci\n" +
|
||||
"# sequence.\n" +
|
||||
"def fibonacci(n):\n" +
|
||||
' """Compute the nth number in the Fibonacci sequence."""\n' +
|
||||
" x = 1\n" +
|
||||
" if n == 0:\n" +
|
||||
" return 0\n" +
|
||||
" elif n == 1:\n" +
|
||||
" return 1\n" +
|
||||
" else:\n" +
|
||||
" return fibonacci(n - 1) + fibonacci(n - 2)\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"# Use a for loop to generate and print the first 10 numbers in the Fibonacci sequence.\n" +
|
||||
"for i in range(10):\n" +
|
||||
" print(fibonacci(i))\n" +
|
||||
"\n" +
|
||||
"# Output:\n" +
|
||||
"# 0\n" +
|
||||
"# 1\n" +
|
||||
"# 1\n" +
|
||||
"# 2\n" +
|
||||
"# 3\n" +
|
||||
"# 5\n" +
|
||||
"# 8\n" +
|
||||
"# 13\n" +
|
||||
"# 21\n" +
|
||||
"# 34\n";
|
||||
|
||||
export const DEFAULT_CONFIG_SOURCE = JSON.stringify(
|
||||
defaultConfig(AVAILABLE_OPTIONS),
|
||||
null,
|
||||
2
|
||||
);
|
24
playground/src/index.css
Normal file
24
playground/src/index.css
Normal file
|
@ -0,0 +1,24 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
html,
|
||||
#root {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.shadow-copied {
|
||||
--tw-shadow: 0 0 0 1px #f07171, inset 0 0 0 1px #f07171;
|
||||
--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color),
|
||||
inset 0 0 0 1px var(--tw-shadow-color);
|
||||
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
|
||||
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./style.css";
|
||||
import Editor from "./Editor";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<Editor />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
|
|
@ -225,7 +225,7 @@ export const AVAILABLE_OPTIONS: OptionGroup[] = [
|
|||
{"name": "pydocstyle", "fields": [
|
||||
{
|
||||
"name": "convention",
|
||||
"default": '"convention"',
|
||||
"default": 'None',
|
||||
"type": 'Convention',
|
||||
},
|
||||
]},
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
html,
|
||||
#root,
|
||||
#app {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.options {
|
||||
height: 100vh;
|
||||
overflow-y: scroll;
|
||||
padding: 1em;
|
||||
min-width: 300px;
|
||||
border-right: 1px solid lightgray;
|
||||
}
|
||||
|
||||
.options ul {
|
||||
padding-left: 1em;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.options li {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
.options details {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.options summary {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.options input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.editor {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#error {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
min-height: 1em;
|
||||
padding: 1em;
|
||||
background: darkred;
|
||||
color: white;
|
||||
}
|
17
playground/tailwind.config.cjs
Normal file
17
playground/tailwind.config.cjs
Normal file
|
@ -0,0 +1,17 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
|
||||
module.exports = {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
ayu: "#f07171",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["Inter var", ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
|
@ -16,6 +16,8 @@ use crate::settings::{flags, Settings};
|
|||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::source_code_style::SourceCodeStyleDetector;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const TYPES: &'static str = r#"
|
||||
export interface Check {
|
||||
|
@ -59,6 +61,11 @@ pub fn run() {
|
|||
console_log::init_with_level(Level::Debug).expect("Initializing logger went wrong.");
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn current_version() -> JsValue {
|
||||
JsValue::from(VERSION)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
|
||||
let options: Options = serde_wasm_bindgen::from_value(options).map_err(|e| e.to_string())?;
|
||||
|
|
|
@ -17,7 +17,7 @@ pub enum Convention {
|
|||
#[serde(deny_unknown_fields, rename_all = "kebab-case", rename = "Pydocstyle")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
default = r#""convention""#,
|
||||
default = r#"None"#,
|
||||
value_type = "Convention",
|
||||
example = r#"
|
||||
# Use Google-style docstrings.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue