Skip to content

LSP: Getting started

Matthew Sackman edited this page Sep 9, 2025 · 20 revisions

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

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. Tell your editor that the CUE language server should be started by running cue lsp serve

VSCode

Our CUE extension is available on the marketplace. If cue is installed on your machine, the extension should find it and enable LSP support automatically if possible.

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
  :init
  :commands (lsp lsp-deferred)
  (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" }

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:

  1. 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:

    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:

    #Schema {
      name!: string
      age!: int
    }
    data: #Schema
    data: age: 0

    If you place your cursor on the age in the last line, and goto-definition, then you will be taken to the definition of age within the #Schema struct.

  2. Completion. Completions are provided both for field names and for values.

    num: int
    num: > 6
    num: 17
    out: 3 + n

    As you type the n on the last line, num will be suggested.

    #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.

  3. 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:

    // 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:

    #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.

  4. 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