1

I’ve got elixir-mode and lsp-mode (before I was using eglot but the same problem persisted) 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 with enter and then tab to expand. The code expands to:

defmodule $1 do
        $0
end

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

What I was expecting was, upon pressing tab, the code will expand as it does now but the cursor (point) should be placed at $1 where I can type the module name, then another tab will move cursor (point) to $0 for the content of the module body in that order.

When I disable lsp-mode 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 get to type the module name then another tab places the cursor inside the module body.

How can I get the experience I just described with lsp-mode and elixir? I have included below a stripped down version of my config in case someone will like to try is recreate what I've described.

;; Produce backtraces when errors occur
;;(setq debug-on-error t)
(setq message-log-max 10000)


;; Add aditional load paths
;;(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
;;(add-to-list 'load-path (expand-file-name "site-lisp" user-emacs-directory))

(defconst my-savefile-dir (expand-file-name "savefile" user-emacs-directory))


;; create the savefile dir if it doesn't exist
(unless (file-exists-p my-savefile-dir)
  (make-directory my-savefile-dir))

(defconst *is-a-mac* (eq system-type 'darwin))


;;----------------------------------------------------------------------------
;; Adjust garbage collection thresholds during startup, and thereafter
;;----------------------------------------------------------------------------
(let ((normal-gc-cons-threshold (* 20 1024 1024))
      (init-gc-cons-threshold (* 128 1024 1024)))
  (setq gc-cons-threshold init-gc-cons-threshold)
  (add-hook 'emacs-startup-hook
            (lambda () (setq gc-cons-threshold normal-gc-cons-threshold))))

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))

;; Package configs
(require 'package)
(setq package-enable-at-startup nil)
(setq package-archives '(("org" . "http://orgmode.org/elpa/")
                         ("gnu" . "http://elpa.gnu.org/packages/")
                         ("melpa" . "https://melpa.org/packages/")))

(package-initialize)


;; bootstrap `use-package`
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

;; From use-package README
(eval-when-compile
  (require 'use-package))

;; To aid benchmarking
(setq use-package-verbose t)

(unless (fboundp 'helm-mode)
  (ido-mode t)
  (setq ido-enable-flex-matching t))



(autoload 'zap-up-to-char "misc"
  "Kill up to, but not including ARGth occurrence of CHAR." t)

(require 'uniquify)
(setq uniquify-buffer-name-style 'forward)

(require 'saveplace)
(setq-default save-place t)

(global-set-key (kbd "M-/") 'hippie-expand)
(global-set-key (kbd "C-x C-b") 'ibuffer)
(global-set-key (kbd "M-z") 'zap-up-to-char)

(global-set-key (kbd "C-s") 'isearch-forward-regexp)
(global-set-key (kbd "C-r") 'isearch-backward-regexp)
(global-set-key (kbd "C-M-s") 'isearch-forward)
(global-set-key (kbd "C-M-r") 'isearch-backward)

(show-paren-mode 1)
(setq-default indent-tabs-mode nil)
(setq save-interprogram-paste-before-kill t
      apropos-do-all t
      mouse-yank-at-point t
      require-final-newline t
      visible-bell t
      load-prefer-newer t
      ediff-window-setup-function 'ediff-setup-windows-plain
      save-place-file (concat user-emacs-directory "places")
      backup-directory-alist `(("." . ,(concat user-emacs-directory
                                               "backups"))))


(use-package exec-path-from-shell
  :if (memq window-system '(mac ns))
  :ensure t
  :init (setq-default exec-path-from-shell-arguments nil)
  :config
  (progn
    (dolist (var '("SSH_AUTH_SOCK" "SSH_AGENT_PID" "GPG_AGENT_INFO" "LANG" "LC_CTYPE" "WORKON_HOME"))
      (add-to-list 'exec-path-from-shell-variables var))
    (exec-path-from-shell-initialize)))


;; Flycheck
(use-package flycheck
  :ensure t
  :init
  (setq flycheck-display-errors-function #'flycheck-display-error-messages-unless-error-list)
  :init (global-flycheck-mode))

;; Company
(use-package company
  :ensure t
  :hook (after-init . global-company-mode)
  :init
  (setq tab-always-indent 'complete)
  (setq company-minimum-prefix-length 2)
  (setq company-dabbrev-downcase 0)
  (setq company-idle-delay 0)
  (setq company-require-match 'never)
  (setq company-show-numbers t)
  (setq-default company-dabbrev-other-buffers 'all
                company-tooltip-align-annotations t)
  (setq tab-always-indent 'complete)
  :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))


;; Suspend page-break-lines-mode while company menu is active
;; (see https://github.com/company-mode/company-mode/issues/416)

(with-eval-after-load 'company
  (with-eval-after-load 'page-break-lines
    (defvar-local sanityinc/page-break-lines-on-p nil)

    (defun sanityinc/page-break-lines-disable (&rest ignore)
      (when (setq sanityinc/page-break-lines-on-p (bound-and-true-p page-break-lines-mode))
        (page-break-lines-mode -1)))

    (defun sanityinc/page-break-lines-maybe-reenable (&rest ignore)
      (when sanityinc/page-break-lines-on-p
        (page-break-lines-mode 1)))

    (add-hook 'company-completion-started-hook 'sanityinc/page-break-lines-disable)
    (add-hook 'company-after-completion-hook 'sanityinc/page-break-lines-maybe-reenable)))

;; LSP
(use-package lsp-mode
  :ensure t
  :commands lsp
  :hook ((elixir-mode . lsp))
  :init
  (setq lsp-prefer-flymake nil)
  (setq lsp-enable-completion-at-point t)
  (setq lsp-enable-xref t)
  :config
  (add-to-list 'exec-path "/Users/napo/github/elixir-ls/release")) ;; Uncomment for lsp-mode

(use-package lsp-ui
  :after (lsp-mode)
  :hook (lsp-mode . lsp-ui-mode)
  :ensure t
  :init
  ;;(setq lsp-ui-sideline-enable nil)
  ;;(setq lsp-ui-sideline-ignore-duplicate t)
  (setq lsp-ui-doc-max-width 50)
  (setq lsp-ui-doc-max-hight 20)
  (setq lsp-ui-doc-include-signature t)
  (setq lsp-ui-peek-always-show t)
  :commands lsp-ui-mode)

(use-package company-lsp
  :after (company lsp-mode)
  :ensure t
  :commands company-lsp
  :init
  (push 'company-lsp company-backends))


;; Elxir
(use-package elixir-mode
  :after (company lsp-mode)
  :ensure t
  :config
  ;; Create a buffer-local hook to run elixir-format on save, only when we enable elixir-mode.
  (add-hook 'elixir-mode-hook
            (lambda () (add-hook 'before-save-hook 'elixir-format nil t))))

Thanks.

1

Install yasnippet https://github.com/joaotavora/yasnippet . lsp-mode is using it to expand the completion templates.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.