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-odin

v0.1.1

Published

Odin language support for the CodeMirror code editor

Downloads

263

Readme

@codincod/codemirror-lang-odin NPM version

[ CHANGELOG ]

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

CodeMirror has never shipped Odin, and there is no stream mode from CodeMirror 5 to fall back on either, so an Odin file in a CodeMirror 6 editor has until now been plain text. No Lezer grammar for Odin existed anywhere before this one.

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 {odin} from "@codincod/codemirror-lang-odin"

const view = new EditorView({
  parent: document.body,
  doc: `package main

import "core:fmt"

Point :: struct {
	x, y: f32,
}

main :: proc() {
	p := Point{x = 1, y = 2}
	fmt.println(p)
}
`,
  extensions: [basicSetup, odin()]
})

Coverage

Odin dev-2025-04. Declarations of every kind (:: for procedures, types and constants, := for variables), structs with tags and parapoly parameters, unions, enums, bit sets, bit fields, matrices, #soa and #simd types, procedure groups, polymorphic types ($T, $T/Spec) with where clauses, foreign blocks and foreign imports, attributes, compile-time when, the whole directive vocabulary, or_return and its family, both ternaries, type assertions, ranges, labels, and the automatic semicolons that make almost all of this readable without one.

Measured against the 1090 files of the standard library that ships with the compiler, 99.91% parse with no error node. The one file left is base:builtin, which describes the compiler's built-in procedures in a notation Odin's own parser rejects as well (-> (A, B, C, ...)), and which the compiler never reads as ordinary Odin.

The corpus does not ship with this package. It is BSD-3 and belongs to the Odin project, so test/corpus.ts takes the path to your own copy of it.

What a tokenizer has to settle

Almost all of Odin can be written down as a grammar. Seven things cannot, and they are the whole of tokens.ts.

A newline can end a statement. Odin inserts a semicolon at a line break when the token in front of it could end one, which is why almost no Odin source writes a semicolon. Whether the break ends the statement depends on the token behind it and on what stands after it: a brace where a body could stand, an else, or a where all take the semicolon back.

A brace is two different things. if x == Foo { opens a body and x := Foo{} opens a compound literal, and the same character spells either. Odin settles it by where the brace stands, so the tokenizer asks the parser whether a body could stand here and lets the answer decide. That is Odin's own rule: its parser refuses a compound literal in the head of an if, a for or a switch unless it is parenthesised, and those heads are exactly the places where a body could stand instead.

A name is not an expression. x, y := f() and x, y = f() are the same two names and the same comma, and only the operator behind them differs. Odin's parser reads the list and then looks for the colon; an LR parser cannot hold both readings open that long, so the tokenizer does the same lookahead one word earlier and hands the parser a different token for a name being declared. The same lookahead separates loop: for {} from x: int.

:: says what is being declared. main :: proc() {} declares a procedure, Point :: struct {} a type and MAX :: 100 a constant. The three read identically until the value is over, which is far too late to name the node a highlighter wants, so the tokenizer looks one word past the :: and hands the parser three different tokens. That is what makes a procedure name look like a procedure name before its body has been read.

in walks or asks. for x in xs walks a map or an array, and x in xs asks whether a set holds something. Odin turns the operator off while it reads the head of a for, and the tokenizer does the same by handing a range clause an in of its own, where only a range clause could stand.

A number may end in its point. 1. is a float and 1..<n is a range from

  1. A token rule reads as far as it can and would take the range's first dot for the point of the number, so numbers are scanned by hand, where the character behind the dot can be looked at first. Odin's own scanner does the same, and the one here is a reading of it, down to the letters that count as digits in 0z and 0h.

Block comments nest. /* /* */ */ is one comment in Odin rather than two, which no regular token can count.

What the tree does not say

Types are expressions. That is not a simplification: Odin's own AST has no separate type node, because Point :: struct {} really is a value bound to a name and [4]int really is an expression. So struct, []T, map[K]V and proc() are alternatives of expression in the grammar, exactly as they are in the compiler.

One field rule does three jobs. Struct fields, procedure parameters and results are written the same way, down to the using in front and the string tag behind, so one rule shape serves all three. It accepts a tag where only a struct may carry one, which is the usual trade: a grammar that is wrong about what the compiler will reject and right about what it will accept.

A do body on a procedure literal is left out. f :: proc() do g() is legal and appears nowhere in the standard library. Letting an unbraced statement end where a procedure literal may stand is what the parser generator runs out of memory over, so the one form the grammar refuses is the one nobody writes.