Emacs-based workflow

I’m trying to set-up an (efficient) Emacs-based workflow to develop Julia code (on a linux system).

Currently, here is what I’m using:

  • I have a live Julia REPL in an ansi-term buffer, which uses Revise to update the Julia code as I edit source files. I’m currently using my own isend-mode package to interact with this buffer and evaluate Julia code blocks, but am thinking of trying @Tamas_Papp’s julia-repl instead1.

  • In order to search for something in the documentation, I use zeal and zeal-at-point. But I don’t like spawning an external process (and window), especially when most of this documentation (and more) is available from Julia itself in a form that could easily be browsed within Emacs (but I haven’t found any readily-available tool to do this).

  • In order to search for and (jump to) function definitions, I’m using InteractiveCodeSearch.jl. Again, I’d like to streamline the process of looking for the definition of the function at point (which I don’t think would be too difficult; I might try to develop a package to do it if I get around to it…)

Some of the points bothering me might very well be fixed by LanguageServer and lsp-mode, but I haven’t been able to successfully set these up yet. I had difficulties installing LanguageServer, and then had some trouble with lsp-julia2. In your opinion, is it worth to invest some time to make it work?


On another note, one thing I lack is the ability to quickly run tests. In most other languages (like C++ or Python), I would set up compilation commands to build and/or test my program, and bind them to a single key so that I can speed-up development cycles. In Julia, I use the same technique to run thorough tests (as in julia -O3 --project runtests.jl), but long startup times prevent me from running these too often. Most of the time, I simply go to the REPL and hit upRET. But I don’t like this process very much, since it sometimes interferes with other experiments I made in the REPL (and e.g. the last command is not what I thought it would be).


Have I blatantly missed anything? Do you (Emacs users) have entirely different workflows? Are you facing some of the same issues?



1 in particular, julia-repl’s use of bracketed paste might solve some quirks I have with isend-mode when sending indented blocks.

2 I tried to use @gdkrmr’s fork of lsp-julia (which seemed to be the most up-to-date) and had strange Emacs errors. At first sight, it looked like these might be due to incompatibilities between recent lsp-mode versions and my outdated Emacs-25; I’ll have to investigate.

13 Likes

I’ve been using this interface to jupyter for my Julia REPL for the past several months, and it’s been lovely. There’s actually some julia-specific code in the repo to make Julia’s REPL a bit more friendly rather than just a generic jupyter prompt like provided by e.g. ob-ipython.

I hope to update my fork some time in the coming few weeks and then maybe even publish to Melpa; just fyi.

Startup times for just Julia have gotten pretty darn fast. It’s re-precompiling the package that probably takes all the time. But this would be no different than running ] test in the REPL, so if you find that sufficient other than messing up your REPL session, binding running tests with the Julia binary to a key shouldn’t be any worse (as long as it’s an async command).

4 Likes

Thanks, I’ll definitely try this!

Yes, sorry if I was unclear. I do run extensive tests (like the full test suite) from a M-x (re)compile command, and it is as fast to run them this way (with a fresh julia process) as from ] test. And I usually don’t care about how long it takes Julia to re-compile the necessary modules, since the tests themselves are long and there is potentially many of them.

What I meant is that when I’m working on a single function (o small part of the code), I like having a small test case to see whether I’m improving things or not (and at that time, I’m not necessarily interested in knowing whether I broke something else, which is what the full test suite is useful for). This “small test” is usually a single expression like

@btime myfun($testArg)

and I like to be able to run it right away (for now, in a “hot” REPL, relying on Revise to ensure that an up-to-date version of the code is run).

One thing I have in mind would be to have an “interactive” REPL open in a terminal where I can interact at will, and another REPL in a hidden/buried terminal buffer for “automated” interactions. I would not type directly in that second terminal, but would rather have small commands (bound to readily accessible keys) to send some pre-defined expression to the REPL. But that sounds an awful lot like I would then be re-inventing a poor-man’s LSP :confused:

I also get error messages in the *Message* buffer, but they are not fatal, you can see a discussion here

I am using spacemacs with the julia-layer and everything works pretty well. There are currently some issues:

  • There was no LanguageServer.jl release for Julia 1.0. Solved with release v0.5.0.
  • There are breaking changes in the last release of lsp-mode and you have to figure out which version to use.
  • The julia-layer is not yet updated to work with the new version of lsp-mode, see the link above.

