Hi everyone. I’m new to Julia and language servers. I’ve set up Eglot with eglot-jl and it seems to be working fine. The only thing that’s off is that Eglot takes 15 to 30 s to start up. It’s not a big deal but it got me wondering if I misconfigured something.
I’m running Emacs 30.0.50 with the latest version of the packages as of yesterday. My emacs config goes like this:
;; LSP
(setq eglot-connect-timeout 60)
(setq eglot-autoshutdown t)
(setq eglot-ignored-server-capabilities '(:documentHighlightProvider))
;; Adapted from trev-dev’s configuration on https://github.com/joaotavora/eglot/discussions/993
(with-eval-after-load 'eglot
(define-key eglot-mode-map (kbd "C-c p D") #'eglot-find-typeDefinition)
(define-key eglot-mode-map (kbd "C-c p d") #'eglot-find-declaration)
(define-key eglot-mode-map (kbd "C-c p i") #'eglot-find-implementation)
(define-key eglot-mode-map (kbd "C-c p r") #'eglot-rename)
(define-key eglot-mode-map (kbd "C-c p f") #'eglot-format)
(define-key eglot-mode-map (kbd "C-c p F") #'eglot-format-buffer))
;; Julia
(with-eval-after-load 'julia-mode
(eglot-jl-init))
(with-eval-after-load 'julia-repl
(julia-repl-set-terminal-backend 'vterm))
(setq julia-repl-skip-comments t)
(defun set-up-julia-mode ()
"Function with customizations for ‘julia-mode’."
(add-hook 'before-save-hook #'delete-trailing-whitespace
nil 'local)
(display-fill-column-indicator-mode)
(setq-local electric-pair-text-syntax-table
text-syntax-table)
(eglot-ensure)
(julia-repl-mode))
(add-hook 'julia-mode-hook #'set-up-julia-mode)
It’s not unusual that the language server takes a long time to start up, see e.g. this FAQ entry:
That being said, things have dramatically improved lately, especially with new Julia versions. What version of Julia are you using? Also be aware that start up might especially slow the first time you’re using the language server in a project, or right after you updated something (either Julia or eglot-jl or the language server)
Hi @ffevotte, thanks. I have Julia v. 1.9.2. I’ve been configuring Emacs for Julia by tweaking settings and relaunching it always on the same file, so as not to unsettle what had been going on in the background. The first time it did take longer, but after that I was still seeing some
[stderr] [ Info: Downloading caches...
in the *EGLOT … events* buffer at startup, which got me wondering if caching has some problems.
Also, I was in doubt about when I should call eglot-jl-init. eglot-jl’s readme says that
If eglot-jl-init has been run in your emacs session, running eglot in a julia-mode buffer will start a language server for the Julia project to which the buffer belongs.
so I put it in a (with-eval-after-load 'julia-mode …) form, but I’m not 100% sure about this choice.
IIRC, 1.9 was already faster than previous versions. But I think v1.10 introduced new improvements in terms of startup times.
It might simply be that caching is not finished, but AFAIU this does not prevent the language server to start. It merely reduces its usefulness until all packages are correctly indexed.
It looks like what you’re doing is OK.
If you’re still bothered about startup times, you can try updating everything:
update Julia to v1.10
update the version of LanguageServer.jl internally used by eglot-jl.
There are several ways to update the LanguageServer. If you don’t want to risk breaking your existing installation of eglot-jl, one way could be along the lines of
shell$ mkdir -p some/path/of/your/choosing/eglot-jl-env
shell$ cd some/path/of/your/choosing/eglot-jl-env
shell$ julia --project=. -e 'import Pkg; Pkg.add(["LanguageServer", "SymbolServer"])'
I’m taking note of this and I might go back to it. Not right now because I don’t want to change too many things I don’t understand much about. Thanks again
The LS should be printing some metrics for startup times, which may help diagnose what’s going on here. 20s until init finishes doesn’t sound particularly unrealistic though.
In buffer *EGLOT (project-name) events*, you can look for the first request/response exchange between client and server. Look for lines like the following at the beginning of the file:
[internal] Mon Feb 5 13:48:21 2024:
...
[client-request] (id:1) Mon Feb 5 13:48:21 2024:
...
[stderr] No Changes to `~/.emacs.d/elpa/eglot-jl-20221128.1655/Project.toml`
[stderr] No Changes to `~/.emacs.d/elpa/eglot-jl-20221128.1655/Manifest.toml`
[stderr] [ Info: Environment successfully resolved
[stderr] ┌ Info: Running language server
[stderr] │ env = "/home/francois/.emacs.d/elpa/eglot-jl-20221128.1655/Project.toml"
[stderr] │ src_path = "......"
[stderr] │ project_path = "......"
[stderr] └ depot_path = ""
[server-reply] (id:1) Mon Feb 5 13:48:25 2024:
Here, it says that eglot itself started at 14:48:21, it sent the first request right away, then the server initialized and sent its first reply at 13:48:25. This means that after 4s the server was responsive (maybe not entirely operational, though).
Now look at the output of M-xeglot-stderr-buffer. Near the end you’ll see a report like:
============== Startup timings ==============
0.0 - LS startup started (0.0s since last event)
0.0078921 - connection established (0.0078921s since last event)
0.062229 - (async) listening to client events (0.054337s since last event)
0.093183 - (async) listening to symbol server events (0.030954s since last event)
0.093212 - starting combined listener (2.9087e-5s since last event)
0.13288 - LSP/initialize (0.039665s since last event)
1.3817 - LSP/initialized (1.2488s since last event)
4.5315 - LSP/textDocument/didOpen (3.1498s since last event)
4.8939 - LSP/textDocument/didOpen (0.36242s since last event)
4.9523 - LSP/textDocument/didOpen (0.05841s since last event)
4.9984 - LSP/textDocument/didOpen (0.046067s since last event)
5.0427 - LSP/workspace/didChangeConfiguration (0.044282s since last event)
8.9397 - symbols received (3.897s since last event)
8.9467 - extended methods computed (0.0069768s since last event)
8.9467 - project deps computed (1.7166e-5s since last event)
8.947 - env map computed (0.00033784s since last event)
9.3886 - initial lint done (0.44151s since last event)
=============================================
AFAIU this means it took 9-10s for the Language Server to become fully operational and perform its first linting of the entire project (which is a rather large project, in this instance).
Now the start up time is down to 8 s By the way, I measure this time after running emacs my_file.jl, counting the seconds from when the buffer becomes visible to when Eglot’s things show up in the mode line. Now I get the timings. After the second launch:
Startup timings
[stderr] ============== Startup timings ==============
[stderr] 0.0 - LS startup started (0.0s since last event)
[stderr] 0.016782 - connection established (0.016782s since last event)
[stderr] 0.16407 - (async) listening to client events (0.14728s since last event)
[stderr] 0.23795 - (async) listening to symbol server events (0.073885s since last event)
[stderr] 0.238 - starting combined listener (4.6968e-5s since last event)
[stderr] 0.34248 - LSP/initialize (0.10449s since last event)
[stderr] 3.3328 - LSP/initialized (2.9903s since last event)
[stderr] 7.1672 - LSP/textDocument/didOpen (3.8344s since last event)
[stderr] 9.1428 - LSP/workspace/didChangeConfiguration (1.9756s since last event)
[stderr] 18.193 - symbols received (9.0504s since last event)
[stderr] 18.197 - extended methods computed (0.0039608s since last event)
[stderr] 18.197 - project deps computed (1.812e-5s since last event)
[stderr] 18.197 - env map computed (2.861e-6s since last event)
[stderr] 18.275 - initial lint done (0.078178s since last event)
[stderr] =============================================
I upgraded this way. Julia is still 1.9.2 (it’s the version in Fedora 39’s repos).
The start up time will depend on the number of packages to be indexed, doesn’t it?
In my case, times of over 20 seconds are not that unusual. It is worth it, but it would be
nice to see those times decrease more. (I am on 1.10 now.)
Kinda. Package indexing should be async and not stop the more code-local features from working. The main delay here is caused by code loading/init and then parsing and analyzing the workspace, which is blocking.