Model or dataset
razzmatazz/csharp-language-server avatar
razzmatazz/csharp-language-server

csharp-ls: a Roslyn based language server for C#, written in F#

Roslyn-based LSP server for C#

1,005 stars73 forksF#MIT

At a glance

What is it?
csharp-ls is a language server that gives any editor completion, diagnostics and refactoring for C# by driving Roslyn over the LSP protocol, and it is written in F# rather than C#. The interesting design choices are that every expensive capability is off by default, and that the server can hand your editor virtual URIs for decompiled and source generated code instead of temporary files.
Who is it for?
Adopt csharp-ls if you edit C# in an editor without a first class C# language service, or if you want Roslyn's completion, diagnostics and refactoring without the weight of a full IDE extension, and if you are willing to install the .NET 10 SDK.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly F#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the server gives you, and which projects it can load

csharp-ls brings advanced language features like code completion, diagnostics and refactoring to your editor for C# projects, and the README also notes that it supports projects targeting older .NET SDK versions, including .NET Core 3 and .NET Framework 4.8, and potentially earlier ones. That combination is the interesting part, because the installation requires the .NET 10 SDK or later while the server can still analyse a project that targets a much older runtime. The implementation is Roslyn's, and the acknowledgements say it plainly, that csharp-ls uses Roslyn to parse and update code, uses the LSP interface from the Ionide.LanguageServerProtocol project, and uses ILSpy's decompiler to turn types in assemblies back into C# source. The project is written in F#, and it is careful to state that it is not affiliated with Microsoft Corp., which matters when you are deciding whether an editor integration is a first party experience or a community one. The licence is MIT and the README is explicit that it is provided with no warranty of any kind.

Installing it is one command, and the SDK requirement is real

The quick start is short. You install the .NET 10 SDK or later, then install the server as a dotnet tool, and your editor's LSP client should automatically detect and start `csharp-ls` when you open C# project files.

console
dotnet tool install --global csharp-ls

The distribution channel is worth noting, since the package is published on NuGet as `csharp-ls` and installed globally as a tool, which means the server is a normal .NET application on your machine rather than something bundled in your editor. That is why the SDK requirement bites. Your editor does not need to know about Roslyn or the .NET SDK, but the server does, and it needs a modern SDK to start even when the project you are opening targets .NET Framework 4.8. For a team with mixed target frameworks, that combination is what makes a single server usable across projects, and it is the reason the older target support is called out separately in the description rather than left as an accident.

Every expensive capability is off by default

The settings are read from the `csharp` workspace configuration section and can also be provided through `workspace/didChangeConfiguration`, and the defaults are the most interesting thing about them. `csharp.logLevel` controls the server output forwarded via `$/logTrace`, with levels from trace to error and a default of info. `csharp.applyFormattingOptions` uses the formatting options supplied by the client, which may override .editorconfig values, and defaults to false. `csharp.analyzersEnabled` runs Roslyn analyzers, including IDE style rules and third party NuGet analyzers, as part of diagnostics, and the README notes that this increases diagnostic latency and CPU usage, so it defaults to false. `csharp.useMetadataUris` serves decompiled metadata and source generated documents under `csharp:/` URIs, and defaults to false. `csharp.razorSupport` enables Razor document support, also off by default. The rest are `csharp.solutionPathOverride` to pick a solution when several exist in the workspace, `csharp.locale` to force the output language, and two debug settings, one for the delay before the solution loads and one that logs periodic request queue statistics. The pattern is consistent: features that cost CPU or need special client support are opt-in, and the ones that make the server predictable are on.

Two logging channels, and a command line for the hard cases

The command line is where the server exposes its internals, and the usage block is short enough to be worth reading in full. The flags are `--help`, `--version`, `--loglevel` with a level, `--solution` to specify a .sln file relative to the current directory, `--locale` to force the output locale and override `DOTNET_CLI_UI_LANGUAGE`, `--debug`, `--diagnose` to run diagnostics, `--features` for optional features given as a comma separated list of metadata-uris and razor-support, and `--rpclog` to write an RPC wire log to a file with read, write and error channels. The environment variable `DOTNET_CLI_UI_LANGUAGE` follows the standard dotnet CLI convention for the output locale and is overridden by the flag and the setting. The logging design has two independent channels, and the README is explicit that they are independent: `--loglevel` only controls stderr console output, while the server supports the LSP `$/setTrace` and `$/logTrace` notifications, forwarding its internal log output as trace notifications when a client sets tracing to messages or verbose. In Visual Studio Code, tracing is enabled through the `[langId].trace.server` setting, and the `--rpclog` option gives you the wire protocol itself when the LSP level is not enough.

Virtual URIs instead of temporary files

