Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/opencode/src/lsp/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const LANGUAGE_EXTENSIONS: Record<string, string> = {
".cc": "cpp",
".c++": "cpp",
".cs": "csharp",
".csx": "csharp",
".css": "css",
".d": "d",
".pas": "pascal",
Expand Down
156 changes: 132 additions & 24 deletions packages/opencode/src/lsp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,31 +703,10 @@ export const Zls: Info = {
export const CSharp: Info = {
id: "csharp",
root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]),
extensions: [".cs"],
extensions: [".cs", ".csx"],
async spawn(root) {
let bin = which("roslyn-language-server")
if (!bin) {
if (!which("dotnet")) {
log.error(".NET SDK is required to install roslyn-language-server")
return
}

if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
log.info("installing roslyn-language-server via dotnet tool")
const proc = Process.spawn(["dotnet", "tool", "install", "--global", "roslyn-language-server", "--prerelease"], {
stdout: "pipe",
stderr: "pipe",
stdin: "pipe",
})
const exit = await proc.exited
if (exit !== 0) {
log.error("Failed to install roslyn-language-server")
return
}

bin = path.join(Global.Path.bin, "roslyn-language-server" + (process.platform === "win32" ? ".exe" : ""))
log.info(`installed roslyn-language-server`, { bin })
}
const bin = await getRoslynLanguageServer()
if (!bin) return

return {
process: spawn(bin, ["--stdio", "--autoLoadProjects"], {
Expand All @@ -737,6 +716,135 @@ export const CSharp: Info = {
},
}

export const Razor: Info = {
id: "razor",
root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]),
extensions: [".razor", ".cshtml"],
async spawn(root) {
const bin = await getRoslynLanguageServer()
if (!bin) return

const razor = await findVscodeRazorExtension()
if (!razor) {
log.info("VS Code C# extension with Razor support not found, skipping Razor LSP")
return
}

log.info("using VS Code Razor extension for roslyn-language-server", { extension: razor.extension })
return {
process: spawn(
bin,
[
"--stdio",
"--autoLoadProjects",
`--razorSourceGenerator=${razor.compiler}`,
`--razorDesignTimePath=${razor.targets}`,
"--extension",
razor.extension,
],
{
cwd: root,
},
),
}
},
}

let roslynLanguageServerInstall: Promise<string | undefined> | undefined

async function getRoslynLanguageServer() {
const existing = which("roslyn-language-server")
if (existing) return existing

const global = await roslynLanguageServerGlobalPath()
if (global) return global

roslynLanguageServerInstall ||= installRoslynLanguageServer().finally(() => {
roslynLanguageServerInstall = undefined
})
return roslynLanguageServerInstall
}

async function installRoslynLanguageServer() {
if (!which("dotnet")) {
log.error(".NET SDK is required to install roslyn-language-server")
return
}

if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
log.info("installing roslyn-language-server via dotnet tool")
const proc = Process.spawn(["dotnet", "tool", "install", "--global", "roslyn-language-server", "--prerelease"], {
stdout: "pipe",
Comment thread
Hona marked this conversation as resolved.
stderr: "pipe",
stdin: "pipe",
})
Comment thread
Hona marked this conversation as resolved.
const exit = await proc.exited
if (exit !== 0) {
log.error("Failed to install roslyn-language-server")
return
}

const resolved = which("roslyn-language-server")
if (resolved) {
log.info(`installed roslyn-language-server`, { bin: resolved })
return resolved
}

const global = await roslynLanguageServerGlobalPath()
if (global) {
log.info(`installed roslyn-language-server`, { bin: global })
return global
}

log.error("Installed roslyn-language-server but could not resolve executable")
}

async function roslynLanguageServerGlobalPath() {
const bin = path.join(
process.env.DOTNET_CLI_HOME ?? os.homedir(),
".dotnet",
"tools",
"roslyn-language-server" + (process.platform === "win32" ? ".cmd" : ""),
)
return (await pathExists(bin)) ? bin : undefined
}

async function findVscodeRazorExtension() {
const roots = [
process.env.VSCODE_EXTENSIONS,
path.join(os.homedir(), ".vscode", "extensions"),
path.join(os.homedir(), ".vscode-insiders", "extensions"),
path.join(os.homedir(), ".vscode-server", "extensions"),
path.join(os.homedir(), ".vscode-server-insiders", "extensions"),
].filter((item) => item !== undefined)

for (const root of [...new Set(roots)]) {
const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => [])
const candidates = await Promise.all(
entries
.filter((entry) => entry.isDirectory() && entry.name.startsWith("ms-dotnettools.csharp-"))
.map(async (entry) => ({
path: path.join(root, entry.name, ".razorExtension"),
modified: (await fs.stat(path.join(root, entry.name)).catch(() => undefined))?.mtimeMs ?? 0,
})),
)
for (const entry of candidates.sort((a, b) => b.modified - a.modified).map((candidate) => candidate.path)) {
const result = {
compiler: path.join(entry, "Microsoft.CodeAnalysis.Razor.Compiler.dll"),
targets: path.join(entry, "Targets", "Microsoft.NET.Sdk.Razor.DesignTime.targets"),
extension: path.join(entry, "Microsoft.VisualStudioCode.RazorExtension.dll"),
}
if (
(await pathExists(result.compiler)) &&
(await pathExists(result.targets)) &&
(await pathExists(result.extension))
) {
return result
}
}
}
}

export const FSharp: Info = {
id: "fsharp",
root: NearestRoot([".slnx", ".sln", ".fsproj", "global.json"]),
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/content/docs/lsp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ OpenCode comes with several built-in LSP servers for popular languages:
| astro | .astro | Auto-installs for Astro projects |
| bash | .sh, .bash, .zsh, .ksh | Auto-installs bash-language-server |
| clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Auto-installs for C/C++ projects |
| csharp | .cs | `.NET SDK` installed |
| csharp | .cs, .csx | `.NET SDK` installed |
| clojure-lsp | .clj, .cljs, .cljc, .edn | `clojure-lsp` command available |
| dart | .dart | `dart` command available |
| deno | .ts, .tsx, .js, .jsx, .mjs | `deno` command available (auto-detects deno.json/deno.jsonc) |
Expand All @@ -36,6 +36,7 @@ OpenCode comes with several built-in LSP servers for popular languages:
| php intelephense | .php | Auto-installs for PHP projects |
| prisma | .prisma | `prisma` command available |
| pyright | .py, .pyi | `pyright` dependency installed |
| razor | .razor, .cshtml | `.NET SDK` and VS Code C# extension installed |
| ruby-lsp (rubocop) | .rb, .rake, .gemspec, .ru | `ruby` and `gem` commands available |
| rust | .rs | `rust-analyzer` command available |
| sourcekit-lsp | .swift, .objc, .objcpp | `swift` installed (`xcode` on macOS) |
Expand Down
Loading