With julia-repl you can just do C-c C-d (or K in spacemacs) on a symbol and the repl will print the documentation.

This would be a nice addition, ESS has a shortcut for this. If I am working on a single test, most of the times it is sufficient to run that test(-set) interactively until it works and then run the entire suite just to check.

+1

1 Like

Thanks, I’ll try this.


I’ve never used ESS; I’ll have to look at what it does exactly. But would you find it useful if I submitted a PR to julia-repl, so that a user-specified command could be sent to the REPL in one key press?

julia-repl is probably the best place for this. I would ask @Tamas_Papp about this.

You would probably do something like here to locate the project folder.

1 Like

I use ctags/etags for this. Works pretty well.

1 Like

I just use emacs with julia-mode and some terminals for REPL and running test scripts. I like this zeal thing, though. That’s great!

Thanks, I did not know one could index julia files using ctags. I wonder if you index only Julia itself (and maybe the installed packages), or also your own code (in order to navigate within your sources as you’re developing them). In the latter case, do you have any way to automatically update the index as your sources evolve?

I only have it setup for Julia 0.6 at the moment. In .emacs I have:

;; CTAGS ETAGS
; https://www.emacswiki.org/emacs/EtagsTable
(require 'etags-table)
(setq etags-table-alist
      '(
        (".*\\.jl$" "~/julia/julia-0.6/TAGS"  "~/.julia/v0.6/TAGS")
        ))
(setq tags-case-fold-search nil) ; case sensitive search

then I bulild the tags with:

~ >> cat julia/julia-0.6/build-ctags.sh 
#!/bin/sh
ctags -R -e --options=contrib/ctags --languages=julia --totals=yes base
~ >> cat .julia/v0.6/build-ctags.sh  
#!/bin/sh
ctags -R -e --options=/home/mauro/julia/julia-0.6/contrib/ctags --totals=yes --exclude=.* --exclude=.git --languages=julia

.i.e. I build a ctags file for julia-base and the installed packages (which also include (most of) my personal package/code).

Navigation is pretty nice with M-. jumping to a function def and M-, back. If there are several methods, they will show in a window to select (which is ok for a few methods but not so useful when there are 10+)

For Julia 1.0, the julia-0.6/contrib/ctags file has been moved to GitHub - JuliaEditorSupport/julia-ctags: Julia language support for CTAGS.

julia-repl is a quick & dirty effort to take advantage of ansi terminal features in Julia. I hope that code introspection (docstrings, function argument lookup) will improve as LanguageServer interfaces stabilize, which is why I am not making a huge effort to develop a parallel solution, as that would imply a fundamental redesign. In the meantime, C-c C-d is a stopgap (in particular, it won’t use module names, eg Foo.bar will look up Main.bar, that is a bug).

For quickly running something without interrupting your main work, consider using a buffer suffix (C-c C-s). This will send code to another *julia-suffix* buffer. You can use different versions, too (C-c C-v).

I look at LanguageServer from time to time, but found it not ready the last time I did that (about 3 months ago). If someone things it is ready for regular use and there is anything I can do on the julia-repl side to make it easier, please let me know. I know some open issues have been around for a long time, but I always try to fix them if I get a break from other things.

3 Likes

Similar setup here, macOS. I’ve opened An Issue against LanguageServer.jl since it doesn’t want to pass tests to satisfy PackageCompiler.

I thought I would post a small update, since I managed to make lsp-mode, lsp-ui and lsp-julia work nicely.

Here is the relevant part of my configuration (based on use-package), in case someone would be interested:

(use-package dash)
(use-package s)
(use-package lsp-mode
  :init
  ;; Use flycheck instead of flymake (better lsp-ui integration)
  (setq lsp-prefer-flymake nil)

  :config
  ;; Prevent long documentation showing up in the echo area from messing up the
  ;; window configuration -> only show the first line
  (defun ff/lsp-eldoc-advice (orig-fun &rest args)
    (let ((msg (car args)))
      (if msg
          (funcall orig-fun (->> msg (s-trim-left)
                                     (s-split "\n")
                                     (first))))))
  (advice-add 'lsp--eldoc-message :around #'ff/lsp-eldoc-advice)

  ;; Avoid questions about restarting the LSP server when quitting emacs
  (defun ff/lsp-disable-server-autorestart ()
    (setq lsp-restart nil))
  (add-hook 'kill-emacs-hook #'ff/lsp-disable-server-autorestart))
