Launching JETLS
This guide explains how to launch the JETLS language server using jetls serve and describes the available communication channels.
The jetls executable
The jetls executable is the main entry point for JETLS, providing four commands:
jetls serve- Start the language server for editor integration (this page)jetls check- Run diagnostics from the command linejetls test- Run tests with TestRunner.jl from the command linejetls schema- Print JSON Schema for JETLS configuration
The executable can be installed as an executable app via Pkg.jl:
julia -e 'using Pkg; Pkg.Apps.add(; url="https://github.com/aviatesk/JETLS.jl", rev="release")'
jetls serve --help
jetls serve - Start language server for editor integration
Starts JETLS as an LSP server for use with LSP client editors.
By default, communicates via stdin/stdout.
Usage: jetls serve [OPTIONS]
Options:
--stdio Use standard input/output (default)
--pipe-connect=<path> Connect to client's Unix domain socket/named pipe
--pipe=<path> Alias for --pipe-connect (LSP client convention)
--pipe-listen=<path> Listen on Unix domain socket/named pipe
--socket=<port> Listen on TCP socket
--clientProcessId=<pid> Monitor client process (enables crash detection)
--help, -h Show this help message
Examples:
jetls serve
jetls serve --pipe-listen=/tmp/jetls.sock
jetls serve --socket=8080
Communication channels
jetls serve supports multiple communication channels between the client and server. Choose based on your environment and requirements:
pipe-connect / pipe-listen (Unix domain socket / named pipe)
- Advantages: Complete isolation from
stdin/stdout, preventing protocol corruption; fastest for local communication - Best for: Local development, Remote SSH, WSL
- Limitations: Not suitable for cross-container communication
- Note: Client is responsible for socket file cleanup in both modes
jetls serve provides two pipe modes:
pipe-connect
Server connects to a client-created socket. This is the mode used by the jetls-client VSCode extension and is generally easier to implement:
- Client creates and listens on the socket first
- Client spawns the server process
- Server immediately connects to the client's socket
- No stdout monitoring required - simpler client implementation
Example:
jetls serve --pipe-connect=/tmp/jetls.sock--pipe=<path> is accepted as an alias for --pipe-connect=<path>. This matches the flag convention stock LSP clients use for their pipe transport (e.g. vscode-languageclient spawns the server with --pipe=<name>).
pipe-listen
Server creates and listens on a socket, then waits for the client to connect. This is the traditional LSP server mode:
- Client spawns the server process
- Server creates socket and prints
<JETLS-PIPE-READY>/tmp/jetls.sock</JETLS-PIPE-READY>to stdout - Client must monitor stdout for the readiness notification
- Client connects to the socket after receiving notification
Example:
jetls serve --pipe-listen=/tmp/jetls.socksocket (TCP)
- Advantages: Complete isolation from
stdin/stdout, preventing protocol corruption; works across network boundaries; supports port forwarding - Best for: Manual remote connection across different machines (without VSCode Remote); shared server accessed by multiple developers
- Limitations: May require firewall configuration; potentially less secure than local alternatives
Example:
jetls serve --socket=7777The server will print <JETLS-PORT>7777</JETLS-PORT> to stdout once it starts listening. This is especially useful when using --socket=0 for automatic port assignment, as the actual port number will be announced:
jetls serve --socket=0
# Output: <JETLS-PORT>54321</JETLS-PORT> (actual port assigned by OS)Use with SSH port forwarding to connect from a different machine:
ssh -L 8080:localhost:8080 user@remote
# Then connect your local client to localhost:8080stdio
- Advantages: Simplest setup; maximum compatibility; works everywhere
- Best for: Dev containers; environments where
pipedoesn't work - Limitations: Risk of protocol corruption if any code writes to
stdin/stdout
Example:
jetls serve --stdio
# or simply
jetls serveWhen using stdio mode, any println(stdout, ...) in your code or dependency packages may corrupt the LSP protocol and break the connection. Prefer pipe or socket modes when possible.
Client process monitoring
The --clientProcessId option enables the server to monitor the client process for crash detection, where the server periodically checks whether the specified process is still alive. If the client crashes or terminates unexpectedly, the server will automatically shut down, ensuring proper cleanup even when the client cannot execute the normal LSP shutdown sequence.
When specified via command line, the process ID should match the processId field that the client sends in the LSP initialize request parameters.
Initialization options
JETLS accepts static initialization options that are set once at server startup and require a server restart to take effect. Unlike dynamic configuration that can be changed at runtime, these options configure fundamental server behavior.
Schema
A JSON Schema for initialization options is available. See the JSON Schema section for download links.
How to configure initialization options
Initialization options can be configured via:
- Editor-specific settings
- The
[initialization_options]section in.JETLSConfig.toml
When both sources are present, file-based configuration takes precedence (file > client > default), as like JETLS configuration priority.
File-based configuration
Configure initialization options in .JETLSConfig.toml at your project root:
[[initialization_options.analysis_overrides]]
path = "test/fixtures/**"This method is client-agnostic and can be easily committed to version control.
Client configuration
The method and format for specifying initialization options varies by editor. Consult your editor's documentation on how to configure LSP initialization options. Below are examples for VSCode and Zed.
If your editor does not support specifying initialization options, use the file-based configuration instead.
VSCode (jetls-client extension)
Configure initialization options in VSCode's settings.json:
{
"jetls-client.initializationOptions": {
"analysis_overrides": [
{
"path": "test/fixtures/**"
}
]
}
}Zed (JuliaEditorSupport/zed-julia extension)
Configure initialization options in Zed's settings.json:
{
"lsp": {
"jetls": {
"initialization_options": {
"analysis_overrides": [
{
"path": "test/fixtures/**"
}
]
}
}
}
}Options reference
analysis_overrides
- Type: array of tables
- Default:
[]
Excludes the matched files from full analysis. This is primarily a workaround for the known memory leak, where memory usage grows with each re-analysis: limiting costly analysis helps keep the server usable until the leak is fixed.
Each entry requires a path field: a glob pattern selecting the files. Patterns are matched against file paths relative to the workspace root, and ** matches directories recursively. Use / as the separator on all platforms, including Windows; backslashes are not interpreted as separators.
# Exclude test fixtures from full analysis
[[initialization_options.analysis_overrides]]
path = "test/fixtures/**"Matched files receive no code-loading or inference diagnostics. Instead, they report toplevel/analysis-skipped at their first line. Live analysis keeps working with a fallback module context; see Files without full analysis for what remains available.
The exclusion also applies to files reached through include. The optional on_include field selects what full analysis does at such an include:
"stop"(default): full analysis reportstoplevel/analysis-stoppedat the call site and stops that analysis unit before loading the excluded file."skip": full analysis reportstoplevel/analysis-skippedat the call site and continues without loading the excluded file. Use this only when the code after theincludedoes not depend on the excluded file, e.g. for a test file that runs its own test set; otherwise, the missing definitions cause false diagnostics in the rest of the unit.
# Analyze `test/runtests.jl` except for an expensive test file it includes
[[initialization_options.analysis_overrides]]
path = "test/test_expensive.jl"
on_include = "skip"See Excluding files from full analysis.
analysis_overrides is provided as a temporary workaround and may be removed or changed at any time.
reuse_native_inference
- Type: boolean
- Default:
false
When enabled, calls to methods defined outside the modules being analyzed reuse results from Julia's native inference cache instead of being analyzed recursively.
This may substantially speed up full analysis of packages with large dependencies. The reused results come from the dependencies' precompiled images, so the speedup requires those dependencies to be precompiled, and grows with how much of their code precompilation covers. Packages whose analysis time is dominated by their own code see little change, while those that spend much of it in dependencies can become several times faster.
[initialization_options]
reuse_native_inference = trueAnalysis results may change slightly when this option is enabled, since the reused results come from Julia's compiler rather than the JETLS analyzer, and that can change the diagnostics that are reported. Diagnostics for the modules being analyzed are unaffected in practice, but this remains an experimental option that may be removed or changed in future releases.
configuration_section
- Type: string
- Default: unset
A contract for language client implementors rather than for users: the client declares the configuration section it stores JETLS settings under (e.g. "jetls-client.settings" for the VSCode extension), and JETLS then registers workspace/didChangeConfiguration with that section so the client only sends the notification when the relevant settings actually change. When unset, the notification is registered without a section filter.
pull_diagnostics
- Type: boolean
- Default:
false
A contract for language client implementors rather than for users: when set, JETLS serves the JETLS/live diagnostics of open files through textDocument/diagnostic (pull diagnostics) instead of pushing them via textDocument/publishDiagnostics so the client decides when they are computed and when they go away. This suits clients that tie pulled diagnostics to their editor state: the VSCode extension sets this option because VSCode clears pulled diagnostics as soon as a tab closes, while textDocument/didClose may arrive much later. Unopened files are pushed either way.