Skip to content

aviatesk/JETLS.jl

Repository files navigation

JETLS.jl

The goal of this project is to develop a new language server for Julia, currently called "JETLS.jl". This language server aims to enhance developer productivity by providing advanced static analysis and seamless integration with the Julia runtime. By leveraging tooling technologies like JET.jl, JuliaSyntax.jl and JuliaLowering.jl, JETLS aims to offer enhanced language features such as type-sensitive diagnostics, macro-aware go-to definition and such.

This repository manages JETLS.jl, a Julia package that implements a language server, and jetls-client, a sample VSCode extension that serves as a language client for testing JETLS.jl. For information on how to use JETLS.jl with other frontends, please refer to the Other editors section.

Requirements

  • VSCode v1.93.0 or higher
  • npm v11.0.0 or higher
  • Julia v"1.12.0-beta2" or higher

Steps

  • Run julia --project=. -e 'using Pkg; Pkg.instantiate()' in this folder to install all necessary Julia packages.
  • Run npm install in this folder to install all necessary node modules for the client.
  • Open this folder in VSCode.
  • Press Ctrl+Shift+B to start compiling the client and server in watch mode.
  • Switch to the Run and Debug View in the Sidebar (Ctrl+Shift+D).
  • Select Launch Client from the drop-down menu (if it is not already selected).
  • Press to run the launch configuration (F5).
  • In the Extension Development Host instance of VSCode, open a Julia file.

Development Note

Coding Guidelines

This section contains meta-documentation related to development. For more detailed coding guidelines, please refer to CLAUDE.md, which has been organized to be easily recognized by AI agents (Claude models in particular).

AI-Assisted Development

When working with AI agents for development, consider the following tips:

  • AI agents generally produce highly random code without test code to guide them, yet they often struggle with writing quality test code themselves. Thus the recommended approach is to prepare solid test code yourself first, then ask the agent to implement the functionality based on these tests.
  • AI agents will run the entire JETLS test suite using Pkg.test() if not specified otherwise, but as mentioned above, for best results, it's better to include which test code/files to run in your prompt.
  • You can have the ./julia script in the root directory of this repository to specify which Julia binary should be used by agents. If the script doesn't exist, the agent will default to using the system's julia command. For example, you can specify a local Julia build by creating a ./julia script like this:

    ./julia

    #!/usr/bin/env bash
    exec /path/to/julia/usr/bin/julia "$@"
    The ./julia script is gitignored, so it won't be checked into the git tree.

[sources] Dependencies

In JETLS, since we need to use packages that aren’t yet registered (e.g., JuliaLowering.jl) or specific branches of JET.jl and JuliaSyntax.jl, the Project.toml includes [sources] section. The [sources] section allows simply running Pkg.instantiate() to install all the required versions of these packages on any environment, including the CI setup especially.

On the other hand, it can sometimes be convenient to Pkg.develop some of the packages listed in the [sources] section and edit their source code while developing JETLS. In particular, to have Revise immediately pick up changes made to those packages, we may need to keep them in locally editable directories. However, we cannot run Pkg.develop directly on packages listed in the [sources] section, e.g.:

julia> Pkg.develop("JET")
ERROR: `path` and `url` are conflicting specifications
...

To work around this, you can temporarily comment out the [sources] section and run Pkg.develop("JET"). This lets you use any local JET implementation. After running Pkg.develop("JET"), you can restore the [sources] section, and perform any most of Pkg operations without any issues onward. The same applies to the other packages listed in [sources].

JETLS_DEV_MODE

JETLS has a development mode that can be enabled through the JETLS_DEV_MODE preference. When this mode is enabled, the language server enables several features to aid in development:

  • Automatic loading of Revise when starting the server, allowing changes to be applied without restarting
  • try/catch block is added for the top-level handler of non-lifecycle-related messages, allowing the server to continue running even if an error occurs in each message handler, showing error messages and stack traces in the output panel

You can configure JETLS_DEV_MODE using Preferences.jl:

julia> using Preferences

julia> Preferences.set_preferences!("JETLS", "JETLS_DEV_MODE" => true; force=true) # enable the dev mode

Alternatively, you can directly edit the LocalPreferences.toml file.

While JETLS_DEV_MODE is disabled by default, we strongly recommend enabling it during JETLS development. For development work, we suggest creating the following LocalPreferences.toml file in the root directory of this repository:

