Skip to content

Repository files navigation

Zed Julia

This extension adds Julia support to Zed, powered by the JETLS language server.

Zed with JETLS completions and diagnostics

Quick links

Installation

  1. Install the latest version of Zed for your platform.
  2. Start Zed.
  3. Inside Zed, go to the extensions view by executing the zed: extensions command (or click Zed->Extensions).
  4. In the extensions view, simply search for the term julia in the search box, then select the extension named Julia and click the install button.

Julia executable

By default, JETLS and the built-in Julia tasks resolve julia from the worktree environment's PATH. No configuration is required if the desired Julia executable is already available there.

To select Julia per project, use an approved direnv file:

  • If you use juliaup, select the Julia channel by setting JULIAUP_CHANNEL:

    .envrc (project root)

    export JULIAUP_CHANNEL=1.13

    This takes effect when julia on the worktree PATH resolves to the juliaup launcher.

  • To put a specific Julia installation on PATH:

    .envrc (project root)

    JULIA_HOME="/path/to/julia"
    PATH_add "$JULIA_HOME/bin"

Run direnv allow after creating or modifying the file. This changes the Julia executable used by both JETLS and the built-in tasks.

Warning

Zed loads the worktree environment once per session, so .envrc changes (including direnv allow) do not affect an already-open project — not even after a language server restart. Reopen the project to apply them.

To override Julia only for JETLS, see Julia for JETLS; unlike .envrc edits, those settings apply with an automatic server restart.

Language server

Important

Version 0.2 is a breaking migration from LanguageServer.jl to JETLS. See Migrating to version 0.2 for the new requirements and configuration changes.

Warning

JETLS is a new language server and still experimental. Notably, it currently has a known memory leak issue where memory usage grows with each re-analysis (see the announcement for details and a workaround). If the language server causes problems, you can disable it and keep using the rest of the extension.

Julia for JETLS

JETLS requires Julia v1.12.2 through 1.13.x. To override only the Julia runtime used by managed JETLS without changing the worktree PATH, set binary.env in Zed settings. Unlike the direnv method, these overrides apply only to JETLS, not to the built-in tasks, and changing them restarts the server automatically:

  • If you use juliaup, select the Julia channel by setting JULIAUP_CHANNEL:

    ~/.config/zed/settings.json (global) or .zed/settings.json (per-project)

    {
      "lsp": {
        "jetls": {
          "binary": {
            "env": {
              "JULIAUP_CHANNEL": "1.13",
            },
          },
        },
      },
    }

    This takes effect when julia on the worktree PATH resolves to the juliaup launcher.

  • To specify a Julia executable directly, set the standard Pkg app runtime override JULIA_APPS_JULIA_CMD:

    ~/.config/zed/settings.json (global) or .zed/settings.json (per-project)

    {
      "lsp": {
        "jetls": {
          "binary": {
            "env": {
              "JULIA_APPS_JULIA_CMD": "/path/to/specific/julia/executable",
            },
          },
        },
      },
    }

    The command must be the Julia executable itself (on Windows, julia.exe rather than a .bat/.cmd wrapper), since the extension spawns it directly; JULIAUP_CHANNEL is ignored in this case.

The selected Julia executable is used to install and update JETLS, verify the installation, and launch the server.

Each executable and Julia minor version (e.g. 1.12 vs 1.13) combination uses a separate managed depot, so worktrees can select different Julia installations without repeatedly reinstalling JETLS, and Julia patch releases reuse the existing depot.

Automatic installation and updates

Each zed-julia release pins an exact JETLS release. Using the selected Julia executable, the extension automatically installs the pinned release as a Julia Pkg app the first time it is needed, and reinstalls it when a zed-julia update changes the pin. The initial installation may take several minutes while Julia resolves and precompiles JETLS.

Network access is required when JETLS is installed or updated. Every installation starts with a small fetch through Zed's downloader that verifies the pinned release, so disallowing downloads on the Zed side stops the installation before Julia's package manager starts any of its own downloads. Once the pinned release is installed, verifying and launching it is network-free, though the server may still access the network to instantiate a workspace package environment unless full_analysis.auto_instantiate is disabled.

JETLS is installed into a depot private to the extension — nothing creates or modifies a user-global ~/.julia/bin/jetls. The server launches with that depot first in JULIA_DEPOT_PATH and the user depot chain (~/.julia, or JULIA_DEPOT_PATH if set in the shell or binary.env) after it: everything the managed installation writes stays private, while packages and precompile caches already in the user depots are reused when analyzing workspace dependencies.

Tip