The extended client capabilities section describes the feature that is most likely to surprise you, and it is the one that has no equivalent in a plain language server. When it is enabled, `textDocument/definition` on a symbol in a compiled assembly or in a source generated file returns a virtual `csharp:/` URI instead of a temporary file path, and that URI can then be passed to a custom `csharp/metadata` request to retrieve the decompiled or generated source text. The URI forms are given explicitly, with decompiled symbols addressed as `csharp:/<project.csproj>/decompiled/<Symbol>.cs` and source generated files as `csharp:/<project.csproj>/generated/<HintName>`. The feature can be switched on three ways, through the `csharp.useMetadataUris` setting, through the `--features metadata-uris` command line flag, or by setting the `experimental.csharp.metadataUris` client capability to true. The reason it exists is worth understanding, because the alternative is ugly. Without it, the server has to materialise a temporary file on disk so that your editor can open it, which then has to be cleaned up, which sometimes does not happen, and the path your editor shows is meaningless. With virtual URIs, the document is addressed by what it is rather than by where it briefly existed.

Which clients need the extensions, and what the alternatives are

The README says csharp-ls implements the standard LSP protocol, but that some features require a non-standard implementation, and that this is where editor specific plugins can be helpful. It then names the notable clients and their plugins, a Neovim plugin for an extended LSP implementation, two Emacs routes through the general lsp-mode and a dedicated eglot-csharp, and a Visual Studio Code extension. The alternatives section lists OmniSharp and the C# Dev Kit for Visual Studio Code, and the honest reading is that those two remain the first thing to try if you are on Windows with Visual Studio tooling, since they are the supported path. csharp-ls earns its place with editors that speak LSP well and that have no first party C# support, and with the parts of the .NET ecosystem outside Visual Studio, where the community has historically relied on OmniSharp. The csharp-ls specific argument is that it is Roslyn based, current with the SDK, and it gives you the new language features rather than the older ones.

The engineering, and the AI assistance policy

The repository is a .NET solution with the usual central management files, `Directory.Build.props`, `Directory.Build.targets` and `Directory.Packages.props` for central package versioning, a `global.json` pinning the SDK, a `nuget.config`, a `csharp-language-server.sln`, and directories for `src/`, `tests/`, `docs/`, `plans/` and `nix/`, with a `flake.nix` and a `flake.lock` for Nix users, plus `.githooks/` and `.config/` directories. The documentation is unusually complete for a project of this size, with a changelog, a features document, a troubleshooting document, a contribution guide that covers build instructions and the codebase overview, and a codebase architecture document that covers the project structure, LSP message handling, request scheduling and the test infrastructure. The last document is the one to read first if you want to contribute. And then there is the AI assistance section, which states that the project was developed with substantial help from large language models, naming specific models, and says that source files carry an `Assisted-by:` header line per the Linux kernel coding assistants convention. That is a deliberate and rare choice, and it means a reader can tell at a glance which parts of the codebase were written with machine assistance.

Editorial conclusion

Adopt csharp-ls if you edit C# in an editor without a first class C# language service, or if you want Roslyn's completion, diagnostics and refactoring without the weight of a full IDE extension, and if you are willing to install the .NET 10 SDK. Do not adopt it in a workflow that already has OmniSharp or the C# Dev Kit working well for you, since the README lists both as alternatives, and note that a few features need a non-standard client, so a plain LSP client will not see them. Before you commit, decide which optional capabilities you want, because analyzers, metadata URIs and Razor support are all off by default and each has a stated cost or a client requirement. Also read the AI assistance note, since the project states it was developed with substantial help from large language models and that source files carry an Assisted-by header. The licence is MIT, the newest release is 0.28.0 from 2026-09-13, and the last push was the same day.

Frequently asked questions

How do I install csharp-ls?

Install the .NET 10 SDK or later, then run `dotnet tool install --global csharp-ls`. The package is published on NuGet as `csharp-ls`, and once it is installed your editor's LSP client should detect and start it when you open C# project files.

Which csharp-ls features are off by default?

Analyzers are off by default because they increase diagnostic latency and CPU usage, applying the client's formatting options is off, serving decompiled metadata and source generated documents under `csharp:/` URIs is off, and Razor support is off. They are enabled through the `csharp.*` settings, the `--features` command line flag, or, for metadata URIs, a client capability.

What are the csharp:/ URIs for?

With `csharp.useMetadataUris` enabled, a definition request on a symbol in a compiled assembly or a source generated file returns a virtual `csharp:/` URI instead of a temporary file path, and a custom `csharp/metadata` request retrieves the decompiled or generated source. The URIs look like `csharp:/<project.csproj>/decompiled/<Symbol>.cs` and `csharp:/<project.csproj>/generated/<HintName>`.

How do I debug csharp-ls?

There are two independent channels. `--loglevel` controls stderr output, while the server also supports the LSP `$/setTrace` and `$/logTrace` notifications and forwards its internal log output when a client sets tracing to messages or verbose, which in Visual Studio Code is the `[langId].trace.server` setting. For the wire protocol itself, `--rpclog` writes an RPC log to a file, and `--debug` enables debug mode.

Official sources

  1. License: MIT
  2. Project website
  3. razzmatazz/csharp-language-server on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/razzmatazz-csharp-language-server.svg)](https://hysenlabs.com/projects/razzmatazz-csharp-language-server)

Was this useful?