LocalPreferences.toml

[JETLS] # enable the dev mode of JETLS
JETLS_DEV_MODE = true

[JET] # additionally, allow JET to be loaded on nightly
JET_DEV_MODE = true

Note that in tests, this mode is always disabled to ensure that internal errors are properly raised rather than being suppressed by the additional try/catch block (see test/LocalPreferences.toml).

Dynamic Registration

This language server supports dynamic registration of LSP features.

With dynamic registration, for example, the server can switch the formatting engine when users change their preferred formatter, or disable specific LSP features upon configuration change, without restarting the server process (although neither of these features has been implemented yet).

Dynamic registration is also convenient for language server development. When enabling LSP features, the server needs to send various capabilities and options to the client during initialization. With dynamic registration, we can rewrite these activation options and re-enable LSP features dynamically, i.e. without restarting the server process.

For example, you can dynamically add , as a triggerCharacter for "completion" as follows. First, launch jetls-client in VSCode1, then add the following diff to unregister the already enabled completion feature. Make a small edit to the file the language server is currently analyzing to send some request from the client to the server. This will allow Revise to apply this diff to the server process via the dev mode callback (see runserver.jl), which should disable the completion feature:

diff --git a/src/completions.jl b/src/completions.jl
index 29d0db5..728da8f 100644
--- a/src/completions.jl
+++ b/src/completions.jl
@@ -21,6 +21,11 @@ completion_options() = CompletionOptions(;
 const COMPLETION_REGISTRATION_ID = "jetls-completion"
 const COMPLETION_REGISTRATION_METHOD = "textDocument/completion"

+let unreg = Unregistration(COMPLETION_REGISTRATION_ID, COMPLETION_REGISTRATION_METHOD)
+    unregister(currently_running, unreg)
+end
+
 function completion_registration()
     (; triggerCharacters, resolveProvider, completionItem) = completion_options()
     documentSelector = DocumentFilter[

Tip

You can add the diff above anywhere Revise can track and apply changes, i.e. any top-level scope in the JETLS module namespace or any subroutine of _handle_message that is reachable upon the request handling.

Warning

Note that currently_running::Server is a global variable that is only defined in JETLS_DEV_MODE. The use of this global variable should be limited to such development purposes and should not be included in normal routines.

After that, delete that diff and add the following diff:

diff --git a/src/completions.jl b/src/completions.jl
index 29d0db5..7609a6a 100644
--- a/src/completions.jl
+++ b/src/completions.jl
@@ -9,6 +9,7 @@ const COMPLETION_TRIGGER_CHARACTERS = [
     "@",  # macro completion
     "\\", # LaTeX completion
     ":",  # emoji completion
+    ",",  # new trigger character
     NUMERIC_CHARACTERS..., # allow these characters to be recognized by `CompletionContext.triggerCharacter`
 ]

@@ -36,6 +37,8 @@ function completion_registration()
             completionItem))
 end

+register(currently_running, completion_registration())
+
 # completion utils
 # ================

This should re-enable completion, and now completion will also be triggered when you type ,.

For these reasons, when adding new LSP features, check whether the feature supports dynamic/static registration, and if it does, actively opt-in to use it. That is, register it via the client/registerCapability request in response to notifications sent from the client, most likely InitializedNotification. The JETLS.register utility is especially useful for this purpose.

Other Editors

  • Minimal Emacs (eglot client) setup:

    (add-to-list 'eglot-server-programs
                 '(((julia-mode :language-id "julia")
                    (julia-ts-mode :language-id "julia"))
                   "julia"
                   "--startup-file=no"
                   "--project=/path/to/JETLS.jl"
                   "/path/to/JETLS.jl/runserver.jl"))
  • Minimal Neovim setup (requires Neovim v0.11):

    vim.lsp.config("jetls", {
        cmd = {
           "julia",
           "--startup-file=no",
           "--project=/path/to/JETLS.jl",
           "/path/to/JETLS.jl/runserver.jl",
        },
        filetypes = {"julia"},
    })
    vim.lsp.enable("jetls")
  • Zed extension for Julia/JETLS is available: See aviatesk/zed-julia#avi/JETLS

Footnotes

  1. Of course, the hack explained here is only possible with clients that support dynamic registration. VSCode is currently one of the frontends that best supports dynamic registration.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published