diff --git a/modules/completion/company/autoload.el b/modules/completion/company/autoload.el index d605ac9b5..ae6fd349b 100644 --- a/modules/completion/company/autoload.el +++ b/modules/completion/company/autoload.el @@ -1,5 +1,13 @@ ;;; completion/company/autoload.el -*- lexical-binding: t; -*- +;;;###autoload +(defvar +company-backend-alist + '((text-mode :derived (company-dabbrev company-yasnippet)) + (prog-mode :derived (:separate company-capf company-yasnippet)) + (conf-mode :derived company-capf company-dabbrev-code company-yasnippet)) + "An alist matching modes to company backends. The backends for any mode is +built from this.") + ;;;###autodef (defun set-company-backend! (modes &rest backends) "Prepends BACKENDS (in order) to `company-backends' in MODES. @@ -16,24 +24,24 @@ Examples: '(company-shell :with company-yasnippet)) (set-company-backend! 'js2-mode '(:separate company-irony-c-headers company-irony)) - (set-company-backend! 'sh-mode nil)" + (set-company-backend! 'sh-mode nil) + +To have BACKENDS apply to any mode that is a parent of MODES, set MODES to +:derived, e.g. + + (set-company-backend! :derived 'text-mode 'company-dabbrev 'company-yasnippet)" (declare (indent defun)) - (dolist (mode (doom-enlist modes)) - (let ((hook (intern (format "%s-hook" mode))) - (fn (intern (format "+company|init-%s" mode)))) - (cond ((null (car-safe backends)) - (remove-hook hook fn) - (unintern fn nil)) - ((fset fn - (lambda () - (when (or (eq major-mode mode) - (and (boundp mode) (symbol-value mode))) - (require 'company) - (make-local-variable 'company-backends) - (dolist (backend (reverse backends)) - (cl-pushnew backend company-backends - :test (if (symbolp backend) #'eq #'equal)))))) - (add-hook hook fn)))))) + (let ((type :exact)) + (when (eq modes :derived) + (setq type :derived + modes (pop backends))) + (dolist (mode (doom-enlist modes)) + (if (null (car backends)) + (setq +company-backend-alist + (delq (assq mode +company-backend-alist) + +company-backend-alist)) + (setf (alist-get mode +company-backend-alist) + (cons type backends)))))) ;; FIXME obsolete :company-backend ;;;###autoload @@ -41,6 +49,40 @@ Examples: :obsolete set-company-backend! `(set-company-backend! ,modes ,@backends)) + +;; +;; Library +;; + +(defun +company--backends () + (or (cl-loop for (mode . rest) in +company-backend-alist + for type = (car rest) + for backends = (cdr rest) + if (or (and (eq type :derived) (derived-mode-p mode)) ; parent modes + (and (eq type :exact) (eq major-mode mode)) ; major modes + (and (boundp mode) (symbol-value mode))) ; minor modes + nconc backends) + (default-value 'company-backends))) + + +;; +;; Hooks +;; + +;;;###autoload +(defun +company|init-backends () + "Set `company-backends' for the current buffer." + (unless (eq major-mode 'fundamental-mode) + (set (make-local-variable 'company-backends) (+company--backends))) + (add-hook 'after-change-major-mode-hook #'+company|init-backends nil 'local)) + +(put '+company|init-backends 'permanent-local-hook t) + + +;; +;; Commands +;; + ;;;###autoload (defun +company/toggle-auto-completion () "Toggle as-you-type code completion." diff --git a/modules/completion/company/config.el b/modules/completion/company/config.el index 0e3bc00c1..b43d136b0 100644 --- a/modules/completion/company/config.el +++ b/modules/completion/company/config.el @@ -15,10 +15,9 @@ company-frontends '(company-pseudo-tooltip-frontend company-echo-metadata-frontend) - company-backends - '((:separate company-capf company-yasnippet)) company-transformers '(company-sort-by-occurrence)) :config + (add-hook 'company-mode-hook #'+company|init-backends) (global-company-mode +1)) diff --git a/modules/completion/company/test/test-company.el b/modules/completion/company/test/test-company.el index 9892516a7..a523479be 100644 --- a/modules/completion/company/test/test-company.el +++ b/modules/completion/company/test/test-company.el @@ -6,45 +6,63 @@ (load! "../autoload")) (describe ":company-backend" - :var (a) + :var (a +company-backend-alist backends) (before-each - (setq company-backends '(default) - text-mode-hook nil + (setq-default company-backends '(t)) + (setq +company-backend-alist nil a (get-buffer-create "x")) + (fset 'backends + (lambda (mode) + (let ((major-mode mode)) + (+company--backends)))) (set-buffer a) (spy-on 'require)) (after-each - (fmakunbound '+company|init-text-mode) (kill-buffer a)) - (it "adds hooks and defines +company|init-MODE" - (set-company-backend! 'text-mode '(backend-1)) - (expect (fboundp '+company|init-text-mode)) - (expect text-mode-hook :to-equal '(+company|init-text-mode))) + ;; + (it "sets backends for a major mode" + (set-company-backend! 'text-mode 'a) + (expect (backends 'text-mode) :to-equal '(a))) - (it "adds grouped backends" - (set-company-backend! 'text-mode '(backend-1)) - (text-mode) - (expect company-backends :to-equal '((backend-1) default))) + (it "sets backends for a derived-mode" + (set-company-backend! :derived 'prog-mode 'a) + (expect (backends 'prog-mode) :to-equal '(a)) + (expect (backends 'emacs-lisp-mode) :to-equal '(a))) - (it "adds multiple backends" - (set-company-backend! 'text-mode 'backend-1 'backend-2) - (text-mode) - (expect company-backends :to-equal '(backend-1 backend-2 default))) + (it "sets multiple backends for exact major modes" + (set-company-backend! '(text-mode emacs-lisp-mode) 'a 'b) + (expect (backends 'text-mode) :to-equal (backends 'emacs-lisp-mode))) - (it "adds single backend" - (set-company-backend! 'text-mode 'backend-1) - (text-mode) - (expect company-backends :to-equal '(backend-1 default))) + (it "sets cumulative backends" + (set-company-backend! :derived 'prog-mode 'a) + (set-company-backend! 'emacs-lisp-mode 'b) + (expect (backends 'emacs-lisp-mode) :to-equal '(b a))) - (it "overwrites past values" - (set-company-backend! 'text-mode 'backend-1) - (set-company-backend! 'text-mode 'backend-2) - (text-mode) - (expect company-backends :to-equal '(backend-2 default))) + (it "overwrites past backends" + (set-company-backend! 'text-mode 'old 'backends) + (set-company-backend! 'text-mode 'new 'backends) + (expect (backends 'text-mode) :to-equal '(new backends))) - (it "unsets past values" - (set-company-backend! 'text-mode 'backend-1) + (it "unsets past backends" + (set-company-backend! 'text-mode 'old) (set-company-backend! 'text-mode nil) - (text-mode) - (expect company-backends :to-equal '(default))))) + (expect (backends 'text-mode) :to-equal (default-value 'company-backends))) + + (it "unsets past parent backends" + (set-company-backend! :derived 'prog-mode 'old) + (set-company-backend! 'emacs-lisp-mode 'child) + (set-company-backend! :derived 'prog-mode nil) + (expect (backends 'emacs-lisp-mode) :to-equal '(child))) + + (it "overwrites past cumulative backends" + (set-company-backend! :derived 'prog-mode 'base) + (set-company-backend! 'emacs-lisp-mode 'old) + (set-company-backend! 'emacs-lisp-mode 'new) + (expect (backends 'emacs-lisp-mode) :to-equal '(new base))) + + (it "overwrites past parent backends" + (set-company-backend! :derived 'prog-mode 'base) + (set-company-backend! 'emacs-lisp-mode 'child) + (set-company-backend! :derived 'prog-mode 'new) + (expect (backends 'emacs-lisp-mode) :to-equal '(child new)))))