@codincod/codemirror-lang-nushell
v0.7.0
Published
Nushell language support for the CodeMirror code editor
Maintainers
Readme
@codincod/codemirror-lang-nushell 
This package implements Nushell language support for the CodeMirror code editor, using a Lezer grammar written for this package.
Nothing existed before it. CodeMirror has no Nushell mode, legacy or otherwise,
and neither does any other editor library on npm. Editors that colour Nushell
today either reach for the bash mode, which reads {|it| } as a brace and
$"($x)" as a broken string, or reach for tree-sitter, which a CodeMirror
editor cannot load.
Written 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 {nushell} from "@codincod/codemirror-lang-nushell"
const view = new EditorView({
parent: document.body,
doc: `def largest [dir: string] {
ls $dir
| where size > 1mb
| sort-by size --reverse
| first 5
}
`,
extensions: [basicSetup, nushell()]
})Measured against 12 963 .nu files, one per public repository the search
turned up, which is 77 MiB of code: 96.55% parse with no error node. The
corpus is sifted for what else writes a .nu, which is a language called nurl
and the fixture files of two parsers, and then nu-check is run over what is
left: npm run clean asks the shell about every file as a script and as a
module and moves out the 314 it will read as neither. A file the shell itself
will not parse is not one a grammar should be judged on.
The shell is asked only about the shape. A module it cannot find, a file it cannot source, a variable an outer scope was to have supplied, a command given one argument too many: each of those is about what stands around the file, and the file stays. What goes is the signature written the way 0.59 wrote it, the fixture a test suite keeps because it is invalid, and the delimiter nobody closed. Before the sieve the same grammar read 95.93%.
What the grammar decides
Nushell is a shell by ancestry and an expression language by construction. It
keeps a pipeline, a bare word and a # comment, and underneath them it has
closures, records, tables, ranges, cell paths, a type grammar and match
patterns. Most of the work is in saying which of the two a bracket has opened.
A brace opens a record, a closure, a block or a list of match arms, and the
four are spelled alike. {a: 1} is a record, {|x| $x} is a closure, {ls} is
a block, and the brace after match $x holds arms. The tokenizer reads what
follows the brace, builds the order the four are preferred in, and asks the
parser which of them it has room for. Nothing else here costs as much.
A command's arguments are a flat run, and an operator among them is a word
rather than a tree. where size > 10mb is a command and three arguments.
Nushell cannot build that tree either without the command's signature to say
what shape each argument is in, so a grammar that guessed would be wrong as
often as right. Inside a parenthesis, behind a let and in a condition the
operators do build a tree, because there the language means arithmetic.
A bare word may hold an operator character and may not end with one, so
http://example.com is one word and a: is the word a and then a colon.
Where a name is what the parser is waiting for, a second token stops at the
colon instead, which is what tells {year:0} from -map 0:v.
A leading dot is a name at the head of a pipeline and a cell path anywhere
else. .append is a command; $x.append reaches into a record.
A newline ends a statement where the parser has room for one and is skipped
everywhere else. A newline followed by a single | is skipped as well, which is
how a pipeline is written down the page.
What it gives an editor
Syntax highlighting, folding for blocks, records, lists, tables, signatures,
closures and match arms, indentation that knows } and ] close what came
before them, and completion for the builtins, the type names and the variables
the shell sets.
nushellLanguage is exported for use with LanguageSupport, and parser is
the grammar on its own.
Testing it on your own code
npm run corpus -- path/to/your/scriptsIt prints the proportion of files that came out clean, and npm run gaps
prints the shapes the failures have in common. The corpus itself is not
included.