(use-package lsp-ui
  :ensure t

  :init
  (setq lsp-ui-doc-enable nil)

  :config
  (define-key lsp-ui-mode-map [remap xref-find-definitions] #'lsp-ui-peek-find-definitions)
  (define-key lsp-ui-mode-map [remap xref-find-references]  #'lsp-ui-peek-find-references))
;; I've installed lsp-julia as a git submodule of my emacs configuration directory
(use-package lsp-julia
  :load-path (lambda () (expand-file-name "~/.emacs.d/packages/lsp-julia")))
11 Likes

Thanks for the writeup. Sorry for the silly question, but what does it do when it works? Eg how can I show some documentation for a function, or something similar?

4 Likes

Not a silly question, I wondered this myself for a long time before I got LSP to work…

Here is a screenshot:

Notice in particular:

  • in the modeline:

    • “LSP[julia-ls:4750]” in the minor-modes list indicates that the server process has started
    • “>bar(z::Float64)” comes from which-function-mode; I don’t think it has anything to do with lsp (and while writing this, I realize that the indication incorrectly refers to an argument named z)
    • “FlyC:0/1” is the indication that flycheckhas highlighted 0 errors and 1 warning in the buffer (you can see it in the left fringe in front of the “println(t)” line. This is fed by LSP
  • in the echo area:

    • “foo(x)” is displayed by eldoc-mode, which is fed by the documentation provided by LSP. One of the tweaks in my config above is that if the documentation for a particular function spans multiple lines, I don’t want the echo area to grow and show it, thereby messing with my window configuration.
  • in the “Foo.jl” buffer

    • indications in the left fringe and wiggly underlines are shown by flycheck to indicate errors/warnings. These come from LSP
    • indications in the right of the window show information about the symbols appearing in the line at point. This is lsp-ui-sideline-mode in action. If point was on a flycheck warning/error, then the error message would be displayed here instead.
  • a few actions that LSP allows you to perform:

    • M-xlsp-describe-thing-at-point: display the documentation for the thing at point (the “lsp-help” buffer shown here, was obtained by a prior invocation of this command, when point was on println)
    • M-.: find the definition of the symbol at point. In my configuration above, I remap this so that it uses lsp-ui-peek instead of the standard xref mechanism.
    • M-?: find all references to the symbol at point. Again, I remapped this to use lsp-ui-peek
    • M-xlsp-rename: rename all occurrences of the symbol at point.

This is more or less everything that I discovered (by reading/skimming the docs and experimenting a bit). There might be other stuff.

8 Likes

@ffevotte’s configuration works well for me, if I am in a package, but not otherwise.

For instance, I have been working with Gen.jl recently, and the configuration works great for navigating and working with the package’s files in the /src/ directory. However, say I switch over to /examples/ to try and run the examples related to the package, I exclusively get the errors on undeclared variables, so the lines

import Distributions
import Gen: random, logpdf

gives the errors Julia language server: Use of possibly undeclared variable: <X> [Missing variable], where X is [Distributions, Gen, random, logpdf]. This is related to the LanguageServer.jl, but I’m not familiar enough with lsp to know where I’m going wrong here.

Any pointers or suggestions?

LanguageServer.jl does not work when you are not inside a package. E.g. I have a file ~/test.jl which I use for quick testing and LanguageServer.jl starts searching my entire home folder. Blacklisting ~ is no good, because it blacklists all sub-folders.

EDIT:
LSP Julia doesn’t work for me, but I can get autocompletions with:

(add-to-list 'company-backends `(company-dabbrev))
(setq company-dabbrev-downcase nil)

Have you tried updating lsp-mode? There was an issue which should be fixed by https://github.com/emacs-lsp/lsp-mode/pull/827.

3 Likes

Ah. I believe I’m using melpa-stable’s lsp-mode, which is on commit 789b67 (the latest release), which dates to January of this year.

I’ll try switching to regular melpa.

EDIT:
Also, I finally decided to commit to switching to Emacs about a month ago, so I’m still fairly new.
I confess I don’t know elisp.

2 Likes