Skip to content

LSP: Getting started

Matthew Sackman edited this page Sep 27, 2025 · 12 revisions

CUE has an LSP server built-in. LSP servers are used to provide language-specific support for editors.

The earliest version of CUE that has LSP support is v0.15.0-alpha.1.

Editor configuration

Guidance for every possible editor is beyond our means. In general, you need to:

  1. Install the cue binary and make sure it's available on your PATH. v0.15.0-alpha.1 is the earliest version of CUE that has LSP support.
  2. Your editor should be configured to use the command cue lsp serve to start CUE's LSP server.

VSCode

Our CUE extension is available on the VSCode Marketplace and OpenVSX. If cue is installed on your machine, the extension should find it and enable LSP support automatically.

Intellij

Our CUE plugin is available on the marketplace. LSP support is not yet available for this extension (work in progress), but the LSP4IJ plugin can be used, for example.

Emacs

The lsp-mode package works with CUE's LSP server. Something like this should work:

(use-package lsp-mode
  :commands (lsp lsp-deferred)
  :init
  (with-eval-after-load 'lsp-mode
	(add-to-list 'lsp-language-id-configuration '(cue-mode . "cue"))
	(lsp-register-client (make-lsp-client
                      :new-connection (lsp-stdio-connection (list "cue" "lsp" "serve"))
                      :activation-fn (lsp-activate-on "cue")
                      :server-id 'cuelsp))
  )
  :hook ((cue-mode . lsp-deferred))
)

(use-package cue-mode)

Helix

[language-server.cuelsp]
command = "cue"
args = ["lsp", "serve"]

[[language]]
name = "cue"
language-servers = [ "cuelsp" ]
file-types = ["cue"]
auto-format = true
comment-token = "//"
indent = { tab-width = 3, unit = "\t" }

Neovim

Tested with Neovim v0.11.x. Install nvim-lspconfig via :

git clone https://github.com/neovim/nvim-lspconfig ~/.config/nvim/pack/nvim/start/nvim-lspconfig

or via a 3rd-party plugin manager.

Then use something like this minimal ~/.config/nvim/init.lua:

vim.cmd([[syntax on]])
vim.cmd([[filetype plugin indent on]])

-- Enable the CUE LSP
vim.lsp.enable("cue")

-- Go-to-definition on Ctrl-]
vim.keymap.set("n", "<C-]>", vim.lsp.buf.definition, { desc = "LSP Definition" })

-- Hover on Ctrl-h
vim.keymap.set("n", "<C-h>", vim.lsp.buf.hover, { desc = "LSP Hover" })

-- Optional: set trace level logging (logs at ~/.local/state/nvim/lsp.log)
vim.lsp.set_log_level('trace')

-- Optional: format on save
vim.api.nvim_create_autocmd("BufWritePre", {
  pattern = "*.cue",
  callback = function()
    vim.lsp.buf.format({ async = false })
  end,
})

vim-lsp

Add to your ~/.vim/after/plugin/vim-lsp.vim

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

Nova

A community-supported extension is available

Advanced

The foundation of CUE's LSP server is based on Go's gopls LSP server. As such, it supports the same remote mode of operation. This means you can configure it to listen on a socket (e.g. cue lsp serve -listen='unix;/run/user/1000/cuelsp/cuelsp') and then configure your editor to connect to that socket (e.g. cue lsp -remote='unix;/run/user/1000/cuelsp/cuelsp')

Supported features

As of CUE v0.15.0-alpha.1, the CUE LSP server supports:

Goto-definition

There are two modes for this. Firstly, from a value, goto-definition will take you to all the places where that value is defined. For example:

```cue
num: int
num: > 6
num: 17
out: 3 + num
```
If you place your cursor on the `num` in the last line, and goto-definition, then you will be shown all 3 definitions of `num` on the previous lines.
The other mode is to place your cursor on a field name itself:

```cue
#Schema {
  name!: string
  age!: int
}
data: #Schema
data: age: 0
```
If you place your cursor on the `age` field in the last line, and goto-definition, then you will be taken to the definition of `age` within the `#Schema` struct.

Completion

Completions are provided both for field names and for values.

```cue
num: int
num: > 6
num: 17
out: 3 + n
```
As you type the `n` on the last line, `num` will be suggested.

```cue
#Schema {
  name!: string
  age!: int
}
data: #Schema
data: a
```
As you type the `a` on the last line, `age:` will be suggested as a field name.

Hover

Hover is used to display relevant documentation in your editor as you "hover over" (or similar) a value. This works exactly the same way as Goto-definition; it works for field values:

```cue
// num must be an int
num: int
// num must be greater than six
num: > 6
num: 17
out: 3 + num
```
Hovering over `num` on the last line will show the docs from lines 1 and 3.
It also works for fields:

```cue
#Schema {
  // The name of the thing
  name!: string
  // The age of the thing
  age!: int
}
data: #Schema
data: age: 0
```
Hovering over `age` on the last line will show the docs for the `age` field within `#Schema`.

Formatting

The CUE LSP server can format entire files. This means you can configure your editor to "format on save" if you wish to.

Limitations

Static and partial evaluation only

The LSP server does not currently attempt to fully evaluate CUE; this could take a long time, and/or require additional configuration. Instead, it performs a "static analysis": analysing your code without running it. This is very similar to how type-checking is implemented in many "statically typed" languages. Although a simple unification of struct fields is performed, and attempts are made to resolve references, there is no evaluation of expressions in general. Field names are not tested against patterns, and dynamic fields are not evaluated.

Dynamic indexing with a constant will work. For example:

[8, {a: "hi"}, {a: true}][1].a

If you goto-definition from the final a, then that will jump to the a: "hi" field because the constant index [1] is understood. But if this constant index is made into a dynamic expression then the LSP server will not attempt to evaluate that expression. For example:

n: 1
[8, {a: "hi"}, {a: true}][n*1].a

CUE files within modules only

As of v0.15.0-alpha.1, the LSP server will currently only support CUE files within modules, and that have a package declaration.

Clone this wiki locally