λ +completion -
Author: lispcat 187922791+lispcat@users.noreply.github.com
This file contains configuration to Emacs' completion.
;; TODO: set C-M-c to completion-at-point (orig C-M-i).
;; TODO: try corfu + cape (follow system crafters vid)
;; - https://www.youtube.com/watch?v=f0FMo_XxujU
;; + tempel (replace org-tempo)
;; TODO: start adding some setup.el in my config, little by little.
;; TODO: org-modern (sexy as f)
;; ? : corfu, kind-icon, wgrep?, consult-dir, cape
;; ^ more at ~/code/cloned/daviwil-dots/.emacs.d/modules/dw-interface.el
;; TODO: vim keybinds for vertico completion shit (work on later) (also daviwil)
;;
;; a framework for minibuffer completion
;; (https://github.com/minad/vertico)
(leaf vertico
:init
(vertico-mode 1)
;; :setq
;; (vertico-scroll-margin . 0) ; Different scroll margin
;; (vertico-count . 20) ; Show more candidates
;; (vertico-resize . t) ; Grow and shrink the Vertico minibuffer
;; (vertico-cycle . t) ; Enable cycling for `vertico-next/previous'
)
;; A few more useful configurations...
(leaf emacs :elpaca nil
:init
;; Support opening new minibuffers from inside existing minibuffers.
(setq enable-recursive-minibuffers t)
;;
;; Emacs 28 and newer: hide commands in M-x that do not work in the current mode.
;; (setq read-extended-command-predicate #'command-completion-default-include-p)
;;
;; Add prompt indicator to `completing-read-multiple'.
;; We display [CRM<separator>], e.g., [CRM,] if the separator is a comma.
(defun crm-indicator (args)
(cons (format "[CRM%s] %s"
(replace-regexp-in-string
"\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
crm-separator)
(car args))
(cdr args)))
(advice-add #'completing-read-multiple :filter-args #'crm-indicator)
;;
;; Do not allow the cursor in the minibuffer prompt
(setq minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode))
(leaf consult
:bind (;; generic binds
("C-s" . consult-line)
;; C-c bindings in `mode-specific-map'
("C-c M-x" . consult-mode-command)
;; ("C-c )" . consult-kmacro)
;; C-x bindings in `ctl-x-map'
("C-x M-:" . consult-complex-command) ;; repeat-complex-command
("C-x b" . consult-buffer) ;; switch-to-buffer
("C-x 4 b" . consult-buffer-other-window) ;; switch-to-buffer-other-window
("C-x 5 b" . consult-buffer-other-frame) ;; switch-to-buffer-other-frame
("C-x t b" . consult-buffer-other-tab) ;; switch-to-buffer-other-tab
("C-x r b" . consult-bookmark) ;; bookmark-jump
("C-x p b" . consult-project-buffer) ;; project-switch-to-buffer
("C-x p C-b" . consult-project-buffer) ;; project-switch-to-buffer
;; Custom M-# bindings for fast register access
("M-#" . consult-register-store)
;; ("C-M-#" . consult-register)
("C-M-#" . consult-register-load)
;; Other custom bindings
("M-y" . consult-yank-pop) ;; yank-pop
([remap Info-search] . consult-info)
;; M-g bindings in `goto-map'
("M-g e" . consult-compile-error)
("M-g f" . consult-flymake) ;; Alternative: consult-flycheck
("M-g g" . consult-goto-line) ;; goto-line
("M-g M-g" . consult-goto-line) ;; goto-line
("M-g o" . consult-outline) ;; Alternative: consult-org-heading
("M-g m" . consult-mark)
("M-g k" . consult-global-mark)
("M-g i" . consult-imenu)
("M-g I" . consult-imenu-multi)
("M-g O" . consult-org-heading)
;; M-s bindings in `search-map'
("M-s d" . consult-find) ;; Alternative: consult-fd
("M-s c" . consult-locate)
("M-s g" . consult-grep)
("M-s G" . consult-git-grep)
("M-s r" . consult-ripgrep)
("M-s l" . consult-line)
("M-s L" . consult-line-multi)
("M-s k" . consult-keep-lines)
("M-s u" . consult-focus-lines)
("M-s M" . consult-man) ; T for terminal
("M-s I" . consult-info)
;; Isearch integration
("M-s e" . consult-isearch-history)
(isearch-mode-map
("M-e" . consult-isearch-history) ;; isearch-edit-string
("M-s e" . consult-isearch-history) ;; isearch-edit-string
("M-s l" . consult-line) ;; Needed by: consult-line to detect isearch
("M-s L" . consult-line-multi)) ;; Needed by: consult-line to detect isearch
;; Minibuffer history
(minibuffer-local-map
("M-s" . consult-history) ;; next-matching-history-element
("M-r" . consult-history)) ;; previous-matching-history-element
)
:init
(leader-bind
"s" search-map
"Tt" 'consult-theme
"bb" 'consult-buffer
"fr" 'consult-recent-file
"fm" 'consult-bookmark))
;; used to go to a file in a bookmarked dir n stuff (one ex)
(leaf consult-dir
:init
(leader-bind
"fd" 'consult-dir)
:bind (("C-x C-d" . consult-dir) ; default?
(vertico-map
("C-x C-d" . consult-dir)
("C-x C-j" . consult-dir-jump-file)))
;; :custom
;; (consult-dir-project-list-function nil)
)
;; TODO: do i even need to do this here?
;; - oh wait i do since the other module might overwrite...
;; - but the issue is that it never gets set if those modules
;; are never loaded...
;; - maybe in the other module files, only set those functions
;; if another bind isnt already there?
;; - is it possible to do eval-after-load 'thing OR after init?
;; and throw away the other autoload once one succeeds?
;; (defmacro mi/eval-now-and-after-load (feature &rest body)
;; "Eval BODY, then if FEATURE is not loaded, eval BODY again after FEATURE loaded."
;; (declare (indent defun))
;; (let ((f (cadr feature)))
;; `(progn
;; ;; always eval now
;; ,@body
;; ;; if feature not loaded, eval again after load feature
;; ,(unless (featurep f)
;; `(eval-after-load ',f
;; (lambda () ,@body))))))
(leaf embark
:bind
(("C-." . embark-act)
("C-;" . embark-dwim)
;; ("C-h B" . embark-bindings)
)
:init
;; use embark for showing command prefix help
(setq prefix-help-command #'embark-prefix-help-command)
;; Show the Embark target at point via Eldoc. You may adjust the
;; Eldoc strategy, if you want to see the documentation from
;; multiple providers. Beware that using this can be a little
;; jarring since the message shown in the minibuffer can be more
;; than one line, causing the modeline to move up and down:
;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
:config
;; Hide the mode line of the Embark live/completions buffers
(add-to-list 'display-buffer-alist
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
(leaf embark-consult
:after embark consult
:hook (embark-collect-mode-hook . consult-preview-at-point-mode))
(leaf orderless
:require t
:setq
;; Configure a custom style dispatcher (see the Consult wiki)
;; (orderless-style-dispatchers . '(+orderless-consult-dispatch orderless-affix-dispatch))
;; (orderless-component-separator . #'orderless-escapable-split-on-space)
(completion-styles . '(orderless basic))
(completion-category-defaults . nil)
(completion-category-overrides . '((file (styles partial-completion)))))
(leaf marginalia
:init
(marginalia-mode 1)
:bind ((minibuffer-local-map
("M-A" . marginalia-cycle))
(completion-list-mode-map
("M-A" . marginalia-cycle))))
;; TODO: disable most backends by default add a bunch per mode (org should only have a few
(leaf company
;; :disabled t
:require t
:bind
(company-active-map
("<return>" . nil)
("C-n" . nil)
("C-p" . nil)
("C-s" . company-filter-candidates))
:config
(company-tng-configure-default)
(global-company-mode 1)
(defun +company-return-default-or-complete ()
(interactive)
;; number if selected, nil if not
(if company-selection
(company-complete-selection)
(company-abort)
(execute-kbd-macro (kbd "<return>"))))
(define-key company-tng-map (kbd "<return>") #'+company-return-default-or-complete)
(setq company-backends
'(company-dabbrev company-files)) ; the default, overrides below
(setq company-transformers nil)
(setq lsp-completion-provider :none)
(setq company-idle-delay 0.1)
(setq company-selection-wrap-around t)
(setq company-minimum-prefix-length 1)
(setq company-dabbrev-downcase nil)
(setq company-search-regexp-function 'company-search-words-in-any-order-regexp)
;; org-mode-specific backends
(add-hook 'prog-mode-hook
(lambda ()
(setq-local company-backends
'((company-yasnippet :with company-capf)
company-dabbrev-code
company-files))
(setq-local company-transformers '(company-sort-by-backend-importance))))
(eval-after-load 'org
'(add-hook 'org-mode-hook
(lambda ()
(setq-local company-backends
'((company-dabbrev :with company-files))))))
(eval-after-load 'latex
'(add-hook 'LaTeX-mode-hook
(lambda ()
(setq-local company-backends'nil))))
;; separator for orderless completion:
(defvar +company-separator "&")
(defun +company-insert-separator ()
"Insert `+company-separator' during company completion."
(interactive)
(when (company-manual-begin)
(insert +company-separator)))
(define-key company-active-map (kbd "M-SPC") #'+company-insert-separator)
(setq orderless-component-separator "[ &]")
)
(leaf company-quickhelp
:after company
:bind ("C-c l h c" . company-quickhelp-mode)
:setq
(company-quickhelp-delay . 1)
:config
(company-quickhelp-mode 1))
;; TODO: this is set up for eglot only, not lsp-mode
;; https://stackoverflow.com/questions/72601990/how-to-show-suggestions-for-yasnippets-when-using-eglot
(leaf yasnippet :elpaca yasnippet-snippets
:commands yas-reload-all
:hook (prog-mode-hook . yas-minor-mode)
:bind
(yas-keymap
("RET" . yas-next-field-or-maybe-expand))
:config
(add-to-list 'yas-snippet-dirs
(expand-file-name "no-search/snippets"
+emacs-src-dir))
(yas-reload-all))
(global-set-key [remap dabbrev-expand] 'hippie-expand)
(add-to-list 'hippie-expand-try-functions-list #'yas-hippie-try-expand t)
(leaf isearch :elpaca nil
:bind
("C-M-s" . isearch-forward)
("C-M-r" . isearch-backward))
% CULPRIT OF HANGING, DISABLED
;; (leaf corfu
;; :require t
;; :setq
;; (corfu-cycle . t) ;; Enable cycling through candidates
;; (corfu-auto . t) ;; Enable auto completion
;; (corfu-auto-prefix . 1) ;; Complete after typing 2 characters
;; (corfu-auto-delay . 0.1) ;; Wait time before showing completions
;; (corfu-preview-current . 'insert) ;; Preview first candidate
;; (corfu-preselect . 'prompt) ;; Preselect the prompt
;; (corfu-on-exact-match . nil) ;; Don't auto-complete exact matches
;; ;; Hide commands in M-x which do not apply to the current mode. Corfu
;; ;; commands are hidden, since they are not used via M-x. This setting is
;; ;; useful beyond Corfu.
;; (read-extended-command-predicate . #'command-completion-default-include-p)
;; :bind (corfu-map
;; ("TAB" . corfu-next)
;; ([tab] . corfu-next)
;; ("S-TAB" . corfu-previous)
;; ([backtab] . corfu-previous)
;; ("RET" . nil)
;; ("C-n" . nil)
;; ("C-p" . nil)
;; ("C-RET" . corfu-insert))
;; :init
;; (global-corfu-mode))
;; (leaf cape
;; ;; :disabled t
;; :require t
;; ;; Bind prefix keymap providing all Cape commands under a mnemonic key.
;; ;; Press C-c p ? to for help.
;; :bind ("M-+" . cape-prefix-map) ;; Alternative keys: M-p, M-+, ...
;; ;; Alternatively bind Cape commands individually.
;; ;; :bind (("C-c p d" . cape-dabbrev)
;; ;; ("C-c p h" . cape-history)
;; ;; ("C-c p f" . cape-file)
;; ;; ...)
;; :init
;; ;; Add to the global default value of `completion-at-point-functions' which is
;; ;; used by `completion-at-point'. The order of the functions matters, the
;; ;; first function returning a result wins. Note that the list of buffer-local
;; ;; completion functions takes precedence over the global list.
;; (add-hook 'completion-at-point-functions #'cape-dabbrev) ; current buffers
;; (add-hook 'completion-at-point-functions #'cape-file) ; file name
;; ;; (add-hook 'completion-at-point-functions (cape-company-to-capf 'company-yasnippet)) ; file name
;; ;; (add-hook 'completion-at-point-functions #'cape-elisp-block) ; code block (THE CULPRIT!!!!!)
;; )
;; (leaf yasnippet-capf
;; :after cape
;; :config
;; (defun +capfs-add-yasnippet ()
;; "Add yasnippet-capf to the front of completion-at-point-functions."
;; ;; (add-to-list 'completion-at-point-functions #'yasnippet-capf)
;; (setq-local completion-at-point-functions
;; (cons #'yasnippet-capf
;; completion-at-point-functions))
;; )
;; :hook (prog-mode-hook . +capfs-add-yasnippet))
;; Configure Tempel
;; (use-package tempel
;; ;; Require trigger prefix before template name when completing.
;; ;; :custom
;; ;; (tempel-trigger-prefix "<")
;; :bind (("M-+" . tempel-complete) ;; Alternative tempel-expand
;; ("M-*" . tempel-insert))
;; :init
;; ;; Setup completion at point
;; (defun tempel-setup-capf ()
;; ;; Add the Tempel Capf to `completion-at-point-functions'.
;; ;; `tempel-expand' only triggers on exact matches. Alternatively use
;; ;; `tempel-complete' if you want to see all matches, but then you
;; ;; should also configure `tempel-trigger-prefix', such that Tempel
;; ;; does not trigger too often when you don't expect it. NOTE: We add
;; ;; `tempel-expand' *before* the main programming mode Capf, such
;; ;; that it will be tried first.
;; (setq-local completion-at-point-functions
;; (cons #'tempel-insert
;; completion-at-point-functions)))
;; (add-hook 'conf-mode-hook 'tempel-setup-capf)
;; (add-hook 'prog-mode-hook 'tempel-setup-capf)
;; (add-hook 'text-mode-hook 'tempel-setup-capf)
;; ;; Optionally make the Tempel templates available to Abbrev,
;; ;; either locally or globally. `expand-abbrev' is bound to C-x '.
;; ;; (add-hook 'prog-mode-hook #'tempel-abbrev-mode)
;; ;; (global-tempel-abbrev-mode)
;; )
;; Optional: Add tempel-collection.
;; The package is young and doesn't have comprehensive coverage.
;; (use-package tempel-collection)
;; (leaf abbrev :elpaca nil
;; :bind (("C-c c a" . add-global-abbrev)
;; ("C-c c -" . inverse-add-global-abbrev)
;; ("C-c c e" . edit-abbrevs)))
% misc
(leaf emacs :elpaca nil
:config
(defun my/elisp-custom-keyword-lax-capf ()
"Provide lax completion when in s-expr with preceding :custom keyword."
(when (and (derived-mode-p 'emacs-lisp-mode)
(my/elisp-custom-keyword-lax-capf--pred))
;; get elisp-capf result
(when-let ((result (elisp-completion-at-point)))
;; capf new
(append (take 3 result)
(list :annotation-function
(lambda (cand)
(let ((sym (intern-soft cand)))
(cond
((and sym (boundp sym)) " <var>")
((and sym (fboundp sym)) " <func>")
((keywordp sym) " <key>")
(t "")))))))))
(defun my/elisp-custom-keyword-lax-capf--pred ()
"Predicate for `my/elisp-custom-keyword-lax-capf'.
Checks if the point is under `use-package' or `leaf',
and that the last keyword was :custom."
(when-let*
((limit
(save-excursion
(condition-case nil
;; go up till find use-package or leaf
(progn
(while (not (looking-at-p "(\\(use-package\\|leaf\\)\\b"))
(backward-up-list))
(point))
;; no matches
(error nil)))))
;; search backwards, find last keyword, if ":custom" ret t
(save-excursion
(when (re-search-backward " \\(:\\w+\\)" limit t)
(string= (match-string 1) ":custom")))))
(add-hook 'emacs-lisp-mode-hook
(lambda ()
(add-hook 'completion-at-point-functions
#'my/elisp-custom-keyword-lax-capf nil t))))
(provide '+completion)
% +completion.el ends here
Last updated: August 22, 2025