How is snippet expansion/code completion supposed to work in Emacs with eglot/elixir-ls

I’ve got elixir-mode and eglot configured in my emacs.d and it mostly works. One thing I can’t figure out though is how to work with the code completion (via company) in elixir code.

Say I type defm at this point I get the completion options pop up. I select defmodule and then it expands to

defmodule $1 do
        $0
end

But the cursor (point) moves to the end of end. I get no opportunity to specify the module name and also complete the module body.

When I disable eglot and use only elixir-mode I get the expected behaviour were selecting defmodule from the completion list places the cursor (point) between defmodule and do where I type the module name then another tab places the cursor inside the module body.

What is the way to work with what seems to be eglot’s way of code completion?

@Pilgrim I couldn’t reproduce this bug. Running latest eglot in elpa, and elixir-lsp/elixir-ls (more updated fork at the moment). When I hit enter on defmodule completion, works as expected: cursor at module name, and tab goes to module body.

I wonder if you have a company issue, or with another package.

@rodrigues can I take a look around your emacs.d? My company config is really simple below and I spent hours going over it without success.

;; Company mode
(add-to-list 'completion-styles 'initials t)

(use-package company
  :ensure t
  :hook (after-init . global-company-mode)
  :init
  (setq company-minimum-prefix-length 2)
  ;;(setq company-auto-complete nil)
  (setq company-idle-delay 0)
  (setq company-require-match 'never)
  (setq-default company-dabbrev-other-buffers 'all
                company-tooltip-align-annotations t)
  (setq tab-always-indent 'complete)
  ;;(defvar completion-at-point-functions-saved nil)
  :config
  (define-key company-mode-map (kbd "M-/") 'company-complete)
  (define-key company-active-map (kbd "C-n") 'company-select-next)
  (define-key company-active-map (kbd "C-p") 'company-select-previous))

Eglot currently is setup like so

(use-package eglot
  :ensure t)

and Elixir’s looks like this:

(use-package elixir-mode
  :after (company flycheck)
  :ensure t
  :init
  ;;(add-to-list 'exec-path "/Users/napo/Github/elixir-ls/release")
  (add-to-list 'eglot-server-programs '(elixir-mode . ("/Users/napo/Github/elixir-ls/release/language_server.sh")))

  ;;(add-hook 'elixir-mode-hook #'lsp)
  (add-hook 'elixir-mode-hook 'eglot-ensure)
  (add-hook 'elixir-mode-hook 'flycheck-mode)
  (add-hook 'elixir-mode-hook #'smartparens-mode)
  (add-hook 'elixir-mode-hook
            (lambda () (add-hook 'before-save-hook 'eglot-format nil t))))
1 Like

@Pilgrim my emacs.d repo gets constantly rebased with https://github.com/purcell/emacs.d

Other than that, that’s my only company specific custom config:

(add-hook 'after-init-hook 'global-company-mode)
(setq company-dabbrev-downcase 0)
(setq company-idle-delay 0)

As for eglot/elixir, there it is, at the moment:

(require-package 'elixir-mode)
(require-package 'eglot)
(require 'eglot)
(require 'eldoc-box)

(add-to-list
 'eglot-server-programs
 '(elixir-mode . ("sh" "/Users/rodrigues/code/elixir-ls/release/language_server.sh")))

(add-hook
 'elixir-mode-hook
 (lambda ()
   (subword-mode)
   (eglot-ensure)
   (company-mode)
   (flymake-mode)
   (add-hook 'before-save-hook 'eglot-format nil t)))
1 Like

I run into same issue with my Emacs setup, and I was able to solve it.

Based on the github issue I was able to deduce that Eglot upon start communicates to Language Server that he supports snippets, and I would guess, that based on this start-up info it will either try to pass it into snippet engine or not. You could look into your *EGLOT (....) events* buffer; find first message ( with M-<), and verify your settings.

client-request (id:1) Fri Jan 31 00:35:47 2020:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
          (:processId 14783 :rootPath .... 
                      (:workspace
                       ......
                        (:dynamicRegistration :json-false :completionItem
                                              (:snippetSupport t)
                                              :contextSupport t)

I was able to achieve such setup by installing Yasnippets, and turning them on in elixir-mode


(use-package elixir-mode)

(use-package eglot
  :commands (eglot eglot-ensures)
  :hook
  (elixir-mode . eglot-ensure)
  (before-save . eglot-format-buffer)
  :config
  (add-to-list
   `eglot-server-programs `(elixir-mode ,(expand-file-name  "~/elixir_ls/release/language_server.sh"))))

(use-package yasnippet
  :hook (elixir-mode . yas-minor-mode))
2 Likes