The managed depots live in the jetls-depots directory inside Zed's work directory for this extension (on macOS, for example, ~/Library/Application Support/Zed/extensions/work/julia/jetls-depots). Updates install into a fresh directory and are published atomically, so a failed or interrupted update never breaks the installation in use. Superseded installations, and installations for a Julia you stopped using, are cleaned up automatically after a retention period. It is always safe to delete jetls-depots or anything inside it to reclaim disk space: the pinned JETLS release is reinstalled automatically the next time the language server starts.

Launch configuration

Most users do not need any launch configuration. When the defaults do not fit your setup, customize how the server is launched in the lsp.jetls section of Zed settings.

Managed JETLS arguments

When binary.path is omitted, the extension launches the managed JETLS installation as:

julia --startup-file=no --history-file=no --threads=auto -m JETLS serve

Custom binary.arguments replace this entire argument list, so they must spell out the full julia invocation, including -m JETLS and exactly one serve subcommand. For example, to run the server on a single thread:

~/.config/zed/settings.json (global) or .zed/settings.json (per-project)

{
  "lsp": {
    "jetls": {
      "binary": {
        "arguments": [
          "--startup-file=no",
          "--history-file=no",
          "--threads=1",
          "-m",
          "JETLS",
          "serve",
        ],
      },
    },
  },
}

binary.arguments can be combined with the binary.env runtime overrides to pair custom arguments with a specific Julia channel or executable.

Running JETLS from a source checkout

To run JETLS from a local checkout or fork, point the launch at it with --project in binary.arguments, leaving binary.path unset:

~/.config/zed/settings.json (global) or .zed/settings.json (per-project)

{
  "lsp": {
    "jetls": {
      "binary": {
        "arguments": [
          "--startup-file=no",
          "--history-file=no",
          "--threads=auto",
          "--project=/path/to/JETLS/directory",
          "-m",
          "JETLS",
          "serve",
        ],
      },
    },
  },
}

When the arguments pass --project to julia (before -m JETLS), the launch provides its own JETLS instead of the managed installation. The extension still verifies the Julia version and that the configured server starts, but skips the managed installation and its pin verification, and the checkout resolves its dependencies against your regular Julia environment. Keep the checkout's environment instantiated: a broken checkout is reported before the server starts.

Custom JETLS command

Setting binary.path bypasses the extension entirely: Zed launches exactly the command you specify, with no version preflight, no default arguments, and no managed installation. Prefer the binary.arguments forms above when they can express your setup; use binary.path for a server the extension cannot launch itself, such as a JETLS binary you manage yourself with a user-global Pkg.Apps install:

~/.config/zed/settings.json (global) or .zed/settings.json (per-project)

{
  "lsp": {
    "jetls": {
      "binary": {
        "path": "~/.julia/bin/jetls",
        "arguments": ["serve"],
      },
    },
  },
}

The command must start a compatible language server over standard input and output. Zed accepts absolute paths, ~-relative paths, worktree-relative paths, and bare command names resolved through the worktree PATH. Install custom commands yourself, and complete any required compilation before starting the language server to avoid the LSP initialization timeout.

Server configuration

JETLS has dynamic configuration, which can change throughout the server's lifetime, and static initialization options, which are set once at server startup. Both can be set in a project-local .JETLSConfig.toml or in Zed settings. Dynamic configuration changes are applied to the running server automatically with either method, while changes to initialization options require restarting the language server.

Method 1: Project-specific configuration file

This method uses JETLS's native .JETLSConfig.toml configuration file and is therefore editor-agnostic: the same file configures JETLS in any editor, as well as its CLI (e.g. jetls check). Create a configuration file, e.g.:

.JETLSConfig.toml (project root)

# Use JuliaFormatter instead of Runic
formatter = "JuliaFormatter"

# Prevent JETLS from automatically instantiating the package environment
[full_analysis]
auto_instantiate = false

# Suppress unused argument warnings
[[diagnostic.patterns]]
pattern = "lowering/unused-argument"
match_by = "code"
match_type = "literal"
severity = "off"

# Reuse Julia's native inference cache for faster full analysis
[initialization_options]
reuse_native_inference = true

Method 2: Zed settings

This method uses JETLS's LSP-based configuration mechanism, which Zed supports natively: when you change initialization_options and save settings.json, Zed automatically restarts the server to apply them. Configure initialization options and server settings under the lsp.jetls section:

~/.config/zed/settings.json (global) or .zed/settings.json (per-project)

