Skip to content

Editor Setup (Neovim)

This chapter shows a working Neovim/LazyVim setup for Shape with:

  • LSP (shape-lsp)
  • Tree-sitter highlighting + foreign language injection (tree-sitter-shape)
  • comment support (//) so gc/comment actions work

Option A: cargo (works everywhere)

Terminal window
cargo install shape-lsp

Option B: Mason (pending registry PR)

:MasonInstall shape-lsp

Option A: Build from source (works now)

Terminal window
cd shape/tree-sitter-shape
cc -shared -fPIC -O2 -o parser/shape.so src/parser.c -I src

This produces shape/tree-sitter-shape/parser/shape.so.

Option B: nvim-treesitter (coming soon — pending upstream PR)

:TSInstall shape

Create ~/.config/nvim/lua/plugins/shape.lua:

return {
-- tree-sitter-shape: parser/shape.so + queries/shape/{highlights,injections}.scm
-- Neovim discovers both automatically via runtimepath.
-- Set this to the tree-sitter-shape directory in your Shape checkout.
{
dir = vim.fn.expand("~/src/shape/tree-sitter-shape"),
name = "tree-sitter-shape-local",
lazy = false,
config = function()
vim.filetype.add({ extension = { shape = "shape" } })
vim.treesitter.language.register("shape", "shape")
vim.api.nvim_create_autocmd("FileType", {
pattern = "shape",
callback = function()
vim.treesitter.start()
end,
})
end,
},
{
"neovim/nvim-lspconfig",
opts = function()
vim.lsp.config.shape = {
cmd = { "shape-lsp" },
filetypes = { "shape" },
root_markers = { ".git", "shape.toml" },
-- Inlay-hint settings forwarded as initializationOptions.
-- Shape-unique categories (e.g. bindingStorageClass) are
-- OFF by default; flip to true to opt in.
init_options = {
shape = {
inlayHints = {
enable = true,
-- bindingStorageClass = { enable = true }, -- Shape-unique opt-in
},
},
},
}
vim.lsp.enable("shape")
-- Optional: enable inlay hints globally (Neovim 0.10+)
pcall(vim.lsp.inlay_hint.enable, true)
end,
},
{
"nvim-treesitter/nvim-treesitter",
opts = function(_, opts)
-- Ensure Python parser is installed for foreign block injection
opts.ensure_installed = opts.ensure_installed or {}
if not vim.tbl_contains(opts.ensure_installed, "python") then
table.insert(opts.ensure_installed, "python")
end
end,
},
{
"numToStr/Comment.nvim",
opts = {},
init = function()
vim.api.nvim_create_autocmd("FileType", {
pattern = "shape",
callback = function()
-- Fixes: "Option 'commentstring' is empty"
vim.bo.commentstring = "// %s"
end,
})
end,
},
}

Adjust the dir path to point to tree-sitter-shape in your local Shape checkout.

The tree-sitter-shape directory is added to Neovim’s runtimepath. Neovim discovers:

  • parser/shape.so — the compiled parser (you build this yourself)
  • queries/shape/highlights.scm — Shape syntax highlighting
  • queries/shape/injections.scm — foreign language injection (Python, etc.)

The injection query is generic — it reads the language name from fn <language> name(...) { } and injects the matching tree-sitter parser. Any foreign language with an installed tree-sitter parser gets syntax highlighting automatically.

Open a .shape file and run:

:set ft?
:LspInfo
:InspectTree
:set commentstring?

Expected:

  • filetype is shape
  • shape LSP is attached
  • :InspectTree shows the tree-sitter parse tree (including foreign_body nodes)
  • commentstring is // %s

When the tree-sitter grammar is updated, rebuild the parser:

Terminal window
cd shape/tree-sitter-shape
tree-sitter generate # regenerates src/parser.c from grammar.js
cc -shared -fPIC -O2 -o parser/shape.so src/parser.c -I src

Then restart Neovim.

No LSP features (hover/completion/diagnostics)

Section titled “No LSP features (hover/completion/diagnostics)”
  • verify shape-lsp is on your $PATH (installed via cargo install shape-lsp)
  • update to latest version:
Terminal window
cargo install shape-lsp --force

Foreign blocks have hover but no syntax highlighting

Section titled “Foreign blocks have hover but no syntax highlighting”

Tree-sitter injection handles syntax highlighting; the LSP handles hover/completions. For injection to work:

  1. parser/shape.so must be built and in the runtimepath.
  2. The foreign language’s tree-sitter parser must be installed (e.g., :TSInstall python).
  3. vim.treesitter.start() must run for shape buffers (the config above does this).
  • ensure the FileType shape autocmd above is active
  • reopen file or run :set commentstring=//\ %s
  • check that parser/shape.so exists in the tree-sitter-shape directory
  • check :lua print(vim.treesitter.language.get_lang("shape")) returns "shape"
  • check :lua print(vim.treesitter.highlighter.active[vim.api.nvim_get_current_buf()] ~= nil) returns true

The Shape LSP honors a shape.* settings tree forwarded via the init_options slot (see the lspconfig block in §3) or — for live updates — via workspace/didChangeConfiguration. The inlay-hint master is ON by default; Shape-unique opt-in categories are OFF by default so the editor stays uncluttered:

SettingDefaultDescription
shape.inlayHints.enabletrueMaster toggle.
shape.inlayHints.typeHintstrueInferred type hints on let bindings.
shape.inlayHints.parameterHintstrueParameter-name hints at call sites.
shape.inlayHints.variableTypeHintstrueInferred variable types where elided.
shape.inlayHints.returnTypeHintstrueInferred return-type hints on fn declarations.
shape.inlayHints.chainHintstrueIntermediate-type hints on method-chain expressions.
shape.inlayHints.bindingStorageClass.enablefalse (opt-in)Shape-unique: surfaces the compiler’s BindingStorageClass (Direct / UniqueHeap / SharedCow / SharedAtomic / SharedAtomicMut) inline. Rendered with [… approx].
  • LSP and runtime use the same module/loader architecture; keep your shape-lsp up to date (cargo install shape-lsp --force) when testing language changes.