Puts the actual Mapal logo on .mapal files, plus comment and bracket behavior.
package.json language registration + the file icon
language-configuration.json // comments, bracket matching, auto-close
icons/mapal.svg the mark, square variant
VS Code has no "add one icon" API for file icon themes — a file icon theme is
all-or-nothing, so shipping a Mapal-only one would blank the icons for every other file
type in your project. That is why this uses contributes.languages[].icon instead (VS Code
≥ 1.66): the icon shows for .mapal files, and your existing icon theme keeps handling
everything else. If your active icon theme does define an icon for .mapal, the theme wins —
that is by design.
Not published to the Marketplace, so build a .vsix and install it:
python3 editors/vscode/package-vsix.py
code --install-extension editors/vscode/mapal-lang-0.1.0.vsix # or:
cursor --install-extension editors/vscode/mapal-lang-0.1.0.vsixThen restart the editor. Verify it actually registered — the message alone is not proof:
code --list-extensions | grep mapal-langIf the code command is not on your PATH, use the binary inside the app:
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code".
Dropping this directory (or a symlink to it) into ~/.vscode/extensions does not work on
current VS Code or Cursor. They keep extensions.json in that directory as the authoritative
registry and do not scan for unregistered folders, so a hand-placed extension is ignored
in silence — no error, no icon, no highlighting, indistinguishable from a broken
extension. Only the CLI install writes the registry entry.
That is also why package-vsix.py exists: vsce is the normal way to build a .vsix and it
needs npm, whereas a .vsix is just a ZIP holding extension.vsixmanifest,
[Content_Types].xml and an extension/ directory. The script builds one directly and
asserts the archive contains all three before claiming success.
A CLI install copies the files, so editing the repo afterwards changes nothing in the editor. Either rebuild and reinstall:
python3 editors/vscode/package-vsix.py && code --install-extension editors/vscode/mapal-lang-0.1.0.vsix --forceor launch a throwaway window that loads the source directly, which is better for grammar work:
code --extensionDevelopmentPath="$(pwd)/editors/vscode" .- Open a
.mapalfile — the status bar should read Mapal, not Plain Text. If it says Plain Text the extension is not loaded and nothing else matters. - Command palette →
Developer: Inspect Editor Tokens and Scopes, cursor on a token — should showsource.mapaland a scope such assupport.function.builtin.mapal.
syntaxes/mapal.tmLanguage.json covers comments, strings, guard arrows (split-scoped — chrome
as an arrow, discriminant by what it is), labels, fn declarations, builtins, bindings, call
position, arrows, numbers, types, operators, and the reserved-and-rejected category.
Run editors/test.sh after any edit — it asserts both editors together.
Scope names have to be ones themes actually style. Labels were first scoped
entity.name.label, which is semantically right and looked broken: VS Code's default themes
give it #C8C8C8 against an editor foreground of #CCCCCC, a 4/255 difference that renders
as plain text. They are now keyword.control.label.mapal (#C586C0), which is also what the
Vim file does — it links labels to Label, a Statement-family group. When picking a scope,
check it against the theme rather than only against the grammar.
Three things to know if you edit it.
The rule that decides outcomes is earliest match wins, ties broken by listed order — not
listed order alone. This bites specifically: a rule like (?<=->)\s*(ident) begins its match
at the whitespace after the arrow, one column before the keyword rule that matches the
identifier itself, so it wins on position however the patterns are ordered. That is why
#binding and #call-position carry an explicit negative lookahead listing every builtin and
ret; without it, -> println; scoped println as a variable. scope_test.py asserts that
exclusion covers every builtin, so adding one to #keywords and forgetting the exclusion fails
the test rather than quietly mis-coloring.
It has the opposite precedence to the Neovim file: TextMate takes the first matching
pattern, Vim the last. So this file is ordered most-specific first and
../nvim/syntax/mapal.vim least-specific first. A rule added to one
goes at the other end of the other.
And it is less capable in one specific way: x -> name; is lexically identical whether
name is a new variable or a call to a no-value function. The Vim file resolves it by scanning
the buffer for fn declarations; TextMate has no way to know what has been declared, so here
every -> name; reads as a binding. A terminal call to your own function will be colored as a
variable.
That divergence is the argument for not maintaining two grammars forever. The real fix is one LSP server driving both editors from the compiler's own parse (ADR-0008), at which point both hand-written grammars become fallbacks.