{
  "lsp": {
    "jetls": {
      "settings": {
        // Prevent JETLS from automatically instantiating the package
        // environment
        "full_analysis": {
          "auto_instantiate": false,
        },
        // Use JuliaFormatter instead of Runic
        "formatter": "JuliaFormatter",
        // Suppress unused argument warnings
        "diagnostic": {
          "patterns": [
            {
              "pattern": "lowering/unused-argument",
              "match_by": "code",
              "match_type": "literal",
              "severity": "off",
            },
          ],
        },
      },
      "initialization_options": {
        // Reuse Julia's native inference cache for faster full analysis
        "reuse_native_inference": true,
      },
    },
  },
}

Note

.JETLSConfig.toml takes precedence over editor settings when both are present.

For complete configuration details, see the JETLS documentation for configuration and initialization options.

Formatter integration

JETLS delegates formatting to an external formatter executable. Install the formatter you want to use as a Julia Pkg app and ensure that its executable is available on PATH. Julia Pkg apps are normally installed into ~/.julia/bin.

Runic

Runic is the default formatter:

julia -e 'using Pkg; Pkg.Apps.add("Runic")'

JETLS invokes the runic executable for document and range formatting.

JuliaFormatter

To use JuliaFormatter instead, install its jlfmt executable:

julia -e 'using Pkg; Pkg.Apps.add("JuliaFormatter")'

Then set formatter = "JuliaFormatter" using either configuration method above. Range formatting requires JuliaFormatter v2.7.0 or later.

For custom formatter executables and further details, see the JETLS formatter documentation.

Disabling the language server

If JETLS causes problems, you can disable it while keeping the rest of the extension working: syntax highlighting, built-in tasks, REPL integration, and so on. Add the following setting:

~/.config/zed/settings.json (global) or .zed/settings.json (per-project)

{
  "languages": {
    "Julia": {
      "enable_language_server": false,
    },
  },
}

Remove the setting to re-enable JETLS.

Migrating to version 0.2

Zed Julia extension v0.2 replaces the LanguageServer.jl backend with JETLS.jl. This is a breaking migration for existing users:

  • JETLS requires Julia v1.12.2 through 1.13.x. See Julia for JETLS for runtime selection.
  • The language-server identifier has changed from julia to jetls. Existing LanguageServer.jl settings under lsp.julia are not migrated or forwarded to JETLS; configure the new server under lsp.jetls.
  • The extension now installs and updates a pinned JETLS Julia Pkg app in an extension-private depot. The LanguageServer.jl environment previously used by the extension is no longer used.
  • JETLS uses Runic as its default formatter. See Formatter integration for installation instructions and the JuliaFormatter alternative.

If JETLS is not suitable for your current setup, you can disable the language server while continuing to use syntax highlighting, built-in tasks, and the other extension features.

Built-in tasks

The extension provides the following built-in tasks:

Task Operation
Julia: Pkg.jl instantiate Pkg.instantiate()
Julia: Pkg.jl precompile Pkg.precompile()
Julia: Pkg.jl update Pkg.update()
Julia: Pkg.jl resolve Pkg.resolve()
Julia: Pkg.jl test Pkg.test()

Open the command palette, run task: spawn (Cmd+Shift+R on macOS or Alt+Shift+T on Linux and Windows), and select a task. Each task uses the julia command from the worktree environment and activates the project at ZED_WORKTREE_ROOT. Note that the Pkg.jl operations may modify the project environment. The task terminal is hidden automatically when the command succeeds.

Running code in the REPL

This section describes how to select Julia code in the editor and run it in Zed's integrated terminal. This is more of a workaround than a full integration. Currently, there is no inline code execution as in VSCode. On the other hand, the language server is not required to make this work.

  1. Open a .jl file in the editor.

  2. From the command palette, run open in terminal. This opens a new terminal in the worktree root (where the Project.toml lives). You can also right-click in the editor and use the context menu or press ctrl-shift-` as defined in the JSON example below.

  3. In the terminal, start the REPL with julia --project.

  4. Now it's time to select some code in the editor, copy it to the clipboard, paste it into the terminal, execute it, and go back to the editor. To make that less tedious, add one or more of the following key bindings. Change the ctrl-shift-f10/11/12 combinations to your liking.

    Note: interacting with the terminal requires sending keystrokes. In the examples, cmd-v is used to paste code. Please adjust this binding for your operating system.

    ~/.config/zed/keymap.json (can be opened via zed: open keymap file)

    [
      {
        // Set the focus back to the editor without hiding the terminal.
        // This is an auxiliary binding used by other bindings.
        "context": "Terminal",
        "bindings": { "ctrl-shift-`": "terminal_panel::ToggleFocus" },
      },
      {
        "context": "Editor && mode == full",
        "bindings": {
          // Open a new terminal and change to the worktree root directory.
          "ctrl-shift-`": "workspace::OpenInTerminal",
    
          // Execute the whole line the cursor is on and move the cursor to
          // the next line. Invoke this binding repeatedly to run line by
          // line.
          "ctrl-shift-f10": [
            "action::Sequence",
            [
              "editor::SelectLine",
              "editor::Copy",
              "editor::MoveRight",
              ["workspace::SendKeystrokes", "ctrl-` cmd-v ctrl-shift-`"],
            ],
          ],
    
          // Execute the enclosing top level block e.g., a function
          // definition. Note the additional keystroke "enter" to actually
          // execute the code.
          "ctrl-shift-f11": [
            "action::Sequence",
            [
              "editor::SelectEnclosingSymbol",
              "editor::CopyAndTrim",
              ["workspace::SendKeystrokes", "ctrl-` cmd-v enter ctrl-shift-`"],
            ],
          ],
    
          // Execute the paragraph (a block surrounded by blank lines).
          "ctrl-shift-f12": [
            "action::Sequence",
            [
              "editor::MoveToStartOfParagraph",
              "editor::SelectToEndOfParagraph",
              "editor::Copy",
              ["workspace::SendKeystrokes", "ctrl-` cmd-v ctrl-shift-`"],
            ],
          ],
        },
      },
    ]

