Emacs setup for lsp-mode?

I’ve just started using IHP (with ihp-new), and I’m having some trouble getting the editor integration working in emacs. I’m using lsp-mode rather than eglot (which I would prefer not to change), and I can’t get it to integrate with nix correctly.

Whether I use direnv-mode or envrc-mode, though I can see that the environment is being applied, lsp-mode can’t seem to find haskell-language-server-wrapper, instead echoing the following in the minibuffer:

LSP :: The following servers support current file but do not have automatic installation: lsp-haskell
You may find the installation instructions at Languages - LSP Mode - LSP support for Emacs.
(If you have already installed the server check lsp-log).

(Unfortunately, there’s no output at all in lsp-log from this invocation of lsp.)

On non-flake nix projects, I normally set lsp-haskell-server-wrapper-function to call nix-shell to find the language server:

(setq lsp-haskell-server-wrapper-function
  (lambda (argv)
    (append
   (append (list "nix-shell" "-I" "." "--command" )
           (list (mapconcat 'identity argv " ")))
   (list "shell.nix"))))

That doesn’t work in my IHP project because there isn’t a shell.nix. (I haven’t really used flakes before, so I might be missing something here.)

I tried using nix develop instead (with and without --impure, which made no difference):

(setq lsp-haskell-server-wrapper-function
  (lambda (argv)
  (append
   (append (list "nix" "develop" "--impure" "--command" )
           (list (mapconcat 'identity argv " "))))))

…this gives me a devenv error message:

warning: Git tree '/Users/Emily/Work/Pannal Village Hall (no git)/ihp-alternative' is dirty
error:
       … while evaluating the attribute 'optionalValue.value'
         at /nix/store/sc2fmza4wlzi16jjjmkg6rmmmf75jxmr-source/lib/modules.nix:856:5:
          855|
          856|     optionalValue =
             |     ^
          857|       if isDefined then { value = mergedValue; }
 
       … while evaluating a branch condition
         at /nix/store/sc2fmza4wlzi16jjjmkg6rmmmf75jxmr-source/lib/modules.nix:857:7:
          856|     optionalValue =
          857|       if isDefined then { value = mergedValue; }
             |       ^
          858|       else {};

       (stack trace truncated; use '--show-trace' to show the full trace)

       error: Failed assertions:
       - devenv was not able to determine the current directory.

         See https://devenv.sh/guides/using-with-flakes/ how to use it with flakes.

I’ve tried the devenv link, but nothing there seems helpful for this specific situation, and googling hasn’t got me anywhere either.

Any ideas for what I can do to get either the direnv support or the more manual wrapping working correctly?

Ah, I found something that works! The reason my usual wrapper didn’t work is because I was manually referencing shell.nix, which doesn’t exist in the project. But nix-shell defaults to using shell.nix if it exists and default.nix otherwise, so I can just remove my explicit specification of the file:

(setq lsp-haskell-server-wrapper-function
  (lambda (argv)
    (append
   (append (list "nix-shell" "-I" "." "--command" )
           (list (mapconcat 'identity argv " "))))))

This gets me a little closer: it’s not properly showing me errors yet, but the language server is no longer exiting immediately.

Do you have any logs from the language server? Maybe it’s not picking up the hie.yaml file in the project correctly (that just forwards the call to $IHP/.hie-bios, which should be this ihp/ihp-ide/lib/IHP/.hie-bios at master · digitallyinduced/ihp · GitHub )

Here’s what shows up in the *lsp-haskell:stderr* buffer (lines like the last two are repeated for each file I save):

2024-06-16T13:23:48.677138Z | Info | invoking build tool to determine build flags (this may take some time depending on the cache)
2024-06-16T13:23:48.680702Z | Info | $IHP/.hie-bios
  Environment Variables
    HIE_BIOS_OUTPUT: /private/tmp/HIE_BIOS_OUTPUT55081-0
    HIE_BIOS_DEPS: /private/tmp/HIE_BIOS_DEPS55081-1
    HIE_BIOS_ARG: /Users/Emily/Work/Pannal Village Hall (no git)/ihp-alternative/Web/Types.hs
2024-06-16T13:24:48.067352Z | Info | Live bytes: 0.00MB Heap size: 0.00MB
2024-06-16T13:24:48.081124Z | Info | Could not identify reverse dependencies for NormalizedFilePath "/Users/Emily/Work/Pannal Village Hall (no git)/ihp-alternative/Web/Types.hs"
2024-06-16T13:25:48.071061Z | Info | Live bytes: 7.52MB Heap size: 319.82MB
2024-06-16T13:26:11.503066Z | Info | Could not identify reverse dependencies for NormalizedFilePath "/Users/Emily/Work/Pannal Village Hall (no git)/ihp-alternative/Web/Types.hs"

I’m not certain what this means, but I thought it looked like it was finding $IHP/.hie-bios. However, I enabled some extra tracing with (setq lsp-log-io t) and restarted the server, giving the following output in *lsp-log: lsp-haskell:…*:


[Trace - 04:13:04 am] Received notification 'textDocument/publishDiagnostics'.
Params: {
  "diagnostics": [
    {
      "message": "/bin/sh: /.hie-bios: No such file or directory\n",
      "range": {
        "end": {
          "character": 0,
          "line": 1
        },
        "start": {
          "character": 0,
          "line": 0
        }
      },
      "severity": 1,
      "source": "cradle"
    }
  ],
  "uri": "file:///Users/Emily/Work/Pannal%20Village%20Hall%20%28no%20git%29/ihp-alternative/Web/View/Prelude.hs",
  "version": 50
}


[Trace - 04:13:04 am] Received response 'nil - (2425)' in 0ms.
Result: {
  "code": -32803,
  "message": "eval: Rule Failed: GetEvalComments"
}


[Trace - 04:13:04 am] Received response 'textDocument/documentHighlight - (2427)' in 170ms.
Result: null


[Trace - 04:13:04 am] Received response 'nil - (2426)' in 0ms.
Result: []


[Trace - 04:13:04 am] Received request 'window/workDoneProgress/create - (12).
Params: {
  "token": "521"
}


[Trace - 04:13:04 am] Sending response 'window/workDoneProgress/create - (12)'. Processing request took 0ms
Params: {
  "jsonrpc": "2.0",
  "id": 12,
  "result": null
}


[Trace - 04:13:04 am] Received response 'textDocument/codeLens - (2428)' in 169ms.
Result: {
  "code": -32803,
  "message": "eval: Rule Failed: GetEvalComments"
}

That suggests to me that something isn’t finding the .hie-bios file alright. I guess because I’m not currently using direnv-mode or envrc-mode, so $IHP isn’t defined? In that case I should be able to define it manually to point to the right place (or re-enable one of those modes and see if it works). Will try.

It works! Setting IHP manually works: I echoed its value from a terminal in the project where direnv had run (echo $IHP, which printed /nix/store/fprzkxymn6q8smh13z7r87r918m6gmrf-source/lib/IHP) and then ran (setenv "IHP" "/nix/store/fprzkxymn6q8smh13z7r87r918m6gmrf-source/lib/IHP") in emacs. At that point the language server started working correctly.

I also tried unsetting the env var again (by running (setenv "IHP")) and enabling envrc-global-mode, which didn’t work. However, direnv-mode did work: I guess however lsp-mode is starting this background process, direnv-mode’s global environment editing is necessary? Perhaps another reason for me to move to eglot.

So to sum up: to get a working configuration I needed to remove the reference to shell.nix from my lsp-haskell-server-wrapper-function, and then set the IHP env var either manually or via direnv-mode. Thanks for your help!

1 Like

Were your issues specific to lsp-mode / direnv-mode? I see there was an lsp-mode config added to the editor guide Emacs direnv by Montmorency · Pull Request #1872 · digitallyinduced/ihp · GitHub (previously only eglot+envrc); does this warrant a mention in docs or was it very specific to your setup?

Oh, interesting! I hadn’t seen that config, I should try it. (It doesn’t seem to be on the website?)

It might be worth mentioning this use of haskell-language-server-wrapper-function, but I’m not sure: it might be a worse approach than the one outlined above.

1 Like