дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
feat(python): support Ruff formatting and config directories (#1573)
Ruff is now an external formatter for Python next to the Ruff language server: Settings › Languages & Frameworks › Ruff has the executable path, a configuration directory (or file), the provider preference, enabled languages, run-on-save, and Reformat with Ruff. Formatting runs on the unsaved document through stdin (with Ruff's --range for selections, in 1-based code-point columns) and never writes the file first. The configuration directory is shared with Ruff LSP, which can then start without a root marker. Tools that can't format a language are rejected even when requested. Co-authored-by: muutot <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
Цей коміт міститься в:
24 files changed
+643
-69
No files matched your search
@@ -3489,12 +3489,14 @@ export type ExtensionCount = {
|
||||
};
|
||||
|
||||
/** A formatter that runs outside the language servers, with its own settings group. */
|
||||
export type ExternalFormatter = "prettier" | "oxfmt";
|
||||
export type ExternalFormatter = "prettier" | "oxfmt" | "ruff";
|
||||
|
||||
/** An external formatter (Prettier, oxfmt) and the languages it formats. */
|
||||
/** An external formatter (Prettier, oxfmt, Ruff) and the languages it formats. */
|
||||
export type ExternalFormatterSettings = {
|
||||
/** Empty path: project node_modules, managed installation, then PATH. */
|
||||
/** Empty path: project node_modules (Prettier/oxfmt), managed installation, then PATH. */
|
||||
path?: string,
|
||||
/** Ruff only: directory containing its configuration, or a configuration file. */
|
||||
configurationDirectory?: string,
|
||||
languages?: string[],
|
||||
/** Prefer this formatter even when a language server offers formatting. */
|
||||
prefer?: boolean,
|
||||
@@ -4815,6 +4817,7 @@ export type LanguagesSettings = {
|
||||
typescript?: TypeScriptSettings,
|
||||
prettier?: ExternalFormatterSettings,
|
||||
oxfmt?: ExternalFormatterSettings,
|
||||
ruff?: ExternalFormatterSettings,
|
||||
java?: JavaSettings,
|
||||
ruby?: RubySettings,
|
||||
markdown?: MarkdownSettings,
|
||||
|
||||
@@ -124,12 +124,12 @@ function editorAction(
|
||||
return { id, title, category, when: EDITOR_WHEN, run: withEditor(run), ...extra };
|
||||
}
|
||||
|
||||
/** Whether saving a `language` file formats it: Format on save, or Run on save of Prettier or oxfmt. */
|
||||
/** Whether saving a `language` file formats it: Format on save, or Run on save of an external formatter. */
|
||||
export function formatsOnSave(settings: Settings | null, language: string): boolean {
|
||||
const { prettier, oxfmt } = validateLanguages(settings?.languages);
|
||||
const { prettier, oxfmt, ruff } = validateLanguages(settings?.languages);
|
||||
return (
|
||||
!!settings?.editor?.formatOnSave ||
|
||||
[prettier, oxfmt].some((formatter) => formatter.runOnSave && formatter.languages.includes(language))
|
||||
[prettier, oxfmt, ruff].some((formatter) => formatter.runOnSave && formatter.languages.includes(language))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -206,6 +206,13 @@ export function registerLspActions(): () => void {
|
||||
(v) => formatDocument(v, { explicit: true, tool: "oxfmt" }),
|
||||
{ keywords: ["format", "oxfmt", "oxc"] },
|
||||
),
|
||||
editorAction(
|
||||
"code.reformatWithRuff",
|
||||
"Reformat with Ruff",
|
||||
"Code",
|
||||
(v) => formatDocument(v, { explicit: true, tool: "ruff" }),
|
||||
{ keywords: ["format", "ruff", "python"] },
|
||||
),
|
||||
editorAction("code.reformatDialog", "Reformat File…", "Code", (v) =>
|
||||
reformatDialog(v, (view) => applyKindSilently(view, "source.organizeImports")),
|
||||
),
|
||||
|
||||
@@ -177,6 +177,8 @@ describe("format on save provider isolation", () => {
|
||||
expect(formatsOnSave(oxfmt, "typescript")).toBe(true);
|
||||
expect(formatsOnSave(oxfmt, "markdown")).toBe(false);
|
||||
expect(formatsOnSave(settings({ prettier: { runOnSave: true } }), "markdown")).toBe(true);
|
||||
expect(formatsOnSave(settings({ ruff: { runOnSave: true } }), "python")).toBe(true);
|
||||
expect(formatsOnSave(settings({ ruff: { runOnSave: true } }), "typescript")).toBe(false);
|
||||
expect(formatsOnSave(settings({}), "typescript")).toBe(false);
|
||||
expect(formatsOnSave(settings({}, true), "rust")).toBe(true);
|
||||
expect(formatsOnSave(null, "typescript")).toBe(false);
|
||||
|
||||
@@ -136,20 +136,26 @@ async function format(view: EditorView, opts: FormatDocumentOptions): Promise<Fo
|
||||
commands.lspFormat(doc.doc, range, { ...options, onSave: opts.onSave, tool: opts.tool }),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof IpcError && e.data?.type === "tool" && (e.data.tool === "prettier" || e.data.tool === "oxfmt")) {
|
||||
const formatter =
|
||||
e instanceof IpcError && e.data?.type === "tool"
|
||||
? {
|
||||
prettier: { failed: "Prettier formatting failed", timedOut: "Prettier formatting timed out" },
|
||||
oxfmt: { failed: "oxfmt formatting failed", timedOut: "oxfmt formatting timed out" },
|
||||
ruff: { failed: "Ruff formatting failed", timedOut: "Ruff formatting timed out" },
|
||||
}[e.data.tool]
|
||||
: undefined;
|
||||
if (e instanceof IpcError && e.data?.type === "tool" && formatter) {
|
||||
const tool = e.data.tool;
|
||||
if (e.kind === "notFound" && e.data.missing) {
|
||||
await executeAction("tools.promptInstall", { name: tool, sticky: !opts.onSave });
|
||||
await executeAction("tools.promptInstall", { name: tool, sticky: !opts.onSave, formatter: true });
|
||||
} else if (e.kind !== "cancelled") {
|
||||
notifications().notify({
|
||||
group: "tools",
|
||||
level: "error",
|
||||
title: tool === "oxfmt" ? tx("oxfmt formatting failed") : tx("Prettier formatting failed"),
|
||||
title: tx(formatter.failed),
|
||||
body:
|
||||
e.kind === "timedOut"
|
||||
? tool === "oxfmt"
|
||||
? tx("oxfmt formatting timed out")
|
||||
: tx("Prettier formatting timed out")
|
||||
? tx(formatter.timedOut)
|
||||
: createElement("span", { className: styles.diagnostics }, e.message),
|
||||
key: `${tool}-format-error:${doc.path}`,
|
||||
sticky: false,
|
||||
|
||||
@@ -44,9 +44,9 @@ afterEach(() => {
|
||||
_resetI18nForTests();
|
||||
});
|
||||
|
||||
describe.each(["prettier", "oxfmt"])("%s formatter errors", (name) => {
|
||||
describe.each(["prettier", "oxfmt", "ruff"])("%s formatter errors", (name) => {
|
||||
const tool = { type: "tool", tool: name, missing: false } as const;
|
||||
const display = name === "prettier" ? "Prettier" : name;
|
||||
const display = name === "prettier" ? "Prettier" : name === "ruff" ? "Ruff" : name;
|
||||
it("uses structured missing-tool information to request the shared install prompt", async () => {
|
||||
const prompt = vi.fn();
|
||||
const dispose = registerAction({
|
||||
@@ -57,9 +57,9 @@ describe.each(["prettier", "oxfmt"])("%s formatter errors", (name) => {
|
||||
try {
|
||||
failWith({ kind: "notFound", message: "unrelated diagnostic", data: { ...tool, missing: true } });
|
||||
await formatDocument(viewFor());
|
||||
expect(prompt).toHaveBeenCalledWith({ name, sticky: true });
|
||||
expect(prompt).toHaveBeenCalledWith({ name, sticky: true, formatter: true });
|
||||
await formatDocument(viewFor(), { explicit: false, onSave: true });
|
||||
expect(prompt).toHaveBeenLastCalledWith({ name, sticky: false });
|
||||
expect(prompt).toHaveBeenLastCalledWith({ name, sticky: false, formatter: true });
|
||||
expect(useNotifications.getState().items).toHaveLength(0);
|
||||
} finally {
|
||||
dispose();
|
||||
@@ -126,10 +126,11 @@ describe.each(["prettier", "oxfmt"])("%s formatter errors", (name) => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Reformat with Prettier / oxfmt", () => {
|
||||
describe("Reformat with an external formatter", () => {
|
||||
it.each([
|
||||
["code.reformatWithPrettier", "prettier"],
|
||||
["code.reformatWithOxfmt", "oxfmt"],
|
||||
["code.reformatWithRuff", "ruff"],
|
||||
])("%s asks the backend for %s whatever the settings select", async (id, tool) => {
|
||||
vi.spyOn(lsp, "lspRequest").mockImplementation(async (request) => {
|
||||
await request();
|
||||
|
||||
@@ -92,7 +92,13 @@ describe("languages settings", () => {
|
||||
validateLanguages({
|
||||
prettier: { path: " ./bin/prettier ", languages: ["markdown", 4, "markdown"], prefer: true, runOnSave: true },
|
||||
}).prettier,
|
||||
).toEqual({ path: "./bin/prettier", languages: ["markdown"], prefer: true, runOnSave: true });
|
||||
).toEqual({
|
||||
path: "./bin/prettier",
|
||||
configurationDirectory: "",
|
||||
languages: ["markdown"],
|
||||
prefer: true,
|
||||
runOnSave: true,
|
||||
});
|
||||
expect(validateLanguages({ prettier: { languages: [] } }).prettier.languages).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -102,10 +108,43 @@ describe("languages settings", () => {
|
||||
prettier: { runOnSave: true },
|
||||
oxfmt: { path: " ./bin/oxfmt ", languages: ["typescript", null, "typescript"], prefer: true },
|
||||
});
|
||||
expect(v.oxfmt).toEqual({ path: "./bin/oxfmt", languages: ["typescript"], prefer: true, runOnSave: false });
|
||||
expect(v.oxfmt).toEqual({
|
||||
path: "./bin/oxfmt",
|
||||
configurationDirectory: "",
|
||||
languages: ["typescript"],
|
||||
prefer: true,
|
||||
runOnSave: false,
|
||||
});
|
||||
expect(v.prettier).toMatchObject({ runOnSave: true, prefer: false, path: "" });
|
||||
});
|
||||
|
||||
it("enables Ruff for Python by default and keeps its settings separate", () => {
|
||||
expect(validateLanguages({}).ruff).toMatchObject({
|
||||
path: "",
|
||||
configurationDirectory: "",
|
||||
languages: ["python"],
|
||||
prefer: false,
|
||||
runOnSave: false,
|
||||
});
|
||||
const v = validateLanguages({
|
||||
ruff: {
|
||||
path: " ./bin/ruff ",
|
||||
configurationDirectory: " ./config/ruff ",
|
||||
languages: ["python", null, "python", "pyi"],
|
||||
prefer: true,
|
||||
runOnSave: true,
|
||||
},
|
||||
});
|
||||
expect(v.ruff).toEqual({
|
||||
path: "./bin/ruff",
|
||||
configurationDirectory: "./config/ruff",
|
||||
languages: ["python", "pyi"],
|
||||
prefer: true,
|
||||
runOnSave: true,
|
||||
});
|
||||
expect(v.prettier).toEqual(DEFAULT_LANGUAGES.prettier);
|
||||
});
|
||||
|
||||
it("leaves the Markdown preview font size unset (editor font) and keeps a set one in 8..72", () => {
|
||||
expect(validateLanguages({}).markdown).toEqual({ previewFontSize: null });
|
||||
expect(validateLanguages({ markdown: { previewFontSize: 90 } }).markdown).toEqual({ previewFontSize: 72 });
|
||||
@@ -143,6 +182,7 @@ describe("language pages", () => {
|
||||
"PHP",
|
||||
"Prettier",
|
||||
"oxfmt",
|
||||
"Ruff",
|
||||
"Ruby",
|
||||
"Rust",
|
||||
"TypeScript",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useId } from "react";
|
||||
import type { ExternalFormatter } from "@/bindings";
|
||||
import { Input } from "@/ui";
|
||||
import { IconButton, Input } from "@/ui";
|
||||
import { Row, Section, Select, Toggle } from "../components/Form";
|
||||
import { useSetting } from "../components/context";
|
||||
import { DEFAULT_LANGUAGES } from "@/platform/languageSettings";
|
||||
import { choosePath } from "./api";
|
||||
import styles from "./Sdk.module.css";
|
||||
|
||||
const names: Record<string, string> = {
|
||||
markdown: "Markdown",
|
||||
@@ -20,17 +22,28 @@ const names: Record<string, string> = {
|
||||
json: "JSON",
|
||||
jsonc: "JSON with Comments",
|
||||
vue: "Vue",
|
||||
python: "Python",
|
||||
};
|
||||
|
||||
interface FormatterPageProps {
|
||||
tool: ExternalFormatter;
|
||||
name: string;
|
||||
pathLabel: string;
|
||||
pathHint?: string;
|
||||
configurationDirectory?: boolean;
|
||||
providerHint?: string;
|
||||
saveHint: string;
|
||||
}
|
||||
|
||||
function FormatterPage({ tool, name, pathLabel, providerHint, saveHint }: FormatterPageProps) {
|
||||
function FormatterPage({
|
||||
tool,
|
||||
name,
|
||||
pathLabel,
|
||||
pathHint = "Auto: nearest project node_modules, managed installation, then PATH. An explicit path overrides auto detection.",
|
||||
configurationDirectory = false,
|
||||
providerHint,
|
||||
saveHint,
|
||||
}: FormatterPageProps) {
|
||||
const id = useId();
|
||||
const [path, setPath] = useSetting<string>(`languages.${tool}.path`);
|
||||
const [languages, setLanguages] = useSetting<string[]>(`languages.${tool}.languages`);
|
||||
@@ -39,13 +52,10 @@ function FormatterPage({ tool, name, pathLabel, providerHint, saveHint }: Format
|
||||
return (
|
||||
<>
|
||||
<Section title={name}>
|
||||
<Row
|
||||
label={pathLabel}
|
||||
htmlFor={id}
|
||||
hint="Auto: nearest project node_modules, managed installation, then PATH. An explicit path overrides auto detection."
|
||||
>
|
||||
<Row label={pathLabel} htmlFor={id} hint={pathHint}>
|
||||
<Input id={id} mono value={path} placeholder="Auto" onChange={(e) => setPath(e.target.value)} />
|
||||
</Row>
|
||||
{configurationDirectory && <ConfigurationDirectory settingKey={`languages.${tool}.configurationDirectory`} />}
|
||||
<Row label="Formatting provider" hint={providerHint}>
|
||||
<Select
|
||||
value={prefer ? tool : "auto"}
|
||||
@@ -75,6 +85,43 @@ function FormatterPage({ tool, name, pathLabel, providerHint, saveHint }: Format
|
||||
);
|
||||
}
|
||||
|
||||
function ConfigurationDirectory({ settingKey }: { settingKey: string }) {
|
||||
const id = useId();
|
||||
const [path, setPath] = useSetting<string>(settingKey);
|
||||
return (
|
||||
<Row
|
||||
label="Configuration directory"
|
||||
htmlFor={id}
|
||||
hint="Empty uses the nearest project pyproject.toml ([tool.ruff]), ruff.toml, or .ruff.toml. A configured directory must contain one of them."
|
||||
>
|
||||
<span className={styles.pathRow}>
|
||||
<Input
|
||||
id={id}
|
||||
mono
|
||||
className={styles.pathInput}
|
||||
value={path}
|
||||
placeholder="Project configuration"
|
||||
onChange={(e) => setPath(e.target.value)}
|
||||
/>
|
||||
<IconButton
|
||||
icon="folder-open"
|
||||
size="small"
|
||||
title="Browse…"
|
||||
aria-label="Select Ruff Configuration Directory"
|
||||
onClick={async () => {
|
||||
const picked = await choosePath({
|
||||
directory: true,
|
||||
title: "Select Ruff Configuration Directory",
|
||||
defaultPath: path || undefined,
|
||||
});
|
||||
if (picked) setPath(picked);
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export function PrettierPage() {
|
||||
return (
|
||||
<FormatterPage
|
||||
@@ -97,3 +144,17 @@ export function OxfmtPage() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function RuffPage() {
|
||||
return (
|
||||
<FormatterPage
|
||||
tool="ruff"
|
||||
name="Ruff"
|
||||
pathLabel="Ruff path"
|
||||
pathHint="Auto: managed installation, then PATH. An explicit path overrides auto detection."
|
||||
configurationDirectory
|
||||
providerHint="Ruff reads the nearest pyproject.toml ([tool.ruff]), ruff.toml, or .ruff.toml. Use it before the language server when preferred."
|
||||
saveHint="Run Ruff on save for enabled languages when it is the selected formatting provider."
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import { GoPage, JavaPage, NodePage, PhpPage, RubyPage, RustPage, TypeScriptPage } from "../languages/LanguagePages";
|
||||
import { MarkdownPage } from "../languages/MarkdownPage";
|
||||
import { OxfmtPage, PrettierPage } from "../languages/FormatterPage";
|
||||
import { OxfmtPage, PrettierPage, RuffPage } from "../languages/FormatterPage";
|
||||
import { ProjectStructurePage } from "../languages/ProjectStructurePage";
|
||||
import { PythonInterpreterPage } from "../languages/PythonInterpreterPage";
|
||||
import { GoBuildTagsPage, GO_BUILD_TAG_SECTIONS } from "../languages/GoBuildTags";
|
||||
@@ -138,6 +138,24 @@ export const LANGUAGE_PAGES: PageSpec[] = [
|
||||
keywords: ["formatter", "oxfmt", "oxc", ".oxfmtrc.json", "format on save", "node_modules"],
|
||||
component: OxfmtPage,
|
||||
},
|
||||
{
|
||||
id: "languages.ruff",
|
||||
projectScope: true,
|
||||
defaultScope: "project",
|
||||
title: "Ruff",
|
||||
group: LANGUAGES_GROUP,
|
||||
order: 403.7,
|
||||
keywords: [
|
||||
"formatter",
|
||||
"ruff",
|
||||
"python",
|
||||
"configuration directory",
|
||||
"pyproject.toml",
|
||||
"ruff.toml",
|
||||
"format on save",
|
||||
],
|
||||
component: RuffPage,
|
||||
},
|
||||
{
|
||||
id: "languages.php",
|
||||
projectScope: true,
|
||||
|
||||
@@ -147,6 +147,25 @@ describe("missing language server prompt", () => {
|
||||
off();
|
||||
});
|
||||
|
||||
it("offers Ruff formatting even when another Python server is installed", async () => {
|
||||
const installed = sampleTools().find((t) => t.name === "basedpyright")!;
|
||||
backend.tools.push({
|
||||
...installed,
|
||||
name: "ruff",
|
||||
displayName: "Ruff",
|
||||
command: "ruff",
|
||||
installMethod: "uv (virtualenv)",
|
||||
languages: ["Python"],
|
||||
alternatives: [],
|
||||
});
|
||||
await promptInstall("ruff", true, new Set(), true);
|
||||
expect(notes.at(-1)).toMatchObject({
|
||||
title: "Ruff is not installed",
|
||||
body: "Reformat Code needs Ruff. Install it via uv (virtualenv)?",
|
||||
key: missingKey("ruff"),
|
||||
});
|
||||
});
|
||||
|
||||
it("stays silent when an installed alternative serves the same languages", async () => {
|
||||
const off = installMissingServerPrompts();
|
||||
// typescript-language-server (on PATH) covers vtsls' languages.
|
||||
|
||||
@@ -46,7 +46,12 @@ export function activate(): () => void {
|
||||
hidden: true,
|
||||
run: async ({ arg }) => {
|
||||
if (arg && typeof arg === "object" && "name" in arg && typeof arg.name === "string") {
|
||||
await promptInstall(arg.name, !("sticky" in arg) || arg.sticky !== false);
|
||||
await promptInstall(
|
||||
arg.name,
|
||||
!("sticky" in arg) || arg.sticky !== false,
|
||||
new Set(),
|
||||
"formatter" in arg && arg.formatter === true,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -21,12 +21,14 @@ export async function promptInstall(
|
||||
name: string,
|
||||
sticky = true,
|
||||
offered: ReadonlySet<string> = new Set(),
|
||||
formatter = false,
|
||||
): Promise<boolean> {
|
||||
if (!useTools.getState().loaded) await refreshTools();
|
||||
const tool = toolByName(name);
|
||||
if (!tool || !tool.canInstall || tool.status !== "missing") return false;
|
||||
const position = tool.alternatives.indexOf(name);
|
||||
if (position > 0 && tool.alternatives.slice(0, position).some((n) => offered.has(n))) return false;
|
||||
formatter ||= tool.kind === "formatter";
|
||||
// An installed alternative (typescript-language-server for vtsls) already serves these files.
|
||||
const served = useTools
|
||||
.getState()
|
||||
@@ -37,17 +39,16 @@ export async function promptInstall(
|
||||
t.status !== "missing" &&
|
||||
t.languages.some((l) => tool.languages.includes(l)),
|
||||
);
|
||||
if (tool.kind !== "formatter" && served) return false;
|
||||
if (!formatter && served) return false;
|
||||
const key = missingKey(name);
|
||||
const langs = tool.languages.length ? tool.languages.join(", ") : "these files";
|
||||
notifications().notify({
|
||||
group: "tools",
|
||||
level: "warning",
|
||||
title: tx`${tool.displayName} is not installed`,
|
||||
body:
|
||||
tool.kind === "formatter"
|
||||
? tx`Reformat Code needs ${tool.displayName}. Install it via ${tool.installMethod}?`
|
||||
: tx`Code insight for ${langs} needs ${tool.command}. Install it via ${tool.installMethod}?`,
|
||||
body: formatter
|
||||
? tx`Reformat Code needs ${tool.displayName}. Install it via ${tool.installMethod}?`
|
||||
: tx`Code insight for ${langs} needs ${tool.command}. Install it via ${tool.installMethod}?`,
|
||||
key,
|
||||
sticky,
|
||||
actions: [
|
||||
|
||||
@@ -25,9 +25,10 @@ const DEFAULT_GO_BUILD_TAGS: GoBuildTags = {
|
||||
experiments: "",
|
||||
};
|
||||
|
||||
/** An external formatter (Prettier, oxfmt) and the languages it formats. */
|
||||
/** An external formatter (Prettier, oxfmt, Ruff) and the languages it formats. */
|
||||
interface ExternalFormatterValid {
|
||||
path: string;
|
||||
configurationDirectory: string;
|
||||
languages: string[];
|
||||
prefer: boolean;
|
||||
runOnSave: boolean;
|
||||
@@ -35,6 +36,7 @@ interface ExternalFormatterValid {
|
||||
|
||||
const DEFAULT_FORMATTER: ExternalFormatterValid = {
|
||||
path: "",
|
||||
configurationDirectory: "",
|
||||
languages: [
|
||||
"markdown",
|
||||
"yaml",
|
||||
@@ -55,6 +57,11 @@ const DEFAULT_FORMATTER: ExternalFormatterValid = {
|
||||
runOnSave: false,
|
||||
};
|
||||
|
||||
const DEFAULT_RUFF_FORMATTER: ExternalFormatterValid = {
|
||||
...DEFAULT_FORMATTER,
|
||||
languages: ["python"],
|
||||
};
|
||||
|
||||
/** `languages.php.languageLevel` values; `""` = Auto (from `composer.json`). */
|
||||
export const PHP_LANGUAGE_LEVELS = [
|
||||
"",
|
||||
@@ -99,6 +106,7 @@ export interface LanguagesValid {
|
||||
typescript: { version: string };
|
||||
prettier: ExternalFormatterValid;
|
||||
oxfmt: ExternalFormatterValid;
|
||||
ruff: ExternalFormatterValid;
|
||||
java: {
|
||||
jdk: string;
|
||||
jdtlsJdk: string;
|
||||
@@ -159,6 +167,7 @@ export const DEFAULT_LANGUAGES: LanguagesValid = {
|
||||
typescript: { version: "bundled" },
|
||||
prettier: DEFAULT_FORMATTER,
|
||||
oxfmt: DEFAULT_FORMATTER,
|
||||
ruff: DEFAULT_RUFF_FORMATTER,
|
||||
java: {
|
||||
jdk: "",
|
||||
jdtlsJdk: "",
|
||||
@@ -228,11 +237,11 @@ const folders = (v: unknown): string[] =>
|
||||
const folderKey = (stored: string) => (stored === "." ? "" : stored);
|
||||
const storedFolder = (rel: string) => normalizeFolder(rel) || ".";
|
||||
|
||||
function validateFormatter(raw: unknown): ExternalFormatterValid {
|
||||
const d = DEFAULT_FORMATTER;
|
||||
function validateFormatter(raw: unknown, d = DEFAULT_FORMATTER): ExternalFormatterValid {
|
||||
const f = obj(raw);
|
||||
return {
|
||||
path: text(f.path, d.path),
|
||||
configurationDirectory: text(f.configurationDirectory, d.configurationDirectory),
|
||||
languages: Array.isArray(f.languages)
|
||||
? [...new Set(f.languages.filter((v): v is string => typeof v === "string"))]
|
||||
: d.languages,
|
||||
@@ -299,6 +308,7 @@ export function validateLanguages(raw: unknown): LanguagesValid {
|
||||
typescript: { version: text(ts.version, d.typescript.version) || d.typescript.version },
|
||||
prettier: validateFormatter(l.prettier),
|
||||
oxfmt: validateFormatter(l.oxfmt),
|
||||
ruff: validateFormatter(l.ruff, DEFAULT_RUFF_FORMATTER),
|
||||
java: {
|
||||
jdk: text(java.jdk, d.java.jdk),
|
||||
jdtlsJdk: text(java.jdtlsJdk, d.java.jdtlsJdk),
|
||||
|
||||
@@ -13,6 +13,19 @@
|
||||
"phpstan.neon, phpstan.neon.dist or phpstan.dist.neon": "phpstan.neon, phpstan.neon.dist or phpstan.dist.neon",
|
||||
"The configuration vendor/bin/phpstan checks saved files with. Empty uses phpstan.neon, phpstan.neon.dist or phpstan.dist.neon in the project root. Absolute, or relative to the project.": "The configuration vendor/bin/phpstan checks saved files with. Empty uses phpstan.neon, phpstan.neon.dist or phpstan.dist.neon in the project root. Absolute, or relative to the project.",
|
||||
"Select PHPStan Configuration File": "Select PHPStan Configuration File",
|
||||
"Reformat with Ruff": "Reformat with Ruff",
|
||||
"Ruff path": "Ruff path",
|
||||
"Auto: managed installation, then PATH. An explicit path overrides auto detection.": "Auto: managed installation, then PATH. An explicit path overrides auto detection.",
|
||||
"Configuration directory": "Configuration directory",
|
||||
"Project configuration": "Project configuration",
|
||||
"Select Ruff Configuration Directory": "Select Ruff Configuration Directory",
|
||||
"Empty uses the nearest project pyproject.toml ([tool.ruff]), ruff.toml, or .ruff.toml. A configured directory must contain one of them.": "Empty uses the nearest project pyproject.toml ([tool.ruff]), ruff.toml, or .ruff.toml. A configured directory must contain one of them.",
|
||||
"Ruff reads the nearest pyproject.toml ([tool.ruff]), ruff.toml, or .ruff.toml. Use it before the language server when preferred.": "Ruff reads the nearest pyproject.toml ([tool.ruff]), ruff.toml, or .ruff.toml. Use it before the language server when preferred.",
|
||||
"Run Ruff on save for enabled languages when it is the selected formatting provider.": "Run Ruff on save for enabled languages when it is the selected formatting provider.",
|
||||
"Ruff formatting failed": "Ruff formatting failed",
|
||||
"Ruff formatting timed out": "Ruff formatting timed out",
|
||||
"Ruff configuration path does not exist: {path}": "Ruff configuration path does not exist: {path}",
|
||||
"Ruff configuration directory has no ruff.toml, .ruff.toml, or pyproject.toml with [tool.ruff]: {path}": "Ruff configuration directory has no ruff.toml, .ruff.toml, or pyproject.toml with [tool.ruff]: {path}",
|
||||
"Show lines added and deleted": "Show lines added and deleted",
|
||||
"{added} line added": "{added} line added",
|
||||
"{added} lines added": "{added} lines added",
|
||||
|
||||
@@ -1714,15 +1714,24 @@
|
||||
"Reformat Code": "重新格式化代码",
|
||||
"Reformat with Prettier": "使用 Prettier 重新格式化",
|
||||
"Reformat with oxfmt": "使用 oxfmt 重新格式化",
|
||||
"Reformat with Ruff": "使用 Ruff 重新格式化",
|
||||
"Prettier path": "Prettier 路径",
|
||||
"oxfmt path": "oxfmt 路径",
|
||||
"Ruff path": "Ruff 路径",
|
||||
"Auto: nearest project node_modules, managed installation, then PATH. An explicit path overrides auto detection.": "自动:依次查找最近的项目 node_modules、受管安装和 PATH。指定路径将覆盖自动检测。",
|
||||
"Auto: managed installation, then PATH. An explicit path overrides auto detection.": "自动:依次查找受管安装和 PATH。指定路径将覆盖自动检测。",
|
||||
"Configuration directory": "配置目录",
|
||||
"Project configuration": "项目配置",
|
||||
"Select Ruff Configuration Directory": "选择 Ruff 配置目录",
|
||||
"Empty uses the nearest project pyproject.toml ([tool.ruff]), ruff.toml, or .ruff.toml. A configured directory must contain one of them.": "留空时使用最近的 pyproject.toml([tool.ruff])、ruff.toml 或 .ruff.toml。指定目录必须包含其中一种配置。",
|
||||
"Formatting provider": "格式化工具",
|
||||
"Automatic (language server first)": "自动(优先语言服务器)",
|
||||
"Run on save": "保存时运行",
|
||||
"Run Prettier on save for enabled languages when it is the selected formatting provider.": "对已启用的语言,在选择 Prettier 作为格式化工具时于保存时运行。",
|
||||
"Run oxfmt on save for enabled languages when it is the selected formatting provider.": "对已启用的语言,在选择 oxfmt 作为格式化工具时于保存时运行。",
|
||||
"Run Ruff on save for enabled languages when it is the selected formatting provider.": "对已启用的语言,在选择 Ruff 作为格式化工具时于保存时运行。",
|
||||
"oxfmt formats an enabled language instead of Prettier below an oxfmt configuration file (.oxfmtrc.json, oxfmt.config.ts) or when it is the selected provider.": "在 oxfmt 配置文件(.oxfmtrc.json、oxfmt.config.ts)所在目录及其子目录中,或选择 oxfmt 作为格式化工具时,oxfmt 将代替 Prettier 格式化已启用的语言。",
|
||||
"Ruff reads the nearest pyproject.toml ([tool.ruff]), ruff.toml, or .ruff.toml. Use it before the language server when preferred.": "Ruff 会读取最近的 pyproject.toml([tool.ruff])、ruff.toml 或 .ruff.toml。选择优先使用 Ruff 时,将优先于语言服务器。",
|
||||
"Enabled languages": "启用的语言",
|
||||
"Reformat Code needs {name}. Install it via {method}?": "重新格式化代码需要 {name}。是否通过 {method} 安装?",
|
||||
"Install Missing Tool": "安装缺失的工具",
|
||||
@@ -1730,6 +1739,10 @@
|
||||
"Prettier formatting timed out": "Prettier 格式化超时",
|
||||
"oxfmt formatting failed": "oxfmt 格式化失败",
|
||||
"oxfmt formatting timed out": "oxfmt 格式化超时",
|
||||
"Ruff formatting failed": "Ruff 格式化失败",
|
||||
"Ruff formatting timed out": "Ruff 格式化超时",
|
||||
"Ruff configuration path does not exist: {path}": "Ruff 配置路径不存在:{path}",
|
||||
"Ruff configuration directory has no ruff.toml, .ruff.toml, or pyproject.toml with [tool.ruff]: {path}": "Ruff 配置目录中没有 ruff.toml、.ruff.toml 或包含 [tool.ruff] 的 pyproject.toml:{path}",
|
||||
"{name} does not support selection formatting for this language; formatted the whole file": "{name} 不支持此语言的选区格式化,已格式化整个文件",
|
||||
"Formatting timed out": "格式化超时",
|
||||
"{name} executable": "{name} 可执行文件",
|
||||
|
||||
@@ -91,6 +91,7 @@ const UNTRANSLATED = new Set<string>([
|
||||
"CMake",
|
||||
"Prettier",
|
||||
"oxfmt",
|
||||
"Ruff",
|
||||
"IntelliJ IDEA",
|
||||
"PHPStan",
|
||||
// Go environment variables and placeholder values
|
||||
|
||||
Reference in new issue
Заблокувати користувача