npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@codincod/codemirror-lang-csharp

v0.1.0

Published

C# language support for the CodeMirror code editor

Readme

@codincod/codemirror-lang-csharp NPM version

[ CHANGELOG ]

This package implements C# language support for the CodeMirror code editor, using a Lezer grammar written for this package.

CodeMirror 6 has never shipped C#. What gets used in its place is the C-family stream mode ported from CodeMirror 5, which colours a list of keywords and builds no tree, so nothing above it can fold a method, indent a switch or ask what the word under the cursor is. Two Lezer grammars for C# exist; one cannot parse class A { void M() { } }, and the other builds a tree out of six node names and carries no highlighting information at all.

Written in part for CodinCod, a competitive coding platform, where it colours the editor people solve puzzles in.

This code is released under an MIT license.

Usage

import {EditorView, basicSetup} from "codemirror"
import {csharp} from "@codincod/codemirror-lang-csharp"

const view = new EditorView({
  parent: document.body,
  doc: `class Greeter(string name)
{
    public void Greet() => Console.WriteLine($"Hello, {name}!");
}
`,
  extensions: [basicSetup, csharp()]
})

Coverage

C# 12. Records, primary constructors, file scoped namespaces, top level statements, pattern matching in all of its forms, switch expressions, LINQ query expressions, lambdas with and without types, local functions, tuples and deconstruction, ranges and indices, collection expressions, nullable types, unsafe pointers, explicit interface implementations, and all five ways C# writes a string.

Measured against 18 089 C# files from six open source projects (Avalonia, Entity Framework Core, ILSpy, Humanizer, Json.NET and osu!), which is 146 MiB of code.

What the grammar cannot say

Four things about C# are settled in src/tokens.ts rather than in the grammar.

A string is five different languages. "a", @"a", $"a", $@"a" and """a""" disagree about what ends a string, what an escape is, and whether a brace opens a hole holding an expression. The tokenizer cuts each one into a start, its content and an end, and the grammar parses the expressions between the pieces. A raw string is read as a single token, because its closing delimiter is however many quotes opened it.

A format specifier is not C#. {value:F2} puts a format string where an expression would go, which is why the language asks anybody who wants a conditional in a hole to parenthesise it.

Two more are settled in the grammar, and are worth knowing about.

Casing decides what is a type. Foo bar is a declaration and foo.Bar() is a call, and after one identifier no amount of lookahead separates them, so the parser follows the convention every C# codebase keeps: a word that starts with a capital is read as a type first and an ordinary name second, and a word that starts with a lower case letter the other way round. Both readings stay alive and the one that fits wins. The trick is @lezer/java's, and C# keeps the same convention Java does.

A preprocessor directive is skipped, not parsed. #if, #region and #pragma are read as one token each and thrown away with the whitespace. Real code puts them in the middle of a declaration and closes them somewhere else entirely, so anything that tried to parse what they wrap would spend its time being wrong about code that compiles. The cost is that a file which declares one namespace in an #if and another in its #else has two namespace headers as far as this parser is concerned.

Testing

npm test                       # the parse specs, then highlighting and indentation
npm run corpus -- path/to/code # error nodes over a directory of real C# files

No corpus ships with the package, because none of that code is ours to redistribute. test/corpus.ts takes a directory and reports the share of files that parse with no error node, grouped by directory so that a project which drags the total down names itself.