Skip to content

Commit

Permalink
Playground: Add Copy as pyproject.toml/ruff.toml and paste from TOML (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser authored Sep 13, 2024
1 parent 43a5922 commit 21bfab9
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 21 deletions.
15 changes: 14 additions & 1 deletion playground/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
145 changes: 126 additions & 19 deletions playground/src/Editor/SettingsEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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={{
Expand All @@ -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),
};
}
11 changes: 11 additions & 0 deletions playground/src/Editor/setupMonaco.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import { Monaco } from "@monaco-editor/react";
import schema from "../../../ruff.schema.json";

export const WHITE = "#ffffff";
export const RADIATE = "#d7ff64";
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 21bfab9

Please sign in to comment.