Skip to content

Latest commit

 

History

History
805 lines (625 loc) · 20.6 KB

wal-complete.org

File metadata and controls

805 lines (625 loc) · 20.6 KB

Completion

Complete commands and code.

Header

;;; wal-complete.el --- Completion. -*- lexical-binding: t -*-

;;; Commentary:
;;
;; Provide Emacs completion packages.

;;; Code:

(eval-when-compile
  (require 'savehist)
  (require 'wal-key-bindings nil t))

(declare-function cape-dabbrev "ext:cape.el")
(declare-function cape-file "ext:cape.el")
(declare-function cape-history "ext:cape.el")
(declare-function cape-wrap-super "ext:cape.el")
(declare-function consult--buffer-query "ext:consult.el")
(declare-function consult--buffer-state "ext:consult.el")
(declare-function consult-line "ext:consult.el")
(declare-function consult-org-agenda "ext:consult.el")
(declare-function consult-org-heading "ext:consult.el")
(declare-function corfu-complete "ext:corfu.el")
(declare-function corfu-mode "ext:corfu.el")
(declare-function junk--stringify "ext:junk.el")
(declare-function junk--parts "ext:junk.el")
(declare-function org-clock-in "ext:org-clock.el")
(declare-function org-clock-out "ext:org-clock.el")
(declare-function org-clocking-buffer "ext:org-clock.el")
(declare-function org-clocking-p "ext:org-clock.el")
(declare-function org-up-heading-safe "ext:org.el")
(declare-function wal-agenda-buffer-p "wal-org.el")
(declare-function wal-append "wal-useful.el")
(declare-function wal-org-clock-in-from-now "wal-org.el")
(declare-function wal-project-buffer-root "wal-workspace.el")
(declare-function wal-list-from "wal-useful.el")
(declare-function wal-switch-to-buffer-obeying-display-actions "wal-useful.el")
(declare-function wal-univ-p "wal-useful.el")

(defvar consult-register--narrow)
(defvar junk-expansion-packs)
(defvar project--list)
(defvar savehist-additional-variables)
(defvar vertico-map)
(defvar wal-active-theme)
(defvar wal-default-path)

;;; Customization:

(defgroup wal-complete nil
  "Change completion settings."
  :group 'wal
  :tag "Completion")

Inline Completion

corfu

Light-weight inline completion. The custom function is taken from the wiki to make sure that corfu is enabled in the minibuffer when it makes sense.

(defun wal-corfu-enable-in-minibuffer ()
  "Enable Corfu in the minibuffer if `completion-at-point' is bound."
  (when (where-is-internal #'completion-at-point (list (current-local-map)))
    (corfu-mode 1)))

(use-package corfu
  :hook
  (((org-mode prog-mode harpoon-prog-like eshell-mode inferior-python-mode) . corfu-mode)
   (minibuffer-setup . wal-corfu-enable-in-minibuffer))

  :custom
  (corfu-cycle t)
  (corfu-auto t)

  :wal-bind
  (("k" . completion-at-point)
   ;; M-k binds `completionist'.
   :map corfu-map
   ("k" . corfu-quit)))

corfu-quick

Jump to a completion candidate with a single letter.

(defun wal-corfu-quick-exit-in-case-of-single-match (&rest _args)
  "Complete immediately if there is only one match."
  (defvar corfu--total)

  (when (= 1 corfu--total)
    (corfu-complete)))

(use-package corfu-quick
  :ensure nil

  :demand t
  :after corfu

  :config
  (advice-add
    'corfu-quick-complete :before-until
    #'wal-corfu-quick-exit-in-case-of-single-match)

  :custom
  (corfu-quick1 "jkl;h")
  (corfu-quick2 "asdfg")

  :wal-bind
  (:map corfu-map
   ("j" . corfu-quick-complete))

  :defines (corfu-map))

corfu-history

Save the completion history.

(use-package corfu-history
  :ensure nil

  :demand t
  :after corfu

  :config
  (add-to-list 'savehist-additional-variables 'corfu-history)
  (corfu-history-mode)

  :custom
  (corfu-history-length 200)

  :defines (corfu-map)
  :functions (corfu-history-mode))

corfu-popupinfo

Show docs or location during selection.

Key bindings:

  • M-t to toggle
  • M-g for location
  • M-h for documentation

Scrolling works like for other-window.

(use-package corfu-popupinfo
  :after corfu
  :demand t
  :ensure nil

  :config
  (corfu-popupinfo-mode)

  :functions (corfu-popupinfo-mode))

corfu-terminal

Allows using corfu in a terminal.

(use-package corfu-terminal)

cape

Provides additional completion function as well as functionality to jerry-rig company backends to work with corfu.

(defun wal-cape-history-file ()
  "Combined `cape-history' and `cape-file'."
  (cape-wrap-super #'cape-history #'cape-file))

(defun wal-cape-eshell-setup ()
  "Set up `cape' fro Eshell."
  (setq-local completion-at-point-functions
              (cons #'wal-cape-history-file
                    completion-at-point-functions)))

(use-package cape
  :hook (eshell-mode . wal-cape-eshell-setup)

  :general
  (completionist "/" 'cape-dabbrev
                 "f" 'cape-file
                 "h" 'cape-history
                 "a" 'cape-abbrev
                 "d" 'cape-dict
                 "k" 'cape-keyword
                 "l" 'cape-line))

tempel

Don’t reduce the boilerplate, reduce your involvement in it with snippets. Completing these snippets is bound to a custom binding.

(defun wal-tempel-comment (elt)
  "Comment the element ELT according to mode."
  (when (eq (car-safe elt) 'c)
    (let ((cs (if (derived-mode-p 'emacs-lisp-mode) ";; " comment-start)))

      (concat cs (cadr elt)))))

(use-package tempel
  :config
  (setq tempel-path (wal-list-from
                     'tempel-path
                     (expand-file-name
                      "data/tempel.eld"
                      wal-default-path)))

  :custom
  (tempel-user-elements '(wal-tempel-comment))
  (tempel-mark (propertize "" 'face 'mode-line-highlight))

  :bind
  (:map tempel-map
   ("M-k" . tempel-kill)
   ("M-a" . tempel-beginning)
   ("M-e" . tempel-end)
   ("M-n" . tempel-next)
   ("M-p" . tempel-previous))

  :general
  (completionist "t" 'tempel-complete)

  :defines (tempel-path tempel-map)
  :functions (tempel-complete))

Minibuffer Completion

vertico

Provide global option narrowing using the vertico family of products. The setup is mostly as per the official docs. This just sets up vertico-multiform-mode to make certain completion tasks unobtrusive.

(use-package vertico
  :defer 1
  :wal-ways t

  :hook (minibuffer-setup . cursor-intangible-mode)

  :config
  ;; Set up minibuffer.
  (setq read-extended-command-predicate #'command-completion-default-include-p
        minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt))

  (vertico-mode)
  (vertico-multiform-mode)

  :custom
  (vertico-cycle t)
  (vertico-resize t)

  (vertico-multiform-categories '((consult-grep buffer)
                                  (consult-location buffer)
                                  (org-heading buffer)
                                  (consult-outline buffer)
                                  (imenu buffer)))

  (vertico-multiform-commands '((tab-switch flat)
                                (partial-recall-switch-to-buffer flat)
                                (wal-project-find-in-here unobtrusive)))

  :wal-bind
  (:map vertico-multiform-map
   ("l" . vertico-multiform-vertical))

  :functions (vertico-mode vertico-multiform-mode))

vertico-directory

Make sure navigating directories during completion is a breeze. This makes sure that we don’t go into directories when jumping in Dired.

(defvar-local wal-command nil
  "Command that started completion session.")

(defun wal-record-this-command ()
  "Record the command which opened the minibuffer."
  (setq-local wal-command this-command))

(defun wal-with-dired-goto-file-ignored (fun &rest args)
  "Advise FUN using ARGS to exit if we came from `dired-goto-file'."
  (unless (and (eq (car args) 'category)
               (eq wal-command 'dired-goto-file))
    (apply fun args)))

(use-package vertico-directory
  :ensure nil

  :demand t
  :after vertico

  :hook
  ((rfn-eshadow-update-overlay . vertico-directory-tidy)
   (minibuffer-setup . wal-record-this-command))

  :config
  ;; We don't want to enter directories when we go to file with Dired.
  (advice-add
   'vertico--metadata-get :around
   #'wal-with-dired-goto-file-ignored)

  :bind
  (:map vertico-map
   ("RET" . vertico-directory-enter)
   ("DEL" . vertico-directory-delete-char)
   ("M-DEL" . vertico-directory-delete-word))

  :functions (vertico-exit))

vertico-quick

Quickly jump to a candidate with a single letter.

(defun wal-vertico-quick-exit-in-case-of-single-match (&rest _args)
  "Exit immediately if there is only one match."
  (defvar vertico--total)

  (when (= 1 vertico--total)
    (vertico-exit)))

(use-package vertico-quick
  :ensure nil

  :demand t
  :after vertico

  :config
  (advice-add
   'vertico-quick-jump :before-until
   'wal-vertico-quick-exit-in-case-of-single-match)

  :custom
  (vertico-quick1 "jkl;h")
  (vertico-quick2 "asdfg")

  :wal-bind
  (:map vertico-map
   ("j" . vertico-quick-exit)))

orderless

Fuzzy matching while completing. The completion settings are as per official docs.

(use-package orderless
  :demand t
  :after vertico

  :config
  ;; Setup basic completion and category defaults/overrides.
  (setq completion-styles '(orderless partial-completion basic)
        completion-category-defaults nil
        completion-category-overrides '((file (styles partial-completion)))))

marginalia

Contextual information during completion, partial completion and completion actions.

(use-package marginalia
  :demand t
  :after vertico

  :config
  (marginalia-mode)

  :bind
  (:map minibuffer-local-map
   ("C-," . marginalia-cycle))

  :functions (marginalia-mode)
  :defines (marginalia-annotator-registry marginalia-command-categories))

embark

Act upon thing-at-point, be it in a buffer or minibuffer. Sets a few more commands in various maps. The entry point command is created using parallel.

(defun wal-browse-html-file (filename)
  "Browse FILENAME provided it's an HTML file."
  (when (not (string= (file-name-extension filename) "html"))
    (user-error "Can only browse HTML files"))

  (browse-url (expand-file-name filename)))

(use-package embark
  :config
  ;; Search using region.
  (define-key embark-region-map
              (kbd "g")
              #'wal-duck-duck-go-region)

  (define-key embark-file-map
              (kbd "x")
              #'wal-browse-html-file)

  (define-key embark-buffer-map
              (kbd "t")
              #'wal-tab-bar-switch-to-buffer-tab)

  :custom
  (embark-mixed-indicator-delay 0.8)
  (embark-cycle-key "C-,")

  :wal-bind
  (("." . embark-act)
   ("M-." . embark-dwim)))

embark-consult

Integration for consult.

(use-package embark-consult
  :demand t
  :after (embark consult)

  :hook (embark-collect-mode . consult-preview-at-point-mode))

consult

Beautiful completion and narrowing within completion. This adds a new source for projects while switching to differentiate open and closed projects. Since consult provides many useful commands a transient combining the most useful ones is bound to the eponymous leader key.

Custom commands and command variants

(defun wal-consult-ripgrep-ignored (&optional dir initial)
  "Search for regexp with rg in DIR with INITIAL input.
Do not ignore hidden files."
  (interactive "P")

  (declare-function consult--grep "ext:consult.el")
  (declare-function consult--ripgrep-builder "ext:consult.el")

  (defvar consult-ripgrep-args)

  (let ((consult-ripgrep-args
         (concat (substring consult-ripgrep-args 0 -1) "--no-ignore .")))

    (consult--grep "Ripgrep (ignored)" #'consult--ripgrep-builder dir initial)))

(defun wal-consult-unregister ()
  "Remove KEY from the register."
  (interactive)

  (let ((key (with-no-warnings
               (consult--read
                (consult-register--candidates)
                :prompt "Unregister: "
                :category 'multi-category
                :group (consult--type-group consult-register--narrow)
                :narrow (consult--type-narrow consult-register--narrow)
                :sort nil
                :require-match t
                :history t
                :lookup #'consult--lookup-candidate))))

    (setq register-alist (assoc-delete-all key register-alist))))

(defun wal-consult-clock (&optional arg)
  "Clock in (or out).

Only non-archived and active headings are matched.

Optional argument ARG can have one of two meanings. If it has the
numeric value of 0 this will call `wal-clock-in-from-now'. If it
has numeric value 4 (the default `universal-argument')
`org-clock-out' is called."
  (interactive "p")

  (require 'org-clock nil t)

  (let ((stop (and arg (eq 4 arg)))
        (discontinue (and arg (eq 0 arg)))
        (previous (when (org-clocking-p)
                    (org-clocking-buffer))))

    (if stop
        (org-clock-out)
      (save-window-excursion
        (consult-org-agenda "-ARCHIVE/-DONE")

        (if discontinue
            (wal-org-clock-in-from-now)
          (org-clock-in))))

    (when previous
      (with-current-buffer previous
        (save-buffer)))

    (when-let ((current (and (org-clocking-p) (org-clocking-buffer))))
      (with-current-buffer current
        (save-buffer)))))

(defun wal-consult-place (&optional prefer-outline)
  "Go to a place with `consult'.

In Org buffers this is done using `consult-org-heading', in
`prog-mode' buffers this is done using `consult-imenu', otherwise
`consult-outline' is used. The latter can be forced if
PREFER-OUTLINE is t."
  (interactive "P")

  (if prefer-outline
      (call-interactively 'consult-outline)
    (cond
     ((derived-mode-p 'org-mode)
      (consult-org-heading "-ARCHIVE"))
     ((derived-mode-p 'prog-mode)
      (call-interactively 'consult-imenu))
     (t
      (call-interactively 'consult-outline)))))

(defun wal-consult-error ()
  "Jump to error.

Use either `flymake' or `flycheck'."
  (interactive)

  (cond
   ((and (bound-and-true-p flycheck-mode)
         (fboundp 'consult-flycheck))
    (call-interactively 'consult-flycheck))
   ((bound-and-true-p flymake-mode)
    (call-interactively 'consult-flymake))
   (t
    (user-error "No syntax checker"))))

(defun wal-consult-project ()
  "Enhanced `project-switch-project' command."
  (interactive)

  (declare-function consult--multi "ext:consult.el")

  (consult--multi
   '(consult--source-open-projects consult--source-projects)
   :prompt "Select project: "
   :require-match t))

Buffer sources

(defvar consult--project-history nil)

(defvar consult--source-projects
  (list :name "Projects"
        :category 'project
        :history 'consult--project-history
        :action 'project-switch-project
        :items (lambda ()
                 (let ((open (consult--open-project-items))
                       (all (mapcar #'car project--list)))

                   (seq-filter (lambda (it) (not (member it open))) all)))))

(defun consult--open-project-items ()
  "Get the open projects."
  (cl-remove-duplicates
   (cl-loop for buffer being the buffers
            for project = (wal-project-buffer-root buffer)
            if project
            collect project)
   :test 'string=))

(defvar consult--source-open-projects
  (list :name "Open projects"
        :category 'project
        :narrow ?o
        :history 'consult--project-history
        :action 'project-switch-project
        :items 'consult--open-project-items))

(defvar wal-consult--source-agenda-buffer
  (list :name "Agenda Buffer"
        :narrow ?a
        :category 'buffer
        :state #'consult--buffer-state
        :history 'buffer-name-history
        :face 'font-lock-keyword-face
        :items #'wal-consult-agenda-buffer--query))

(defun wal-consult-agenda-buffer--query ()
  "Get contributing Org Agenda buffer names."
  (consult--buffer-query
   :sort 'visibility
   :as #'buffer-name
   :predicate #'wal-agenda-buffer-p))

(defvar wal-consult--source-compilation-buffer
  (list :name "Compilations"
        :narrow ?c
        :category 'buffer
        :history 'buffer-name-history
        :action #'wal-switch-to-buffer-obeying-display-actions
        :items #'wal-consult-compilation-buffer--query))

(defun wal-compilation-buffer-p (buffer)
  "Check if buffer BUFFER is a compilation buffer."
  (with-current-buffer buffer
    (derived-mode-p 'compilation-mode 'comint-mode)))

(defun wal-consult-compilation-buffer--query ()
  "Get compilation buffer names."
  (consult--buffer-query
   :sort 'visibility
   :as #'buffer-name
   :predicate #'wal-compilation-buffer-p))

Package configuration

(defun wal-then-set-active-theme (theme)
  "Advise to set `wal-active-theme' to THEME."
  (setq wal-active-theme theme)

  (when current-prefix-arg
    (customize-save-variable 'wal-theme theme))

  (run-hooks 'wal-theme-hook))

(use-package consult
  :commands (consult--multi consult)

  :config
  ;; Integrate with `xref'.
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref)

  ;; Customize sources.
  (consult-customize
   consult--source-recent-file
   consult--source-project-recent-file
   consult--source-project-recent-file-hidden
   consult--source-bookmark
   consult-recent-file
   :preview-key "C-."

   wal-consult-clock
   :prompt "Clock in: ")

  ;; Be sure to set the active theme after switching.
  (advice-add 'consult-theme :after #'wal-then-set-active-theme)

  (advice-add 'consult :around 'wal-with-delayed-transient-popup)

  (with-eval-after-load 'org-agenda
    (wal-insert
     'consult-buffer-sources
     'consult--source-buffer
     'wal-consult--source-agenda-buffer
     :quiet t))

  (with-eval-after-load 'org-keys
    (wal-replace-in-alist
     'org-speed-commands
     '(("j" . consult-org-heading))))

  (with-eval-after-load 'compile
    (wal-insert
     'consult-buffer-sources
     'consult--source-buffer
     'wal-consult--source-compilation-buffer
     :quiet t))

  (transient-define-prefix consult ()
    "Run `consult' commands."
    [["Goto"
      ("SPC" "mark" consult-mark)
      ("r" "register" consult-register)
      ("o" "outline" consult-outline)
      ("e" "error" wal-consult-error)]

     ["Find"
      ("m" "bookmark" consult-bookmark)
      ("@" "global mark" consult-global-mark)
      ("f" "recent file" consult-recent-file)
      ("a" "agenda" consult-org-agenda)]

     ["Search"
      ("n" "grep" consult-ripgrep)
      ("l" "locate" consult-locate)]

     ["Do"
      ("k" "call macro" consult-kmacro)
      ("t" "change theme" consult-theme)]]

    [["Modes"
      ("+" "major mode command" consult-mode-command)
      ("-" "toggle minor mode" consult-minor-mode-menu)]])

  :general
  (adjunct "u" 'wal-consult-unregister)

  :bind
  (:map isearch-mode-map
   ("M-s ," . consult-line))

  :wal-bind
  (("<SPC>" . wal-consult-clock)
   ("i" . wal-consult-place)
   ("'" . wal-consult-project)
   ("M-l" . consult-goto-line)

   ("," . consult-buffer)
   ("M-," . consult)))

Footer

(provide 'wal-complete)

;;; wal-complete.el ends here