Language Server Protocol (LSP) Guide

Turmeric ships a built-in language server that speaks Language Server Protocol over stdio (JSON-RPC 2.0). The server is invoked as:

tur lsp

Editors launch it as a subprocess and communicate via stdin/stdout.

Current capabilities

Capability Status
Diagnostics (textDocument/publishDiagnostics) Supported
Hover documentation Not yet supported
Go-to-definition Not yet supported
Completion Not yet supported

When you open or edit a .tur file, the server compiles it in check-only mode and publishes any parse or type errors back to the editor as diagnostics (red underlines, error panel entries, etc.).

Editor configuration

Neovim (nvim-lspconfig)

Add a custom server entry -- nvim-lspconfig does not bundle Turmeric by default, so you register it manually:

local lspconfig = require("lspconfig")
local configs   = require("lspconfig.configs")

if not configs.turmeric then
  configs.turmeric = {
    default_config = {
      cmd        = { "tur", "lsp" },
      filetypes  = { "tur" },
      root_dir   = lspconfig.util.root_pattern("build.tur", ".git"),
      single_file_support = true,
    },
  }
end

lspconfig.turmeric.setup({})

If you use lazy.nvim and want this to load alongside nvim-lspconfig:

{
  "neovim/nvim-lspconfig",
  config = function()
    -- paste the block above here
  end,
}

Neovim does not auto-detect .tur files as tur filetype. Add this to your config or to ~/.config/nvim/ftdetect/tur.lua:

vim.filetype.add({ extension = { tur = "tur" } })

Neovim (built-in LSP, no plugin)

vim.api.nvim_create_autocmd("FileType", {
  pattern = "tur",
  callback = function(ev)
    vim.lsp.start({
      name    = "turmeric",
      cmd     = { "tur", "lsp" },
      root_dir = vim.fs.dirname(
        vim.fs.find({ "build.tur", ".git" }, { upward = true })[1]
      ),
    })
  end,
})

Vim (vim-lsp)

Install vim-lsp, then add:

if executable('tur')
  au User lsp_setup call lsp#register_server({
    \ 'name': 'turmeric',
    \ 'cmd': {server_info -> ['tur', 'lsp']},
    \ 'allowlist': ['tur'],
    \ })
endif

Add filetype detection if not already present:

augroup TurmericFt
  autocmd!
  autocmd BufRead,BufNewFile *.tur setfiletype tur
augroup END

Vim (ALE)

ALE can drive an LSP binary directly. Add to your ~/.vim/after/ftplugin/tur.vim (or equivalent):

let g:ale_linters = { 'tur': ['turmeric_lsp'] }

Then register the linter in your vimrc or in ~/.vim/ale_linters/tur/turmeric_lsp.vim:

call ale#linter#Define('tur', {
\   'name':            'turmeric_lsp',
\   'lsp':             'stdio',
\   'executable':      'tur',
\   'command':         '%e lsp',
\   'project_root':    function('ale#util#FindProjectRoot'),
\   'language':        'tur',
\ })

VS Code

The bundled VS Code extension (vscode-syntax-ext/) provides syntax highlighting only. To add LSP diagnostics, install a generic LSP client such as vscode-glspc or configure vscode-languageclient in your own extension wrapper.

A minimal settings.json entry using the multi-lsp extension:

{
  "multi-lsp.servers": [
    {
      "language": "tur",
      "command": "tur",
      "args": ["lsp"]
    }
  ]
}

Native VS Code LSP support (using vscode-languageclient inside the Turmeric extension) is planned for a future phase.

Emacs (eglot)

Eglot is built into Emacs 29+. Add a major mode for Turmeric first (or use lisp-mode as a fallback), then register the server:

;; Simple major mode derived from lisp-mode
(define-derived-mode turmeric-mode lisp-mode "Turmeric"
  "Major mode for Turmeric source files.")
(add-to-list 'auto-mode-alist '("\\.tur\\'" . turmeric-mode))

;; Register the LSP server with eglot
(with-eval-after-load 'eglot
  (add-to-list 'eglot-server-programs
               '(turmeric-mode . ("tur" "lsp"))))

;; Auto-start eglot when opening .tur files
(add-hook 'turmeric-mode-hook #'eglot-ensure)

Emacs (lsp-mode)

(with-eval-after-load 'lsp-mode
  (lsp-register-client
   (make-lsp-client
    :new-connection (lsp-stdio-connection '("tur" "lsp"))
    :activation-fn  (lsp-activate-on "tur")
    :server-id      'turmeric)))

(add-hook 'turmeric-mode-hook #'lsp)

Helix

Add to ~/.config/helix/languages.toml:

[[language]]
name              = "turmeric"
scope             = "source.tur"
file-types        = ["tur"]
comment-token     = ";"
indent            = { tab-width = 2, unit = "  " }
language-servers  = ["turmeric-lsp"]

[language-server.turmeric-lsp]
command = "tur"
args    = ["lsp"]

Zed

In ~/.config/zed/settings.json:

{
  "lsp": {
    "turmeric": {
      "binary": {
        "path": "tur",
        "arguments": ["lsp"]
      }
    }
  }
}

Zed also requires a language extension for filetype detection. Until an official extension is published, use the Zed extension API to register .tur manually or rely on generic highlighting.

How it works

When a .tur file is opened or modified, the server:

  1. Writes the current buffer text to a temporary file under /tmp/.
  2. Runs the Turmeric compiler in type-check-only mode (tur_check_only).
  3. Collects all diagnostics via the internal diag_lsp_* API.
  4. Remaps temp-file paths back to the real document URI.
  5. Sends a textDocument/publishDiagnostics notification to the editor.
  6. Deletes the temporary file.

The server uses TextDocumentSyncKind.Full (sync kind 1): the entire file content is sent on every change, not just diffs. This keeps the implementation simple at the cost of slightly more data per keystroke for large files.

Troubleshooting

No diagnostics appear. Verify tur is on your $PATH:

which tur
tur --version

Server exits immediately. Run tur lsp in a terminal and type a minimal JSON-RPC initialize request. Any startup error (missing stdlib, bad install) will appear on stderr.

Diagnostics are stale or missing after save. Some editors only sync on save (not on every keystroke). Check your editor's LSP sync setting; the server responds to both textDocument/didOpen and textDocument/didChange.

See also