Playground: Add Copy as pyproject.toml/ruff.toml and paste from TOML (#13328)

This commit is contained in:
Micha Reiser 2024-09-13 14:44:24 +02:00 committed by GitHub
parent 43a5922f6f
commit 21bfab9b69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 153 additions and 21 deletions

View file

@ -14,7 +14,8 @@
"monaco-editor": "^0.51.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-resizable-panels": "^2.0.0"
"react-resizable-panels": "^2.0.0",
"smol-toml": "^1.3.0"
},
"devDependencies": {
"@types/react": "^18.0.26",
@ -4560,6 +4561,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/smol-toml": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.0.tgz",
"integrity": "sha512-tWpi2TsODPScmi48b/OQZGi2lgUmBCHy6SZrhi/FdnnHiU1GwebbCfuQuxsC3nHaLwtYeJGPrDZDIeodDOc4pA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 18"
},
"funding": {
"url": "https://github.com/sponsors/cyyynthia"
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",

View file

@ -21,7 +21,8 @@
"monaco-editor": "^0.51.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-resizable-panels": "^2.0.0"
"react-resizable-panels": "^2.0.0",
"smol-toml": "^1.3.0"
},
"devDependencies": {
"@types/react": "^18.0.26",

View file

@ -2,10 +2,11 @@
* Editor for the settings JSON.
*/
import MonacoEditor, { useMonaco } from "@monaco-editor/react";
import { useCallback, useEffect } from "react";
import schema from "../../../ruff.schema.json";
import { useCallback } from "react";
import { Theme } from "./theme";
import MonacoEditor from "@monaco-editor/react";
import { editor } from "monaco-editor";
import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
export default function SettingsEditor({
visible,
@ -18,26 +19,86 @@ export default function SettingsEditor({
theme: Theme;
onChange: (source: string) => void;
}) {
const monaco = useMonaco();
useEffect(() => {
monaco?.languages.json.jsonDefaults.setDiagnosticsOptions({
schemas: [
{
uri: "https://raw.githubusercontent.com/astral-sh/ruff/main/ruff.schema.json",
fileMatch: ["*"],
schema,
},
],
});
}, [monaco]);
const handleChange = useCallback(
(value: string | undefined) => {
onChange(value ?? "");
},
[onChange],
);
const handleMount = useCallback((editor: IStandaloneCodeEditor) => {
editor.addAction({
id: "copyAsRuffToml",
label: "Copy as ruff.toml",
contextMenuGroupId: "9_cutcopypaste",
contextMenuOrder: 3,
async run(editor): Promise<undefined> {
const model = editor.getModel();
if (model == null) {
return;
}
const toml = await import("smol-toml");
const settings = model.getValue();
const tomlSettings = toml.stringify(JSON.parse(settings));
await navigator.clipboard.writeText(tomlSettings);
},
});
editor.addAction({
id: "copyAsPyproject.toml",
label: "Copy as pyproject.toml",
contextMenuGroupId: "9_cutcopypaste",
contextMenuOrder: 4,
async run(editor): Promise<undefined> {
const model = editor.getModel();
if (model == null) {
return;
}
const settings = model.getValue();
const toml = await import("smol-toml");
const tomlSettings = toml.stringify(
prefixWithRuffToml(JSON.parse(settings)),
);
await navigator.clipboard.writeText(tomlSettings);
},
});
editor.onDidPaste((event) => {
const model = editor.getModel();
if (model == null) {
return;
}
// Allow pasting a TOML settings configuration if it replaces the entire settings.
if (model.getFullModelRange().equalsRange(event.range)) {
const pasted = model.getValueInRange(event.range);
// Text starting with a `{` must be JSON. Don't even try to parse as TOML.
if (!pasted.trimStart().startsWith("{")) {
import("smol-toml").then((toml) => {
try {
const parsed = toml.parse(pasted);
const cleansed = stripToolRuff(parsed);
model.setValue(JSON.stringify(cleansed, null, 4));
} catch (e) {
// Turned out to not be TOML after all.
console.warn("Failed to parse settings as TOML", e);
}
});
}
}
});
}, []);
return (
<MonacoEditor
options={{
@ -46,13 +107,59 @@ export default function SettingsEditor({
fontSize: 14,
roundedSelection: false,
scrollBeyondLastLine: false,
contextmenu: false,
contextmenu: true,
}}
onMount={handleMount}
wrapperProps={visible ? {} : { style: { display: "none" } }}
language={"json"}
language="json"
value={source}
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
onChange={handleChange}
/>
);
}
function stripToolRuff(settings: object) {
const { tool, ...nonToolSettings } = settings as any;
// Flatten out `tool.ruff.x` to just `x`
if (typeof tool == "object" && !Array.isArray(tool)) {
if (tool.ruff != null) {
return { ...nonToolSettings, ...tool.ruff };
}
}
return Object.fromEntries(
Object.entries(settings).flatMap(([key, value]) => {
if (key.startsWith("tool.ruff")) {
const strippedKey = key.substring("tool.ruff".length);
if (strippedKey === "") {
return Object.entries(value);
}
return [[strippedKey.substring(1), value]];
}
return [[key, value]];
}),
);
}
function prefixWithRuffToml(settings: object) {
const subTableEntries = [];
const ruffTableEntries = [];
for (const [key, value] of Object.entries(settings)) {
if (typeof value === "object" && !Array.isArray(value)) {
subTableEntries.push([`tool.ruff.${key}`, value]);
} else {
ruffTableEntries.push([key, value]);
}
}
return {
["tool.ruff"]: Object.fromEntries(ruffTableEntries),
...Object.fromEntries(subTableEntries),
};
}

View file

@ -3,6 +3,7 @@
*/
import { Monaco } from "@monaco-editor/react";
import schema from "../../../ruff.schema.json";
export const WHITE = "#ffffff";
export const RADIATE = "#d7ff64";
@ -31,6 +32,16 @@ export function setupMonaco(monaco: Monaco) {
defineRustPythonTokensLanguage(monaco);
defineRustPythonAstLanguage(monaco);
defineCommentsLanguage(monaco);
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
schemas: [
{
uri: "https://raw.githubusercontent.com/astral-sh/ruff/main/ruff.schema.json",
fileMatch: ["*"],
schema,
},
],
});
}
function defineAyuThemes(monaco: Monaco) {