diff --git a/packages/opencode/src/bun/index.ts b/packages/opencode/src/bun/index.ts index 5f184727..2a8b48ef 100644 --- a/packages/opencode/src/bun/index.ts +++ b/packages/opencode/src/bun/index.ts @@ -8,7 +8,10 @@ import { readableStreamToText } from "bun" export namespace BunProc { const log = Log.create({ service: "bun" }) - export async function run(cmd: string[], options?: Bun.SpawnOptions.OptionsObject) { + export async function run( + cmd: string[], + options?: Bun.SpawnOptions.OptionsObject, + ) { log.info("running", { cmd: [which(), ...cmd], ...options, diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index baa39906..a8b8e9de 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -1,3 +1,4 @@ +import { readableStreamToText } from "bun" import { BunProc } from "../bun" import { Instance } from "../project/instance" import { Filesystem } from "../util/filesystem" @@ -131,7 +132,21 @@ export const zig: Info = { export const clang: Info = { name: "clang-format", command: ["clang-format", "-i", "$FILE"], - extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"], + extensions: [ + ".c", + ".cc", + ".cpp", + ".cxx", + ".c++", + ".h", + ".hh", + ".hpp", + ".hxx", + ".h++", + ".ino", + ".C", + ".H", + ], async enabled() { const items = await Filesystem.findUp(".clang-format", Instance.directory, Instance.worktree) return items.length > 0 @@ -177,6 +192,33 @@ export const ruff: Info = { }, } +export const rlang: Info = { + name: "air", + command: ["air", "format", "$FILE"], + extensions: [".R"], + async enabled() { + const airPath = Bun.which("air") + if (airPath == null) return false + + try { + const proc = Bun.spawn(["air", "--help"], { + stdout: "pipe", + stderr: "pipe", + }) + await proc.exited + const output = await readableStreamToText(proc.stdout) + + // Check for "Air: An R language server and formatter" + const firstLine = output.split("\n")[0] + const hasR = firstLine.includes("R language") + const hasFormatter = firstLine.includes("formatter") + return hasR && hasFormatter + } catch (error) { + return false + } + }, +} + export const uvformat: Info = { name: "uv format", command: ["uv", "format", "--", "$FILE"], diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index b4294093..e307496c 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -69,6 +69,7 @@ export namespace Format { log.info("checking", { name: item.name, ext }) if (!item.extensions.includes(ext)) continue if (!(await isEnabled(item))) continue + log.info("enabled", { name: item.name, ext }) result.push(item) } return result diff --git a/packages/web/src/content/docs/formatters.mdx b/packages/web/src/content/docs/formatters.mdx index 4b46a4f6..9fc41a53 100644 --- a/packages/web/src/content/docs/formatters.mdx +++ b/packages/web/src/content/docs/formatters.mdx @@ -25,6 +25,7 @@ OpenCode comes with several built-in formatters for popular languages and framew | rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available | | standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available | | htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available | +| air | .R | `air` command available | So if your project has `prettier` in your `package.json`, OpenCode will automatically use it.