0

I had a short experience with emacs a few years ago (spacemacs), and now I want to get back to it seriously. Within literally 3 days, I built my own config that turned regular emacs into a full-fledged programming environment for PHP. I have a working setup for LSP (phpactor) and company-mode. I love the experience so far.

The problem is that LSP doesn't autocomplete standard PHP functions for me, only those related to my code and installed packages. I found auto-complete packages dedicated to this purpose (ac-php ac-php-core), which can be used as a backend for the company. But when I add them to the backend list, LSP stops suggesting classes and methods from my project.

Below are important fragments of my configuration file, which I put together like Dr. Frankenstein.

(use-package company)
(add-hook 'after-init-hook 'global-company-mode)

(use-package company-statistics)
(add-hook 'after-init-hook 'company-statistics-mode)

(use-package php-mode)

(setq programming-packages '(lsp-mode yasnippet lsp-treemacs lsp-ui lsp-ivy flycheck company-php ac-php ac-php-core dap-mode phpactor))

(when (cl-find-if-not #'package-installed-p programming-packages)
  (package-refresh-contents)
  (mapc #'package-install programming-packages))

(add-hook 'php-mode-hook 'lsp)


(use-package lsp-mode
  :commands (lsp lsp-deferred)
  :config
  (setq lsp-clients-php-server-command "/home/user20k/.emacs.d/elpa/phpactor-20221023.608/")
  (setq lsp-phpactor-path "/home/user20k/.emacs.d/elpa/phpactor-20221023.608/phpactor")
  (setq lsp-enabled-clients '(phpactor))
  (setq lsp-disabled-clients '(php-ls iph intelephense serenata))
  (lsp-enable-which-key-integration t)
  :init
  (setq lsp-keymap-prefix "C-c l")
)

(with-eval-after-load 'lsp-mode
  (add-hook 'lsp-mode-hook #'lsp-enable-which-key-integration)
  (require 'dap-php)
  (yas-global-mode)

  (message "eval-after-load") ;; for debug purpose
)

;; this section breaks LSP completion for my source code, 
;; but provides completion for standard PHP functions.
(add-hook 'php-mode-hook
          '(lambda ()
              (require 'company-php)
              (company-mode t)            
              (add-to-list 'company-backends 'company-ac-php-backend)

              (message "php-mode-hook") ;; for debug purpose
          )
)
;; end of troublesome section

How to adapt init.el file so that both sources of hints are included?

Answer

According to How do I change backend for company?

Company runs through its list of backends in company-backends and performs a test to see if it should run a given backend for your current mode. The first to pass the test is used. All others are ignored. To make company use both backends at once, create a new list and use the :with keyword

(add-hook 'php-mode-hook
          '(lambda ()
              (require 'company-php)
              (company-mode t)            
              (add-to-list 'company-backends '(company-capf :with company-ac-php-backend))
          )
)
2
  • Please pose a question. Thx.
    – Drew
    Feb 8 at 22:48
  • Also, if you just want the list of built-in functions, you could take a alternative route and use the company-keywords backend. It doesn't have keywords defined for php by default, but if you know of a way to produce the necessary list, altering company-keywords-alist should be easy enough.
    – Dmitry
    Feb 9 at 14:19

1 Answer 1

1

add-to-list is a function that insert the new element at the beginning of the list (unless the append argument is used). Thus the new element takes priority and is used exclusively. I suggest you use setq-local instead with the full intended value of the variable.

And you might want to use the "grouping" feature so that the completions from both sources are shown together. To do that, put a list value into company-backends that contains all the backends to be grouped. Something like:

(setq-local company-backends '((company-capf company-ac-php-backend))

Use the above call instead of the add-to-list you have in your example. The rest of the setup is probably fine.

6
  • "Thus the new element takes priority and is used exclusively" - didn't expect that. Thx for taking time to elaborate on subject. Does it matter where I put it? I tried few different locations in file. The effect of the change is the same as if I had not called (add-to-list). So again I only get autocomplete from LSP.
    – A.J.
    Feb 9 at 9:11
  • If you put it inside php-mode-hook like in your example, it should work. The only condition for the "grouped" backends to work together, is that they should return the same string as "prefix". Do you install company from ELPA or MELPA, by the way?
    – Dmitry
    Feb 9 at 14:17
  • I put (setq company-backends '(company-capf company-ac-php-backend)) in 'php-mode-hook and it didn't help. Most of the packages were automatically installed by use-package after restarting emacs. All of them are located in ~/.emacs.d/elpa/ BTW how can I dump 'company-backends list into Messages buffer? Do I need to loop through all of them or maybe there is a simpler way?
    – A.J.
    Feb 12 at 12:16
  • I just found the solution :-) You were right with adding company-capf to the list but we were missing one crucial detail, namely :with keyword. So final solution is (add-to-list 'company-backends '(company-capf :with company-ac-php-backend)). Answer was in this topic emacs.stackexchange.com/questions/63831/…
    – A.J.
    Feb 12 at 12:32
  • Hmm, what the keyword :with does is turn the backends after it into sort of "auxiliary" ones, meaning that when the first backend is not used (returns nil from its prefix action, saying that there can't be any completions at point), then the rest of the backends are not tried.
    – Dmitry
    Feb 12 at 19:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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