An .arcm file holds four languages: ArcMoon markup, CSS in [style], build-time
JavaScript in ${ }$ and browser JavaScript in runtime ${ }$. The language server
helps with all four, in any editor that speaks LSP: errors, completions, hover, go to definition, colors, quick fixes and formatting.
Set up your editor
- VS Code, VSCodium: install the extension. Nothing else.
- Zed, Neovim, Vim: install the server (
npm install -g arcmoon-lsp), then runarcmoon-lsp init <editor>. - Any other LSP editor: see Other editors.
VS Code
The extension comes with the server inside, so VS Code needs nothing else: no Node.js, no global install.
- Open the Extensions view: Ctrl+Shift+X (Cmd+Shift+X on macOS).
- Search for ArcMoon Language and click Install.
- Open a .arcm file.
code --install-extension Adam-Elmi.arcmoon-language
It's on the Marketplace,
and on Open VSX for VSCodium and other editors based on VS Code.
To run a different copy of the server, point arcmoon.server.path at its cli.js; leave it empty normally:
{
"arcmoon.server.path": "/home/me/ArcMoon-LSP/server/cli.js"
}Zed
Zed builds the extension itself, so it needs Rust, installed with rustup. After installing the server:
- Create the extension folder:
arcmoon-lsp init zed. It asks where to put it, and offers to add the Rust target it needs. - In Zed, open the command palette (Ctrl+Shift+P), run zed: install dev extension and pick that folder.
- Turn on the server's colors in Zed's settings.json (Zed leaves them off by default):
{
"languages": {
"ArcMoon": { "semantic_tokens": "full" }
}
}Neovim
Neovim 0.11 or newer works with its built-in LSP; older versions use nvim-lspconfig. After installing the server:
- Run
arcmoon-lsp init neovim. It writesarcmoon.luainto~/.config/nvim/lua/. - Load it from your
init.luawithrequire("arcmoon"), then restart Neovim.
The file, if you'd rather add it yourself. It registers the arcmoon file type, starts the server, and makes gc comment with #:
-- ###################
-- ArcMoon for Neovim: .arcm files, the arcmoon-lsp server, comments
-- Put this in your config (after your plugin manager, if you use nvim-lspconfig)
-- ###################
vim.filetype.add({ extension = { arcm = "arcmoon" } })
-- ###################
-- For a local copy of the server: { "node", "/path/to/ArcMoon-LSP/server/cli.js", "--stdio" }
-- ###################
local cmd = { "arcmoon-lsp", "--stdio" }
local root_markers = { "arcmoon.config.js", "package.json", ".git" }
if vim.lsp.config and vim.lsp.enable then
-- ###################
-- Neovim 0.11+: built in, no plugin needed
-- ###################
vim.lsp.config("arcmoon", { cmd = cmd, filetypes = { "arcmoon" }, root_markers = root_markers })
vim.lsp.enable("arcmoon")
else
-- ###################
-- Older Neovim: through neovim/nvim-lspconfig
-- ###################
local ok, lspconfig = pcall(require, "lspconfig")
if ok then
local configs = require("lspconfig.configs")
if not configs.arcmoon then
configs.arcmoon = {
default_config = { cmd = cmd, filetypes = { "arcmoon" }, root_dir = lspconfig.util.root_pattern((table.unpack or unpack)(root_markers)) },
}
end
lspconfig.arcmoon.setup({})
end
end
-- ###################
-- "gc" comments with #
-- ###################
vim.api.nvim_create_autocmd("FileType", {
pattern = "arcmoon",
callback = function()
vim.bo.commentstring = "# %s"
end,
})Vim
Vim needs an LSP plugin: coc.nvim, or yegappan/lsp on Vim 9. After installing the server:
- Run
arcmoon-lsp init vim. It makes .arcm files open as ArcMoon (with#comments), then asks which plugin you use and sets it up. - Restart Vim and open a .arcm file.
For coc.nvim it adds this to coc-settings.json (after asking; a file with comments is left alone and the settings are printed instead):
{
"languageserver": {
"arcmoon": {
"command": "arcmoon-lsp",
"args": ["--stdio"],
"filetypes": ["arcmoon"],
"rootPatterns": ["arcmoon.config.js", "package.json", ".git"]
}
},
"semanticTokens.filetypes": ["arcmoon"]
}With yegappan/lsp, add this to your vimrc after the plugin is loaded:
call LspOptionsSet(#{ semanticHighlight: v:true })
call LspAddServer([#{ name: 'arcmoon', filetype: ['arcmoon'], path: 'arcmoon-lsp', args: ['--stdio'] }])Other editors
Any editor with an LSP client works. Give it:
- Command:
arcmoon-lsp --stdio(ornode /path/to/server/cli.js --stdio). - Files:
*.arcm, as the languagearcmoon. - Project root: the folder with
arcmoon.config.js,package.jsonor.git. - Colors: turn on semantic tokens; the server sends only standard token types, so every theme colors them.
The init command
arcmoon-lsp init sets up an editor from files that ship inside the npm package, so you don't need to clone anything:
arcmoon-lsp init # asks which editor, where, and what to build
arcmoon-lsp init zed # only asks what is left
arcmoon-lsp init zed ./my-folder # the folder is given, so not asked
arcmoon-lsp init zed --yes # no questions: defaults, nothing built or installed
arcmoon-lsp init vim --force # replace files that are already there
arcmoon-lsp --version # the version
arcmoon-lsp --help # every command- Every question has a default, so Enter always works. In CI or a script (no terminal), it uses the defaults and prints the next steps.
- It never replaces your files without asking (
--forceto allow it), and leaves the rest of a folder like~/.vimalone. - Commands it would run (
rustup,cargo,code) are shown first. If one is missing, it says how to get it and carries on. - It warns if the
arcmoon-lspcommand isn't on your PATH.
Errors and warnings
Shown as you type, with the compiler's own messages, at the exact spot:
- Syntax: unclosed strings, code or comments;
[end]that doesn't match; unquoted values; a prop written twice; bad[import]or[for-each]. - Tags and imports: unknown blocks (did you mean …), components that aren't imported, import paths that don't exist (did you mean …), circular imports, a component that has an error itself, and imports you never use (shown faded).
- JavaScript: syntax errors in
${ }$and the runtime rules:exportin runtime code, a build value that isn't exported, a statement where a live value goes, a Node.js module in the browser, an npm package not inbundle. - Refs:
defineRef()names with no element (did you mean …), a single ref on two elements,ref()/refs()with the wrong kind. - CSS: unknown properties, bad values and broken CSS in
[style], style props andcss.props (css.colrhas a quick fix tocss.color). - Files:
[link]stylesheets and[script = src]files that don't exist,runtime ${ }$in a component's[style].
Changing a file re-checks the other open files, so fixing a component clears its error on the pages that use it.
Completions
- After
[:end/end:namefor the open block first, then your imported components, the built-in blocks (as snippets:for-each = ${ items }$, as: "item"]…[end]), then every HTML, SVG and MathML tag. - Props: the attributes of that tag, plus global ones (
class,aria-*,data-*), with their known values (type: "suggeststext,email…). Events suggestruntime ${ () => … }$. - Component props: the props the component reads with
ArcMoon.props(), written to match their defaults. For this component, typing inside[Card =suggeststitle: "",count: 0andbig: true:
${ const { title, count = 0, big = false } = ArcMoon.props(); }$
[div = class: "card"]
[h2]${ title }$[end]
[p]${ count }$ items[end]
[end:div]- Import paths: your aliases (
@/), then folders and.arcmfiles. - Refs: ref names in
defineRef("…"), from the file and the components it uses. - CSS: properties, values (with color swatches), selectors from the class names in your markup, and
var(--…)names. The same works incss.props:css.colsuggestscolor, and inside its quotes, color values. - JavaScript: your variables from every
${ }$of the file, object fields, imports and npm packages, Node.js in build-time code, the DOM in runtime code, andArcMoon.. Exported values arrive in runtime code with their build-time types, and event handlers know their event (onclickgives aPointerEvent). Parameter hints show while you type a call.
Hover and go to definition
- Hover: a tag or attribute (description and an MDN link); a component (its file and props with defaults); an import path (where it leads, aliases resolved, or where it looked if it's missing); a prop on a component (which component reads it); a CSS property; the type of any JavaScript name.
- Go to definition: a component or its import path opens the
.arcmfile;[link]/[script]paths open the file;defineRef("btn")jumps to its element; JavaScript names jump to their declaration, even in another${ }$block, a local.jsfile or npm types. An exported value in runtime code jumps to itsexport.
Quick fixes
- Add the missing import for
[Card]when aCard.arcmexists in the project, using your alias when one fits (@/components/Card.arcm). - Did you mean:
[dvi]→[div],defineRef("my-bnt")→"my-btn","./Crad.arcm"→"./Card.arcm". - Quote an unquoted value.
Colors
The server colors all four languages the same way in every editor, with standard token types your theme already knows:
- Markup: HTML tags and components in different colors,
[end]and built-ins as keywords, prop keys,arcm-*directives,--props, strings, numbers, comments, escapes, and the${/}$markers. - JavaScript: TypeScript's own coloring: variables, functions, parameters, properties and classes, with declarations,
constnames and built-ins (Math,document) marked. - CSS: selectors (tag, class, id, pseudo), properties, values, numbers, colors and
var(--…).
Editor helpers
- Matching
[end]highlighted with its block, from either side, even while the file doesn't parse. - Folding: blocks, multi-line code,
### ###comments, and a run of imports. - Outline: the block tree, with
#id .class, refs, components, import paths and loops. - Formatting (only when you run Format Document): CSS in
[style]and multi-line JavaScript blocks, with Prettier, following your editor's tab size and a project.prettierrc. ArcMoon markup itself is left as written.
Your project's config
The server reads arcmoon.config.js from the project root, without running it: importAliases (for paths, completions and go to definition) and bundle (for the runtime import check). Only plain values are understood, like { "@": "./src" }. A change to the config re-checks the open files.
How it works
For each .arcm file, the server builds hidden documents, one per language: the markup, all the
${ }$ code as one module with Node.js types, all the runtime code as one module with browser types, and all
the CSS. TypeScript and VS Code's CSS service answer questions about them, and every answer is mapped back to the right
spot in your file. ArcMoon's own lexer and parser read the markup, so the server sees code exactly the way the compiler does.
Limits
- One syntax error shows at a time: the parser stops at the first.
- No type errors: ArcMoon code is plain JavaScript.
- Some checks only
arcm buildcan do, because they need the built page: page styles that also style a component, and refs no code uses. arcm-syntaxdoesn't colorarcm-rawbodies yet.
Troubleshooting
- No colors in Zed: add
"semantic_tokens": "full"for ArcMoon in settings.json. - The server doesn't start (Zed, Neovim, Vim): check
arcmoon-lsp --versionin a terminal. If it's not found, install it withnpm install -g arcmoon-lspand make sure npm's global folder is on your PATH. - Imports with
@/show errors: open the project folder (the one witharcmoon.config.js), not a subfolder.