Plot side pane

For plot support in Zed, we recommend using ZedPlotPane.jl.

To install it, run the following in your Julia REPL:

using Pkg
Pkg.add("ZedPlotPane")

To enable the plot pane, load ZedPlotPane (using ZedPlotPane) in your Julia REPL before loading Plots or any other plotting package.

The first plot will create and open ~/.cache/zed-julia/current-plot.png. Drag that tab into a side pane; subsequent plots will update there automatically.

If you close the plot pane, you can re-open it by running ZedPlotPane._open_viewer() in the REPL.

For more information on how to use it, please refer to the ZedPlotPane.jl documentation.

Using Zed from the Julia REPL

Zed is currently not on the list of Julia's predefined editors. You can register it in your Julia startup file:

~/.julia/config/startup.jl

atreplinit() do repl
    InteractiveUtils.define_editor("zed") do cmd, path, line, column
        `$cmd $path:$line:$column`
    end
end

Set the environment variable EDITOR (or VISUAL or JULIA_EDITOR, whatever you use) to zed --wait. Then, using InteractiveUtils.edit etc. will open the document in Zed.

Customizing syntax highlighting

You can change the foreground color and text attributes of syntax tokens, for instance:

~/.config/zed/settings.json

{
  "theme_overrides": {
    "One Dark": {
      "syntax": {
        "comment.doc": {
          "font_style": "italic",
        },
        "function.definition": {
          "color": "#0000AA",
          "font_weight": 700,
        },
      },
    },
  },
}

See Syntax Highlighting and Themes and Tree-sitter Queries for further details.

Syntax tokens are called captures in tree-sitter jargon. The following table lists all captures provided by zed-julia. Some captures have default values (defined in Zed's color themes) and the other captures fall back to one of the defaults. Depending on your color theme, some captures may be set to the editor's foreground color or to a very similar one. In this case, try to assign a different color to improve the contrast.

Capture Is there a default value? Note/Example
boolean yes
comment yes line or block comment
comment.doc yes docstring
constant yes
constant.builtin no, falls back to constant core julia built-in
function.builtin no, falls back to function core julia built-in
function.call no, falls back to function name of the called function
function.definition no, falls back to function name of the defined function
function.macro no, falls back to function name of the macro
keyword yes
keyword.conditional no, falls back to keyword if, else
keyword.conditional.ternary no, falls back to keyword ? :
keyword.exception no, falls back to keyword try, catch
keyword.function no, falls back to keyword function, do, short function definition: =
keyword.import no, falls back to keyword im/export, using, module definition
keyword.operator no, falls back to keyword in, isa, where
keyword.repeat no, falls back to keyword for, while
keyword.return no, falls back to keyword return
keyword.type no, falls back to keyword struct or type definition
label yes label name for @label, @goto
number yes
number.float no, falls back to number
operator yes
punctuation.bracket yes (), [], {}
punctuation.delimiter yes ,, ;, ::
punctuation.special yes ., ..., string interpolation $
string yes
string.escape yes escape sequence
string.special yes command literal
string.special.symbol yes quote expression
type yes
type.builtin no, falls back to type core julia built-in
type.definition no, falls back to type
variable yes
variable.builtin no, falls back to variable core julia built-in: begin and end in indices
variable.member no, falls back to variable example: in foo.bar, the member is bar

About

Julia support for Zed.

Resources

Contributing

Stars

119 stars

Watchers

6 watching

Forks

Releases

Used by

Contributors

Languages