The Gleam Language Server is a program that can provide IDE features to text editors that implement the language server protocol, such as VS Code and Neovim. This document details the current state of the language server and its features.
Project Status
The Gleam Language Server is an official Gleam project and the newest part of the Gleam toolchain. It is actively being developed and is rapidly improving, but it does not have all the features you might find in more mature language servers for older languages.
If you wish to to see what is currently being worked on you can view the Gleam roadmap.
Installation
The Gleam Language Server is included in the regular gleam binary, so if you
have Gleam installed then you have the Gleam language server installed. You may
need to configure your editor to use the language server for Gleam code.
Helix
Helix supports the language server out-of-the-box. No additional configuration is required and Helix will automatically start the language server when a Gleam file is opened.
Neovim
Neovim’s nvim-lspconfig includes
configuration for Gleam. Install nvim-lspconfig with your preferred plugin
manager and then add the language server to your init.lua.
On Nvim 0.11+ and nvim-lspconfig 2.1+:
vim.lsp.enable('gleam')
On Nvim <= 0.10:
require('lspconfig').gleam.setup({})
The language server will then be automatically started when you open a Gleam file.
If you are using nvim-treesitter
you can run :TSInstall gleam to get syntax highlighting and other tree-sitter
features.
VS Code
Install the VS Code Gleam plugin.
The language server will then automatically started when you open a Gleam file.
If VS Code is unable to run the language server ensure that the gleam binary
is included on VS Code’s PATH, and consider restarting VS Code.
Zed
When a Gleam file is opened, Zed will suggest to install the Gleam plugin, once installed the language server will automatically be started when you open a Gleam file.
Other editors
Any other editor that supports the Language Server Protocol can use the Gleam
Language Server. Configure your editor to run gleam lsp from the root of your
workspace.
Features
Multiple project support
You can open Gleam files from multiple projects in one editor session and the Gleam language server will understand which projects they each belong to, and how to work with each of them.
Project compilation
The language server will automatically compile code in Gleam projects opened in the editor. Code generation and Erlang compilation are not performed.
If any files are edited in the editor but not yet saved then these edited versions will be used when compiling in the language server.
The target specified in gleam.tomlis used by the language server. If no
target is specified then it defaults to Erlang.
Error and warning diagnostics
Any errors and warnings found when compiling Gleam code are surfaced in the editor as language server diagnostics.
Code formatting
The language server can format Gleam code using the Gleam formatter. You may want to configure your code to run this automatically when you save a file.
Hover
The language server will show documentation, types, and other information when hovering on:
- Constants.
- Import statements, including unqualified values and types.
- Module functions.
- Module qualifiers.
- Patterns.
- Record fields.
-
The
..used to ignore additional fields in record pattern. - Type annotations.
- Values.
Go-to definition
The language server supports go-to definition for:
- Constants.
- Functions.
- Import statements, including unqualified values and types.
- Type annotations.
- Variables.
Go-to type definition
The language server supports go-to type definition. When triggered on an expression the language server will identify the types of all the values used in the expression and present their definitions for you to view and to jump to.
Find references
The language server supports find references for these entities:
- Functions.
- Function arguments.
- Constants.
- Types
- Custom type variants
- Variables.
Code completion
The language server support completion of:
- Function arguments.
- Functions and constants defined in other modules, automatically adding import statements if the module has not yet been imported.
- Functions and constants defined in the same module.
- Locally defined variables.
- Modules in import statements.
- Record fields.
- Type constructors in type annotations.
- Unqualified types and values in import statements.
Rename
The language server is able to rename:
- Functions.
- Function arguments.
- Constants.
- Types
- Custom type variants
- Variables.
Document symbols
The language server supports listing document symbols, such as functions and constants, for the current Gleam file.
Signature help
The language server can show the type of each argument when calling a function, along with the labels of the arguments that have them.
Code actions
Add annotations
This code action can add type annotations to assignments and functions.
pub fn increment(x) {
x + 1
}
If your cursor is within a function that does not have the all of the argument types and the return type annotated then code action will be suggested, and if run the code will be updated to include them:
pub fn increment(x: Int) -> Int {
x + 1
}
It can also be triggered on letand useassignments.
Add missing import
This code action can add missing imports.
pub fn main() -> Nil {
io.println("Hello, world!")
}
If your cursor is within the io.println and there is an importable module with
the name ioand a function named println then code action will be suggested,
and if run the code will be updated to this:
import gleam/io
pub fn main() -> Nil {
io.println("Hello, world!")
}
Add missing patterns
This code action can add missing patterns to an inexhaustive case expression.
pub fn run(value: Bool) -> Nil {
case value {}
}
If your cursor is within the case expression then code action will be suggested, and if run the code will be updated to this:
pub fn run(value: Bool) -> Nil {
case value {
True -> todo
False -> todo
}
}
Add omitted labels
This code action can add omitted labels to the call of a function or record constructor.
pub type User {
User(first_name: String, last_name: String, likes: List(String))
}
pub fn main() {
let first_name = "Giacomo"
User(first_name, "Cavalieri", ["gleam"])
}
If your cursor is within the call that does not have all labelled supplied then code action will be suggested, and if run the code will be updated to this:
pub type User {
User(first_name: String, last_name: String, likes: List(String))
}
pub fn main() {
let first_name = "Giacomo"
User(first_name:, last_name: "Cavalieri", likes: ["gleam"])
}
Case correction
This code action can correct names written with the wrong case.
pub main() {
let myNumber = 100
}
If your cursor is within the name written with the wrong case then code action will be suggested, and if run the code will be updated to this:
pub main() {
let my_number = 100
}
Collapse nested case expressions
This code takes a case expression where one of the clause bodies is another case expression that matches on a variable from that clause's pattern, and merges the two case expressions into one.
pub fn main() {
case user {
User(name:) ->
case name {
"Joe" -> "Hello, Joe!"
_ -> "Hello there!"
}
Guest -> "You're not logged in!"
}
}
If your cursor is within the nested case the code action will be suggested, and if run the module will be updated to this:
pub fn main() {
case user {
User(name: "Joe") -> "Hello, Joe!"
User(name: _) -> "Hello there!"
Guest -> "You're not logged in!"
}
}
Convert to and from pipe
These code actions can be used to convert between the |> pipe syntax and the
regular function call syntax.
import gleam/list
pub fn main() {
list.map([1, 2, 3], double)
}
If your cursor is within the list argument then the code action will be suggested, and if run the code will be updated to this:
import gleam/list
pub fn main() {
[1, 2, 3] |> list.map(double)
}
The running the code action again will reverse this change.
You can also choose to pipe arguments other than the first by selecting them in your editor before triggering the code action.
Convert to and from use
These code actions can be used to convert between the use syntax and the
regular function call syntax.
pub fn main() {
use profile <- result.try(fetch_profile(user))
render_welcome(user, profile)
}
If your cursor is within one of the use expression then the code action will
be suggested, and if run the code will be updated to this:
pub fn main() {
result.try(fetch_profile(user), fn(profile) {
render_welcome(user, profile)
})
}
The running the code action again will reverse this change.
Discard unused result
This code action assigns unused results to _, silencing the warning.
Typically it is better to handle the result than to ignore the possible
failure.
pub fn main() {
function_which_can_fail()
io.println("Done!")
}
If your cursor is within the result-returning-statement then code action will be suggested, and if run the code will be updated to this:
pub fn main() {
let _ = function_which_can_fail()
io.println("Done!")
}
Expand function capture
This code action converts from the function capture syntax to an anonymous function.
pub fn main() {
let add_eleven = int.add(_, 11)
list.map([1, 2, 3], add_eleven)
}
If your cursor is within the function capture then code action will be suggested, and if run the code will be updated to this:
pub fn main() {
list.map([1, 2, 3], fn(value) { int.add(value, 11) })
}
Extract constant
This code action assigns assigns an expression to a constant.
const first = "Hello, Mike!"
pub fn main() {
list.each([first, "Hello, Joe!"], io.println)
}
If your cursor is within the list then code action will be suggested, and if run the code will be updated to this:
const first = "Hello, Mike!"
const values = [first, "Hello, Joe!"]
pub fn main() {
list.each(values, io.println)
}
Extract variable
This code action assigns assigns an expression to a variable.
pub fn main() {
list.each(["Hello, Mike!", "Hello, Joe!"], io.println)
}
If your cursor is within the list then code action will be suggested, and if run the code will be updated to this:
pub fn main() {
let value = ["Hello, Mike!", "Hello, Joe!"]
list.each(value, io.println)
}
Fill labels
This code action can add any expected labels to a call.
pub fn main() {
Date()
}
If your cursor is within the Date() import the code action will be suggested,
and if run the code will be updated to this:
pub fn main() {
Date(year: todo, month: todo, day: todo)
}
Fill unused fields
This code action can add any fields that were not matched on in a pattern.
pub type Pokemon {
Pokemon(id: Int, name: String, moves: List(String))
}
pub fn main() {
let Pokemon(..) = todo
}
If your cursor is within the .. import the code action will be suggested, and
if run the code will be updated to this:
pub type Pokemon {
Pokemon(id: Int, name: String, moves: List(String))
}
pub fn main() {
let Pokemon(id:, name:, moves:) = todo
}
Generate decoder
This code action can generate a dynamic decoder function from a custom type definition.
pub type Person {
Person(name: String, age: Int)
}
If your cursor is within the "Person" type name then the code action will be
suggested, and if run the code will be updated to this:
import gleam/dynamic/decode
pub type Person {
Person(name: String, age: Int)
}
fn person_decoder() -> decode.Decoder(Person) {
use name <- decode.field("name", decode.string)
use age <- decode.field("age", decode.int)
decode.success(Person(name:, age:))
}
Generate function
This code action can generate the definition of a local function that is being used but does not yet exist.
pub fn main() {
let items = [1, 2, 3]
io.println(describe(items))
}
If your cursor is within describe then the code action will be suggested, and
if run the code will be updated to this:
import gleam/io
pub fn main() {
let items = [1, 2, 3]
io.println(describe(items))
}
fn describe(list: List(Int) -> String {
todo
}
Generate to-JSON function
This code action can generate a function that turns a custom type value into
JSON using the gleam_json library.
pub type Person {
Person(name: String, age: Int)
}
If your cursor is within pub type Person { definition then the code action
will be suggested, and if run the code will be updated to this:
import gleam/json
pub type Person {
Person(name: String, age: Int)
}
fn person_to_json(person: Person) -> json.Json {
let Person(name:, age:) = person
json.object([
#("name", json.string(person.name)),
#("age", json.int(person.age)),
])
}
Inexhaustive let to case
This code action can convert from let to case when the pattern is not
exhaustive.
pub fn unwrap_result(result: Result(a, b)) -> a {
let Ok(inner) = result // error: inexhaustive
inner
}
If your cursor is within the let assignment then the code action will be
suggested, and if run the code will be updated to this:
pub fn unwrap_result(result: Result(a, b)) -> a {
let inner = case result {
Ok(inner) -> inner
Error(_) -> todo
}
inner
}
Inline variable
This code action can inline a variable that is used only once.
pub fn main() {
let greeting = "Hello!"
echo greeting
}
If your cursor is within the greeting variable then the code action will be
suggested, and if run the code will be updated to this:
pub fn main() {
echo "Hello!"
}
Interpolate string
This code action can split a string in order to interpolate a value.
pub fn greet(name: String) -> String {
"Hello, !"
}
If your cursor is before the ! character in the string then the code action
will be suggested, and if run the code will be updated to this:
pub fn greet(name: String) -> String {
"Hello, " <> todo <> "!"
}
If the cursor is selecting a valid Gleam name then that will be used as a variable name in the interpolation.
pub fn greet(name: String) -> String {
"Hello, name!"
// ^^^^ This is selected
}
pub fn greet(name: String) -> String {
"Hello, " <> name <> "!"
}
Pattern match
This code action can generate an exhaustive case expression for variable or argument.
import gleam/list
pub fn run(items: List(Int)) -> Nil {
let result = list.first(items)
}
If your cursor is within the result assignment then the code action will be
suggested, and if run the code will be updated to this:
import gleam/list
pub fn run(items: List(Int)) -> Nil {
let result = list.first(items)
case result {
Ok(value) -> todo
Error(value) -> todo
}
}
Qualify and unqualify
These code actions can be used to add or remove module qualifiers for types and values.
import gleam/option.{Some}
pub fn main() {
[Some(1), Some(2)]
}
If your cursor is within one of the Somes then the “qualify” code action will
be suggested, and if run the code will be updated to this:
import gleam/option.{}
pub fn main() {
[option.Some(1), option.Some(2)]
}
Note that the import statement has been updated as needed, and all instances of
the Some constructor in the module have been qualified.
The “unqualify” action behaves the same, except it removes module qualifiers.
The “unqualify” action is available for types and custom type variants constructors. The “qualify” action is available for all types and values.
Remove block
This code action removes blocks from around single expressions.
pub fn greet(name: String) {
case name {
"Joe" -> { "Hello, Joe!" }
_ -> "Hi " <> name
}
}
If your cursor is within the block containing the "Hello, Joe!" string the code action will be suggested, and if run the module will be updated to this:
pub fn greet(name: String) {
case name {
"Joe" -> "Hello, Joe!"
_ -> "Hi " <> name
}
}
Remove echo
This code action removes echo expressions, useful for once you have finished
debugging.
pub fn main() {
echo 1 + 2
}
If your cursor is within the echo the code action will be suggested, and if
run the module will be updated to this:
pub fn main() {
1 + 2
}
If your module has multiple echo expressions within it they will all be
removed.
Remove opaque from private type
This code action removes redundant opaque from private types, where it does
nothing.
opaque type Direction {
Up
Down
}
If your cursor is within the opaque the code action will be suggested, and if
run the module will be updated to this:
type Direction {
Up
Down
}
Remove redundant tuples
This code action removes redundant tuples from case expression subjects and patterns.
case #(a, b) {
#(1, 2) -> todo
_ -> todo
}
If your cursor is within the case expression the code action will be suggested, and if run the module will be updated to this:
case a, b {
1, 2 -> todo
_, _ -> todo
}
Remove unreachable clauses
This code action removes unreachable clauses from case expressions.
pub fn main() {
case find_user() {
Ok(person) -> todo
Ok(Admin) -> todo
Ok(User) -> todo
Error(_) -> todo
}
}
If your cursor is within one of the unreachable clauses the code action will be suggested, and if run the module will be updated to this:
pub fn main() {
case find_user() {
Ok(person) -> todo
Error(_) -> todo
}
}
Remove unused imports
This code action can be used to delete unused import statements from a module.
import gleam/io
import gleam/list
pub fn main() {
io.println("Hello, Joe!")
}
If your cursor is within the unused import gleam/list import the code action
will be suggested, and if run the module will be updated to this:
import gleam/io
pub fn main() {
io.println("Hello, Joe!")
}
Use label shorthand syntax
This code action updates calls and patterns to use the label shorthand syntax.
case date {
Day(day: day, month: month, year: year) -> todo
}
If your cursor is within the call that could use the shorthand syntax the code action will be suggested, and if run the module will be updated to this:
case date {
Day(day:, month:, year:) -> todo
}
Wrap in block
This code action wraps an assignment value or case expression clause value in a block, making it easy to add additional expressions.
pub fn f(pokemon_type: PokemonType) {
case pokemon_type {
Water -> soak()
Fire -> burn()
}
}
If your cursor is within the soak call the code action will be suggested, and
if run the module will be updated to this:
pub fn f(pokemon_type: PokemonType) {
case pokemon_type {
Water -> {
soak()
}
Fire -> burn()
}
}
Security
The language server does not perform code generation or compile Erlang or Elixir code, so there is no chance of any code execution occurring due to opening a file in an editor using the Gleam language server.
Use outside Gleam projects
The language server is unable to build Gleam code that are not in Gleam projects. When one of these files is opened the language server will provide code formatting but other features are not available.