Changes to the Autocomplete system

While I reorganized my layout, the big changes is getting corfu to
work instead of company, integrating with hippie expand. This may be
completely sufficient for the moment.
This commit is contained in:
Howard Abrams 2023-01-09 20:01:18 -08:00
parent 59e72f33e4
commit 1dbccee411

View file

@ -47,6 +47,12 @@ Changes and settings I like introduced in Emacs 28:
completions-detailed t) completions-detailed t)
#+end_src #+end_src
In Emacs version 28, we can hide commands in ~M-x~ which do not apply to the current mode.
#+begin_src emacs-lisp
(setq read-extended-command-predicate
#'command-completion-default-include-p)
#+end_src
As [[https://tecosaur.github.io/emacs-config/config.html][tec wrote]], I want to use =~/.authsource.gpg= as I dont want to accidentaly purge this file cleaning =~/.emacs.d=, and let's cache as much as possible, as my home machine is pretty safe, and my laptop is shutdown a lot. Also, as [[https://www.bytedude.com/gpg-in-emacs/][bytedude]] mentions, I need to se the =epa-pineentry-mode= to =loopback= to actually get a prompt for the password, instead of an error. As [[https://tecosaur.github.io/emacs-config/config.html][tec wrote]], I want to use =~/.authsource.gpg= as I dont want to accidentaly purge this file cleaning =~/.emacs.d=, and let's cache as much as possible, as my home machine is pretty safe, and my laptop is shutdown a lot. Also, as [[https://www.bytedude.com/gpg-in-emacs/][bytedude]] mentions, I need to se the =epa-pineentry-mode= to =loopback= to actually get a prompt for the password, instead of an error.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package epa-file (use-package epa-file
@ -78,55 +84,6 @@ And some Mac-specific settings:
(add-to-list 'default-frame-alist '(ns-appearance . dark))) (add-to-list 'default-frame-alist '(ns-appearance . dark)))
#+end_src #+end_src
* Support Packages * Support Packages
** Yet Another Snippet System (YASnippets)
Using [[https://github.com/joaotavora/yasnippet][yasnippet]] to convert templates into text:
#+begin_src emacs-lisp
(use-package yasnippet
:config
(add-to-list 'yas-snippet-dirs (expand-file-name "snippets" user-emacs-directory))
(yas-global-mode +1))
#+end_src
Check out [[http://joaotavora.github.io/yasnippet/][the documentation]] for writing them.
Since I have troubles installing Dooms [[https://github.com/hlissner/doom-snippets][collection of snippets]], lets use the [[http://github.com/AndreaCrotti/yasnippet-snippets][yasnippet-snippets]] package:
#+begin_src emacs-lisp
(use-package yasnippet-snippets)
#+end_src
** Auto Insert Templates
The [[https://www.emacswiki.org/emacs/AutoInsertMode][auto-insert]] feature is a wee bit complicated. All I want is to associate a filename regular expression with a YASnippet template. I'm stealing some ideas from Henrik Lissner's [[https://github.com/hlissner/doom-emacs/blob/develop/modules/editor/file-templates/autoload.el][set-file-template!]] macro, but simpler?
#+begin_src emacs-lisp
(use-package autoinsert
:init
(setq auto-insert-directory (expand-file-name "templates" user-emacs-directory))
;; Don't prompt before insertion:
(setq auto-insert-query nil)
(add-hook 'find-file-hook 'auto-insert)
(auto-insert-mode t))
#+end_src
Since auto insertion requires entering data for particular fields, and for that Yasnippet is better, so in this case, we combine them:
#+begin_src emacs-lisp
(defun ha-autoinsert-yas-expand()
"Replace text in yasnippet template."
(yas-expand-snippet (buffer-string) (point-min) (point-max)))
#+end_src
And since I'll be associating snippets with new files all over my configuration, let's make a helper function:
#+begin_src emacs-lisp
(defun ha-auto-insert-file (filename-re snippet-name)
"Autofill file buffer matching FILENAME-RE regular expression.
The contents inserted from the YAS SNIPPET-NAME."
;; The define-auto-insert takes a regular expression and an ACTION:
;; ACTION may also be a vector containing successive single actions.
(define-auto-insert filename-re
(vector snippet-name 'ha-autoinsert-yas-expand)))
#+end_src
As an example of its use, any Org files loaded in /this project/ should insert my config file:
#+begin_src emacs-lisp
(ha-auto-insert-file (rx "hamacs/" (one-or-more any) ".org" eol) "hamacs-config")
#+end_src
* Configuration Changes * Configuration Changes
** Initial Settings and UI ** Initial Settings and UI
Let's turn off the menu and other settings: Let's turn off the menu and other settings:
@ -1379,54 +1336,34 @@ Notes:
- ~ysiw'~ :: puts single quotes around the word, no matter the points position. - ~ysiw'~ :: puts single quotes around the word, no matter the points position.
- ~(~ :: puts spaces /inside/ the surrounding parens, but ~)~ doesn't. Same with ~[~ and ~]~. - ~(~ :: puts spaces /inside/ the surrounding parens, but ~)~ doesn't. Same with ~[~ and ~]~.
** Additional Global Packages ** Additional Global Packages
The following defines my use of the Emacs completion system. Ive decided my /rules/ will be:
- Nothing should automatically appear; that is annoying and distracting.
- Spelling in org files (abbrev or hippie expander) and code completion are separate, but Im not sure if I can split them
- IDEs overuse the ~TAB~ binding, and I should re-think the bindings.
*** Auto Completion *** Auto Completion
Emacs Completion is not obvious, and has lots of different interfaces, some distinct, some connected. Heres the summary as I understand: I dont find the Emacs completion system obvious, with different interfaces, some distinct, some connected. Heres the summary as I understand:
- =complete=, which /we can/ call: #+begin_verse
- =hippie-expand=, which calls: =indent-for-tab-command=, which /we can/ call:
- =dabbrev= └─ =completion-at-point=, which calls:
- =completion-at-point=, which calls: └─ =completion-at-point-functions= (capf), which can call:
- =completion-at-point-functions= (capf), which can call: └─ hippie and dabbrev functions
- hippie and dabbrev functions #+end_verse
We have two initial interfaces that may end up showing the same information (if configured correctly). In =org-mode=, ~TAB~ calls [[help:org-cycle][org-cycle]], which, in the context of typing text, calls the binding for ~TAB~, which is the [[help:indent-for-tab-command][indent-for-tab-command]]. If the line is /indented/, I can complete the word:
Emacs first option, called [[help:complete][complete]], is simple. It completes based on words you are most likely to type.
If the line is /indented/ (the default for the ~TAB~ key), lets complete the word:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(setq tab-always-indent 'complete) (setq tab-always-indent 'complete
#+end_src tab-first-completion 'word-or-paren
Lets also use the ~Command~ key to call this directly : completion-cycle-threshold nil)
#+begin_src emacs-lisp
(global-set-key (kbd "C-TAB") 'complete)
#+end_src #+end_src
Note that no matter the setting for =tab-first-completion=, hitting ~TAB~ twice, results in completion.
The next interface is [[help:completion-at-point][completion-at-point]] (which I set to ~M-TAB~). This code (from mini-buffer) doubles with the other [[Vertico][completing processes]] (like [[help:completing-read][completing-read]]) and presents choices based on a series of functions (see [[https://with-emacs.com/posts/tutorials/customize-completion-at-point/][this essay]] for details). This calls [[help:completion-at-point][completion-at-point]]. This code (from mini-buffer) doubles with the other [[Vertico][completing processes]] (like [[help:completing-read][completing-read]]) and presents choices based on a series of functions (see [[https://with-emacs.com/posts/tutorials/customize-completion-at-point/][this essay]] for details). This will call into the CAPF function list (see the variable, =completion-at-point-functions= and the [[file:ha-programming.org::*Cape][Cape]] section for details).
*** Hippie Expand
What would be nice is that if =complete= doesn't have a match, =completion-at-point= would be called. Until then, we need to bind a key … or automatically display choices after a certain time (see below): The venerable [[help:hippie-expand][hippie-expand]] function does a better job than the default, [[help:dabbrev-expand][dabbrev-expand]], so lets swap it out (see this [[https://www.masteringemacs.org/article/text-expansion-hippie-expand][essay]] by Mickey Petersen) with its default key of ~M-/~ (easy to type on the laptop) as well as ~C-Tab~ (easier on mechanical keyboards):
#+begin_src emacs-lisp
(global-set-key (kbd "M-TAB") 'completion-at-point)
#+end_src
The [[file:ha-org.org::*Spell Checking][Flyspell package]] takes over ~M-TAB~, so lets add another keybinding (or [[Corfu][use time-based menu option]]):
#+begin_src emacs-lisp
(global-set-key (kbd "s->") 'completion-at-point)
(global-set-key (kbd "s-.") 'complete)
#+end_src
The idea of cycling through candidates sounds like a good idea, but lets start with a number before setting this to =t=:
#+begin_src emacs-lisp
(setq completion-cycle-threshold 3)
#+end_src
In Emacs version 28, we can hide commands in ~M-x~ which do not apply to the current mode.
#+begin_src emacs-lisp
(setq read-extended-command-predicate
#'command-completion-default-include-p)
#+end_src
**** Hippie Expand
The venerable [[help:hippie-expand][hippie-expand]] function does a better job than the default, [[help:dabbrev-expand][dabbrev-expand]], so lets swap it out (see this [[https://www.masteringemacs.org/article/text-expansion-hippie-expand][essay]] by Mickey Petersen) with its default key of ~M-/~:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(global-set-key [remap dabbrev-expand] 'hippie-expand) (global-set-key [remap dabbrev-expand] 'hippie-expand)
(global-set-key (kbd "M-<tab>") 'completion-at-point)
#+end_src #+end_src
Details on its job? We need to update its [[help:hippie-expand-try-functions-list][list of expanders]]. I dont care much for [[help:try-expand-line][try-expand-line]], so that is not on the list. Details on its job? We need to update its [[help:hippie-expand-try-functions-list][list of expanders]]. I dont care much for [[help:try-expand-line][try-expand-line]], so that is not on the list.
@ -1434,12 +1371,13 @@ Details on its job? We need to update its [[help:hippie-expand-try-functions-lis
(setq hippie-expand-try-functions-list (setq hippie-expand-try-functions-list
'(try-complete-file-name-partially ; complete filenames, start with / '(try-complete-file-name-partially ; complete filenames, start with /
try-complete-file-name try-complete-file-name
yas-hippie-try-expand ; expand matching snippets
try-expand-all-abbrevs try-expand-all-abbrevs
try-expand-list ; help when function args repeated another func's args try-expand-list ; help when args repeated another's args
try-expand-dabbrev try-expand-dabbrev
try-expand-dabbrev-all-buffers try-expand-dabbrev-all-buffers
try-expand-whole-kill ; grab text from the kill ring try-expand-whole-kill ; grab text from the kill ring
try-expand-dabbrev-from-kill ; similar to above try-expand-dabbrev-from-kill ; as above
try-complete-lisp-symbol-partially try-complete-lisp-symbol-partially
try-complete-lisp-symbol)) try-complete-lisp-symbol))
#+end_src #+end_src
@ -1448,129 +1386,68 @@ In the shell, IDEs and other systems, the key binding is typically ~TAB~. In mod
#+begin_src emacs-lisp #+begin_src emacs-lisp
(advice-add #'indent-for-tab-command :after #'hippie-expand) (advice-add #'indent-for-tab-command :after #'hippie-expand)
#+end_src #+end_src
*** Corfu
The default completion system either inserts the first option directly in the text (without cycling, so lets hope it gets it right the first time), or presents choices in another buffer (who wants to hop to it to select an expansion).
In =org-mode=, ~TAB~ calls [[help:org-cycle][org-cycle]], a complicated function, and attaching the hippie creates more complications. So, I cant use ~TAB~ to expand long words. Keep to ~M-/~ or something else on the Meta key? After using [[http://company-mode.github.io/][company]] for my completion back-end, I switch to [[https://github.com/minad/corfu][corfu]] as it works with the variable-spaced font of my org files (also see [[https://takeonrules.com/2022/01/17/switching-from-company-to-corfu-for-emacs-completion/][this essay]] for my initial motivation).
#+begin_src emacs-lisp
(global-set-key (kbd "M-TAB") 'hippie-expand)
#+end_src
**** Corfu
Then I converted to [[http://company-mode.github.io/][company]], and didnt really need to use Hippies built-in functionality. Now, Im wanting to try out [[https://github.com/minad/corfu][corfu]] (see [[https://takeonrules.com/2022/01/17/switching-from-company-to-corfu-for-emacs-completion/][this essay]] for more details). This package focuses on the UI, relying on other completion facilities. Seems like a good idea, as long as it works with the [[https://github.com/joaotavora/eglot][eglot]] package.
I personally find a menu that constantly pops up as Im typing quite annoying, and if =corfu-auto= is on, then I need a delay before it shows:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package corfu (use-package corfu
:straight (:files (:defaults "extensions/*.el"))
:custom :custom
(corfu-cycle t) ; Enable cycling for `corfu-next/previous' (corfu-cycle t)
(corfu-auto t) ; Enable auto completion (corfu-separator ?\s)
(corfu-auto-delay 1) ; Wait a second. Good idea if corfu-auto is t
(corfu-auto-prefix 3) ; Start if we have typed four letters
(corfu-quit-no-match t) ; quit if there is no match
(corfu-echo-documentation t) ; Show documentation in the echo area
(corfu-scroll-margin 5) ; Use scroll margin
(corfu-preview-current t) ; Disable current candidate preview
(corfu-preselect-first t) ; Showing us the goods? Take it with TAB/RET
(corfu-on-exact-match 'insert) ; Configure handling of exact matches
;; The Escape key, when the Corfu menu shows, goes into some sort of
;; normal movement without removing the menu. Odd behavior.
;; Let's _try_ to make it cancel:
:general
(:keymaps 'corfu-map
:states 'insert
"s-SPC" #'corfu-insert-separator
"C-n" #'corfu-next
"C-p" #'corfu-previous
"<escape>" #'corfu-quit
"<return>" #'corfu-insert
"M-d" #'corfu-show-documentation
"M-l" #'corfu-show-location)
:init :init
(global-corfu-mode)) (global-corfu-mode))
#+end_src
*Note:* The [[elisp:(describe-variable 'corfu-separator)][corfu-separator]] variable, defaults to the ~M-SPC~, can be used to limit the choices with two different sections of selection.
What about the locatio
With the [[https://github.com/minad/corfu/blob/main/extensions/corfu-quick.el][corfu-quick]] package, you can apply /labels/ to every section, making it easier to select something on the Corfu menu without either typing more of the section, or hitting the down arrow keys. The issue is actually loading the file that is embedded in the =extensions= directory. One approach is to call =load-file= on it:
#+begin_src emacs-lisp
;; (load-file (straight--file "repos/corfu/extensions/corfu-quick.el"))
(use-package corfu-quick
:after corfu
:straight (:type built-in)
:bind (:map corfu-map ("C-q" . corfu-quick-insert)))
#+end_src #+end_src
* Yet Another Snippet System (YASnippets)
Using [[https://github.com/joaotavora/yasnippet][yasnippet]] to convert templates into text:
With the [[https://github.com/minad/corfu/blob/main/extensions/corfu-history.el][corfu-history]] extension, we can sort candidates by their history position:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package corfu-history (use-package yasnippet
:after corfu
:straight (:type built-in)
:config :config
(corfu-history-mode)) (add-to-list 'yas-snippet-dirs
(expand-file-name "snippets" user-emacs-directory))
(yas-global-mode +1))
#+end_src #+end_src
Check out [[http://joaotavora.github.io/yasnippet/][the documentation]] for writing them.
For =eshell=, I dont want completion until I ask for it, so then I need to set these variables: Since I have troubles installing Dooms [[https://github.com/hlissner/doom-snippets][collection of snippets]], lets use the [[http://github.com/AndreaCrotti/yasnippet-snippets][yasnippet-snippets]] package:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package eshell (use-package yasnippet-snippets)
:after corfu
:config
(defun corfu-send-shell (&rest _)
"Send completion candidate when inside comint/eshell."
(cond
((and (derived-mode-p 'eshell-mode) (fboundp 'eshell-send-input))
(eshell-send-input))
((and (derived-mode-p 'comint-mode) (fboundp 'comint-send-input))
(comint-send-input))))
(advice-add #'corfu-insert :after #'corfu-send-shell)
:hook
(eshell-mode . ( lambda ()
(setq-local corfu-auto nil)
(setq-local corfu-preselect-first nil)
(corfu-mode))))
#+end_src #+end_src
**** Cape *** Auto Insert Templates
More Capf backends and =completion-in-region= commands are provided by the [[https://github.com/minad/cape][Cape]] package. Among others, the package supplies a file path and a Dabbrev completion backend. Cape provides the =cape-company-to-capf= adapter to reuse Company backends in Corfu. Furthermore the function =cape-super-capf= can merge multiple Capfs, such that the candidates of multiple Capfs are displayed together at the same time. The [[https://www.emacswiki.org/emacs/AutoInsertMode][auto-insert]] feature is a wee bit complicated. All I want is to associate a filename regular expression with a YASnippet template. I'm stealing some ideas from Henrik Lissner's [[https://github.com/hlissner/doom-emacs/blob/develop/modules/editor/file-templates/autoload.el][set-file-template!]] macro, but simpler?
The customization is to add =completion-at-point-functions=, used by =completion-at-point=:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package cape (use-package autoinsert
:init :init
(add-to-list 'completion-at-point-functions #'cape-dabbrev) (setq auto-insert-directory (expand-file-name "templates" user-emacs-directory))
(add-to-list 'completion-at-point-functions #'cape-file) ;; Don't prompt before insertion:
(add-to-list 'completion-at-point-functions #'cape-keyword) (setq auto-insert-query nil)
(add-to-list 'completion-at-point-functions #'cape-rfc1345)
(add-to-list 'completion-at-point-functions #'cape-abbrev)
(add-to-list 'completion-at-point-functions #'cape-ispell)
(add-to-list 'completion-at-point-functions #'cape-dict)
(add-to-list 'completion-at-point-functions #'cape-symbol)
:hook (add-hook 'find-file-hook 'auto-insert)
((eshell-load (auto-insert-mode t))
. (lambda () (add-to-list 'completion-at-point-functions #'cape-history)))))
#+end_src #+end_src
Since auto insertion requires entering data for particular fields, and for that Yasnippet is better, so in this case, we combine them:
Nice feature is be able to remove entries, for instance:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(setq completion-at-point-functions (defun ha-autoinsert-yas-expand()
(remove #'cape-line completion-at-point-functions)) "Replace text in yasnippet template."
(yas-expand-snippet (buffer-string) (point-min) (point-max)))
#+end_src #+end_src
The =pcomplete= system (that =eshell= uses) has some technical issues. Cape provides wrappers, which sanitize the [[help:pcomplete][pcomplete]] function. Until these bugs are fixed upstream, these two advices addresses the problem. And since I'll be associating snippets with new files all over my configuration, let's make a helper function:
#+begin_src emacs-lisp #+begin_src emacs-lisp
;; Silence the pcomplete capf, no errors or messages! (defun ha-auto-insert-file (filename-re snippet-name)
(advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent) "Autofill file buffer matching FILENAME-RE regular expression.
The contents inserted from the YAS SNIPPET-NAME."
;; Ensure that pcomplete does not write to the buffer ;; The define-auto-insert takes a regular expression and an ACTION:
;; and behaves as a pure `completion-at-point-function'. ;; ACTION may also be a vector containing successive single actions.
(advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify) (define-auto-insert filename-re
(vector snippet-name 'ha-autoinsert-yas-expand)))
#+end_src #+end_src
How easy is typing the ~M-/~ key, Now, I can type either that or ~M-TAB~ for dumb (i.e. long) word expa As an example of its use, any Org files loaded in /this project/ should insert my config file:
#+begin_src emacs-lisp
(ha-auto-insert-file (rx "hamacs/" (one-or-more any) ".org" eol) "hamacs-config")
#+end_src
*** Visual Replace with Visual Regular Expressions *** Visual Replace with Visual Regular Expressions
I appreciated the [[https://github.com/benma/visual-regexp.el][visual-regexp package]] to see what you want to change /before/ executing the replace. I appreciated the [[https://github.com/benma/visual-regexp.el][visual-regexp package]] to see what you want to change /before/ executing the replace.
#+begin_src emacs-lisp #+begin_src emacs-lisp