2021-11-02 00:27:14 +00:00
#+TITLE : General Emacs Configuration
#+AUTHOR : Howard X. Abrams
#+DATE : 2020-09-10
A literate programming file for configuring Emacs.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :exports none
2022-03-09 18:45:37 +00:00
;;; ha-config --- Emacs configuration. -*- lexical-binding: t; -* -
;;
;; © 2020-2022 Howard X. Abrams
2022-06-18 00:25:47 +00:00
;; Licensed under a Creative Commons Attribution 4.0 International License.
2022-03-09 18:45:37 +00:00
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams >
;; Maintainer: Howard X. Abrams
;; Created: September 10, 2020
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; ~/other/hamacs/ha-config.org
;; Using `find-file-at-point', and tangle the file to recreate this one .
;;
;;; Code:
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-02 19:41:06 +00:00
* Basic Configuration
2022-04-28 05:14:17 +00:00
I hate a fat-finger that stop Emacs:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-28 05:14:17 +00:00
(setq confirm-kill-emacs 'yes-or-no-p)
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-28 05:14:17 +00:00
2022-06-18 00:25:47 +00:00
New way to display line-numbers. I set mine to =relative= so that I can jump up and down by that value. Set this to =nil= to turn off, or =t= to be absolute.
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-02 19:41:06 +00:00
(setq display-line-numbers t
display-line-numbers-type 'relative)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-04-11 22:34:56 +00:00
I like the rendering to curved quotes using [[help:text-quoting-style ][text-quoting-style ]], because it improves the readability of documentation strings in the =∗ Help∗ = buffer and whatnot.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-11 22:34:56 +00:00
(setq text-quoting-style 'curve)
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-11 22:34:56 +00:00
2022-04-09 16:02:37 +00:00
Changes and settings I like introduced in Emacs 28:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-09 16:02:37 +00:00
(setq use-short-answers t
describe-bindings-outline t
completions-detailed t)
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-09 16:02:37 +00:00
2022-02-02 19:41:06 +00:00
As [[https://tecosaur.github.io/emacs-config/config.html ][tec wrote ]], I want to use =~/.authsource.gpg= as I don’ t 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.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-02 19:41:06 +00:00
(use-package epa-file
:config
(defvar epa-pinentry-mode)
(setq epa-file-select-keys nil
2022-08-02 18:13:48 +00:00
epa-pinentry-mode 'loopback
auth-sources '("~/.authinfo.gpg")
auth-source-cache-expiry nil))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
More settings:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-27 18:35:48 +00:00
(setq truncate-string-ellipsis "…" ; Unicode ellispis are nicer than "..."
debug-on-error t)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
The venerable [[help:hippie-expand ][hippie-expand ]] function does a better job than the default, [[help:dabbrev-expand ][dabbrev-expand ]], so let’ s swap it out (see this [[https://www.masteringemacs.org/article/text-expansion-hippie-expand ][essay ]] by Mickey Petersen):
#+begin_src emacs-lisp
2022-05-17 17:30:33 +00:00
(global-set-key [remap dabbrev-expand] 'hippie-expand)
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-17 17:30:33 +00:00
Details? Check out its [[help:hippie-expand-try-functions-list ][list of expanders ]].
2022-06-18 00:25:47 +00:00
Let’ s bind ~TAB~ instead of the default ~M-/~ . By default, ~TAB~ re-indents the line, but I find that I want that feature when I’ m in Evil’ s =normal state= and hit the ~=~ key, so changing this sounds good. But why not /have both/ ?
#+begin_src emacs-lisp
2022-05-17 17:30:33 +00:00
(advice-add #'indent-for-tab-command :after #'hippie-expand)
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-17 17:30:33 +00:00
Now while we’ re typing along, we can hit the ~TAB~ key after partially typing a word to have it completed.
2021-11-06 00:07:33 +00:00
And some Mac-specific settings:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-13 18:45:32 +00:00
(when (ha-running-on-macos?)
2022-02-02 19:41:06 +00:00
(setq mac-option-modifier 'meta
mac-command-modifier 'super)
2021-12-13 18:45:32 +00:00
(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
(add-to-list 'default-frame-alist '(ns-appearance . dark)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* Support Packages
2022-08-09 16:59:47 +00:00
** Piper and DWIM Shell Commands
2021-11-02 00:27:14 +00:00
Rewriting my shell scripts in Emacs Lisp uses my [[https://gitlab.com/howardabrams/emacs-piper ][emacs-piper project ]], and this code spills into my configuration code, so let's load it now:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-09 00:02:13 +00:00
(use-package piper
2022-08-09 16:57:20 +00:00
:straight (:host gitlab :repo "howardabrams/emacs-piper")
2022-06-18 00:25:47 +00:00
:commands piper shell-command-to-list ; I use this function often
2021-11-09 00:02:13 +00:00
:bind (:map evil-normal-state-map
2021-12-27 17:27:25 +00:00
("C-M-|" . piper)
("C-|" . piper-user-interface)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-08-09 16:59:47 +00:00
Interested in the [[https://github.com/xenodium/dwim-shell-command ][DWIM Shell Command ]] project from [[https://xenodium.com/emacs-dwim-shell-command/ ][Álvaro Ramírez ]]. It acts like the regular [[help:shell-command ][shell-command ]], but the ~q~ closes the results. A command with =<<f>>= operates on the filename of the current buffer, so =chmod a+x <<f>>= works as expected. More useful in [[help:dired ][dired ]] where you can mark a bunch of files and then operate on all files with such syntax.
#+begin_src emacs-lisp
(use-package dwim-shell-command
:straight (:host github :repo "xenodium/dwim-shell-command")
:bind (("M-!" . dwim-shell-command)
:map dired-mode-map
([remap dired-do-async-shell-command] . dwim-shell-command)
([remap dired-do-shell-command] . dwim-shell-command)
([remap dired-smart-shell-command] . dwim-shell-command)))
#+end_src
2021-11-02 00:27:14 +00:00
** Yet Another Snippet System (YASnippets)
Using [[https://github.com/joaotavora/yasnippet ][yasnippet ]] to convert templates into text:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package yasnippet
:config
2021-11-10 22:24:55 +00:00
(add-to-list 'yas-snippet-dirs (expand-file-name "snippets" user-emacs-directory))
2021-11-02 00:27:14 +00:00
(yas-global-mode +1))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-12 21:05:31 +00:00
Check out [[http://joaotavora.github.io/yasnippet/ ][the documentation ]] for writing them.
Seems the best [[https://github.com/hlissner/doom-snippets ][collection of snippets ]] is what Henrik Lissner has made for Doom (otherwise, we should use [[http://github.com/AndreaCrotti/yasnippet-snippets ][yasnippet-snippets ]] package):
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-24 20:47:00 +00:00
(use-package doom-snippets
:after yasnippet
2022-08-09 16:57:20 +00:00
:straight (:host github :repo "doomemacs/snippets")
2022-03-24 20:47:00 +00:00
:config
(add-to-list 'yas-snippet-dirs (thread-last user-emacs-directory
(expand-file-name "straight")
(expand-file-name "repos")
(expand-file-name "doom-snippets"))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-12 21:05:31 +00:00
*Note:* Including his snippets also includes some [[https://github.com/hlissner/doom-snippets#snippets-api ][helper functions ]] and other features.
2021-11-10 22:24:55 +00:00
** Auto Insert Templates
2022-06-18 00:25:47 +00:00
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
2021-11-10 22:24:55 +00:00
(use-package autoinsert
:init
(setq auto-insert-directory (expand-file-name "templates" user-emacs-directory))
2022-06-18 00:25:47 +00:00
;; Don't prompt before insertion:
2021-11-10 22:24:55 +00:00
(setq auto-insert-query nil)
(add-hook 'find-file-hook 'auto-insert)
(auto-insert-mode t))
2022-06-18 00:25:47 +00:00
#+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
2021-11-10 22:24:55 +00:00
(defun ha-autoinsert-yas-expand()
"Replace text in yasnippet template."
(yas-expand-snippet (buffer-string) (point-min) (point-max)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-10 22:24:55 +00:00
And since I'll be associating snippets with new files all over my configuration, let's make a helper function:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-10 22:24:55 +00:00
(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:
2022-06-18 00:25:47 +00:00
;; ACTION may also be a vector containing successive single actions.
2021-11-10 22:24:55 +00:00
(define-auto-insert filename-re
(vector snippet-name 'ha-autoinsert-yas-expand)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-10 22:24:55 +00:00
As an example of its use, any Org files loaded in /this project/ should insert my config file:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-10 22:24:55 +00:00
(ha-auto-insert-file (rx "hamacs/" (one-or-more any) ".org" eol) "hamacs-config")
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
** Request System
2022-06-18 00:25:47 +00:00
The above code needs the [[https://github.com/tkf/emacs-request ][request ]] package:
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package request
:init
(defvar ha-dad-joke nil "Holds the latest dad joke.")
:config
(defun ha-dad-joke ()
"Display a random dad joke."
(interactive)
(message (ha--dad-joke)))
(defun ha--dad-joke ()
"Return string containing a dad joke from www.icanhazdadjoke.com."
(setq ha-dad-joke nil) ; Clear out old joke
(ha--dad-joke-request)
(ha--dad-joke-wait))
(defun ha--dad-joke-wait ()
(while (not ha-dad-joke)
(sit-for 1))
(unless ha-dad-joke
(ha--dad-joke-wait))
ha-dad-joke)
(defun ha--dad-joke-request ()
(request "https://icanhazdadjoke.com"
:sync t
:complete (cl-function
(lambda (&key data &allow-other-keys)
(setq ha-dad-joke data))))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Dad Jokes!
2022-06-18 00:25:47 +00:00
The /critical part/ here, is the [[https://icanhazdadjoke.com/ ][Dad Joke ]] function, a =curl= call to a web service:
#+begin_src sh
2021-11-02 00:27:14 +00:00
curl -sH "Accept: text/plain" https:/ /icanhazdadjoke.com/
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
For this, I use the =request= package, which is /asynchronous/
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
#+end_src
2021-11-02 00:27:14 +00:00
* Configuration Changes
** Initial Settings and UI
2022-06-18 00:25:47 +00:00
Let's turn off the menu and other settings:
#+begin_src emacs-lisp
2022-08-02 18:13:48 +00:00
(when (display-graphic-p)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(horizontal-scroll-bar-mode -1)
(setq visible-bell 1))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-08-02 18:13:48 +00:00
I like being able to enable local variables in =.dir-local.el= files:
#+begin_src emacs-lisp
(setq enable-local-variables t)
#+end_src
2022-08-02 21:41:20 +00:00
** File Access
*** Changes on Save
Always spaces and never tabs. Note that we use =setq-default= since [[elisp:(describe-variable 'indent-tabs-mode) ][indent-tabs-mode ]] is a /buffer-local/ variable, meaning using =setq= , sets it for /that buffer file/ . We want this globally the default:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-08-02 18:13:48 +00:00
(setq-default indent-tabs-mode nil)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-08-02 18:13:48 +00:00
When I push changes to my files to Gerrit and other code review, I don’ t want trailing spaces or any tabs to appear, so let’ s fix all files when I [[elisp:(describe-variable 'before-save-hook) ][save them ]]:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-08-02 18:13:48 +00:00
(defun ha-cleanup-buffer-file ()
"Cleanup a file, often done before a file save."
(interactive)
2022-08-02 21:41:20 +00:00
(ignore-errors
2022-08-09 16:58:22 +00:00
(unless (equal major-mode 'makefile-bsdmake-mode)
(untabify (point-min) (point-max)))
2022-08-02 21:41:20 +00:00
(delete-trailing-whitespace)))
2022-08-02 18:13:48 +00:00
(add-hook 'before-save-hook #'ha-cleanup-buffer-file)
2022-06-18 00:25:47 +00:00
#+end_src
2022-08-02 21:41:20 +00:00
*** Recent Files
The [[https://www.emacswiki.org/emacs/RecentFiles ][recentf ]] feature has been in Emacs for a long time, but it has a problem with Tramp, as we need to turn off the cleanup feature that attempts to =stat= all the files and remove them from the =recent= accessed list if they are readable. The requires recentf to open up a remote files which blocks Emacs at the most inopportune times… like when trying to reboot the machine.
#+begin_src emacs-lisp
(use-package recentf
:straight (:type built-in)
:config
(setq recentf-auto-cleanup 'never) ;; disable before we start recentf!
(recentf-mode 1))
#+end_src
*** File Backups
While I use git as much as I can, sometimes Emacs’ built-in file backup and versioning feature has saved me for files that aren’ t.
As [[https://philjackson.github.io//emacs/backups/2022/01/31/keeping-backups-of-every-edited-file/ ][Phil Jackson ]] mentioned, Emacs has a lot of variations to its file backup strategy, and either change the [[help:backup-directory-alist ][backup-directory-alist ]] to put individual file backups elsewhere, e.g.
#+begin_src emacs-lisp
(setq backup-directory-alist `(("." . ,(concat user-emacs-directory "backups"))))
#+end_src
Or leave them in the current directory, but create an alias so =ls= doesn’ t display them, e.g.
#+begin_src sh
alias ls="ls --color=auto --hide= '*~'"
#+end_src
I'm leaving them side-by-side, but I am keeping some extra copies:
#+begin_src emacs-lisp
(setq create-lockfiles nil ; Having .# files around ain't helpful
auto-save-default t
delete-old-versions t
kept-new-versions 6
kept-old-versions 2
version-control t)
#+end_src
The [[help:version-control ][version-control ]] variable affect backups (not some sort of global VC setting), this makes numeric backups.
*** Auto Save of Files
Save the file whenever I move away from Emacs (see [[https://irreal.org/blog/?p=10314 ][this essay ]]):
#+begin_src emacs-lisp
(defun save-all-buffers ()
"Saves all buffers, because, why not?"
(interactive)
(save-some-buffers t))
(add-hook 'focus-out-hook 'save-all-buffers)
#+end_src
2021-11-02 00:27:14 +00:00
** Completing Read User Interface
After using Ivy, I am going the route of a =completing-read= interface that extends the original Emacs API, as opposed to implementing backend-engines or complete replacements.
*** Vertico
2022-06-18 00:25:47 +00:00
The [[https://github.com/minad/vertico ][vertico ]] package puts the completing read in a vertical format, and like [[https://github.com/raxod502/selectrum#vertico ][Selectrum ]], it extends Emacs’ built-in functionality, instead of adding a new process. This means all these projects work together.
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package vertico
:config (vertico-mode))
2022-06-18 00:25:47 +00:00
#+end_src
My issue with Vertico is when calling =find-file= , the Return key opens =dired= , instead of inserting the directory at point. This package addresses this:
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package vertico-directory
:straight (el-patch :files ("~/.emacs.d/straight/repos/vertico/extensions/vertico-directory.el"))
;; More convenient directory navigation commands
:bind (:map vertico-map
("RET" . vertico-directory-enter)
2021-11-06 00:07:33 +00:00
; ("DEL" . vertico-directory-delete-word)
2021-11-02 00:27:14 +00:00
("M-RET" . minibuffer-force-complete-and-exit)
("M-TAB" . minibuffer-complete))
;; Tidy shadowed file names
:hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
2022-06-18 00:25:47 +00:00
#+end_src
2022-06-15 16:54:34 +00:00
*** Hotfuzz
2022-06-18 00:25:47 +00:00
This fuzzy completion style is like the built-in =flex= style, but has a better scoring algorithm, non-greedy and ranks completions that match at word; path component; or camelCase boundaries higher.
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
(use-package hotfuzz)
#+end_src
While flexible at matching, you have to get the /order/ correct. For instance, ~alireg~ matches with [[help:align-regexp ][align-regexp ]], but ~regali~ does not, so we will use =hotfuzz= for scoring, and not use this as a completion-project (see the =fussy= project below).
2021-11-02 00:27:14 +00:00
*** Orderless
2022-06-15 16:54:34 +00:00
While the space can be use to separate words (acting a bit like a =.*= regular expression), the [[https://github.com/oantolin/orderless ][orderless ]] project allows those words to be in any order.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-06-15 16:54:34 +00:00
(use-package orderless
:commands (orderless-filter)
:custom
(completion-styles '(orderless basic))
(completion-ignore-case t)
(completion-category-defaults nil)
(completion-category-overrides '((file (styles partial-completion)))))
2022-06-18 00:25:47 +00:00
#+end_src
*Note:* Open more than one file at once with =find-file= with a wildcard. We may also give the =initials= completion style a try.
2022-06-15 16:54:34 +00:00
*** Fussy Filtering and Matching
2022-06-18 00:25:47 +00:00
The [[https://github.com/jojojames/fussy ][fussy ]] project is a fuzzy pattern matching extension for the normal [[help:completing-read ][completing-read ]] interface. By default, it uses [[https://github.com/lewang/flx ][flx ]], but we can specify other sorting and filtering algorithms.
2021-11-02 00:27:14 +00:00
2022-06-15 16:54:34 +00:00
How does it compare? Once upon a time, I enjoyed typing ~plp~ for =package-list-packages= , and when I switched to [[https://github.com/oantolin/orderless ][orderless ]], I would need to put a space between the words. While I will continue to play with the different mechanism, I’ ll combine =hotfuzz= and =orderless= .
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-06-15 16:54:34 +00:00
(use-package fussy
:straight (:host github :repo "jojojames/fussy")
:config
(push 'fussy completion-styles)
(setq completion-category-defaults nil
completion-category-overrides nil
fussy-filter-fn 'fussy-filter-orderless-flex
fussy-score-fn 'fussy-hotfuzz-score))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Savehist
Persist history over Emacs restarts using the built-in [[https://www.emacswiki.org/emacs/SaveHist ][savehist ]] project. Since both Vertico and Selectrum sorts by history position, this should make the choice /smarter/ with time.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package savehist
:init
(savehist-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Marginalia
The [[https://github.com/minad/marginalia ][marginalia ]] package gives a preview of =M-x= functions with a one line description, extra information when selecting files, etc. Nice enhancement without learning any new keybindings.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
;; Enable richer annotations using the Marginalia package
(use-package marginalia
:init
(setq marginalia-annotators-heavy t)
:config
(marginalia-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* Key Bindings
To begin my binding changes, let's turn on [[https://github.com/justbur/emacs-which-key ][which-key ]]:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package which-key
:init (setq which-key-popup-type 'minibuffer)
:config (which-key-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-24 20:47:00 +00:00
Why would I ever quit Emacs with a simple keybinding? Let’ s override it:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-24 20:47:00 +00:00
(global-set-key (kbd "s-q") 'bury-buffer)
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-25 22:19:46 +00:00
Oh, and let’ s not close the frame, but instead, the window:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-25 22:19:46 +00:00
(global-set-key (kbd "s-w") 'delete-window)
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-16 20:34:05 +00:00
** Undo
2022-06-18 00:25:47 +00:00
I mean, I /always/ use ~C-/~ for [[help:undo ][undo ]] (and ~C-?~ for [[help:undo-redo ][redo ]]), but when I’ m on the Mac, I need to cover my bases.
2022-03-25 18:01:04 +00:00
Why use [[https://gitlab.com/ideasman42/emacs-undo-fu ][undo-fu ]] instead of the built-in undo functionality? Well, there isn’ t much to the project (that’ s a good thing), but It basically doesn’ t /cycle/ around the redo, which annoying.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-09 01:27:09 +00:00
(use-package undo-fu
:config
2022-03-25 18:01:04 +00:00
(global-set-key [remap undo] 'undo-fu-only-undo)
(global-set-key [remap undo-redo] 'undo-fu-only-redo)
2021-11-09 01:27:09 +00:00
(global-unset-key (kbd "s-z"))
(global-set-key (kbd "s-z") 'undo-fu-only-undo)
(global-set-key (kbd "s-S-z") 'undo-fu-only-redo))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
** Evil-Specific Keybindings
Can we change Evil at this point? Some tips:
- [[https://github.com/noctuid/evil-guide ]]
- [[https://nathantypanski.com/blog/2014-08-03-a-vim-like-emacs-config.html ]]
2022-05-17 17:29:30 +00:00
- [[https://stackoverflow.com/questions/25542097/emacs-evil-mode-how-to-change-insert-state-to-emacs-state-automatically ][Evil insert state is really Emacs? ]] Real answer to that is to set [[help:evil-disable-insert-state-bindings ][evil-disable-insert-state-bindings ]]
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-24 00:41:17 +00:00
(use-package evil
:init
(setq evil-undo-system 'undo-fu
2022-08-02 18:13:48 +00:00
evil-want-fine-undo t ; Be more like Emacs
2021-11-24 00:41:17 +00:00
evil-disable-insert-state-bindings t
evil-want-keybinding nil
evil-want-integration t
2022-03-11 18:55:39 +00:00
evil-escape-key-sequence "jk"
2021-11-24 00:41:17 +00:00
evil-escape-unordered-key-sequence t)
2021-11-02 00:27:14 +00:00
2021-11-24 00:41:17 +00:00
:config
2022-05-17 17:29:30 +00:00
;; Now that `evil-disable-insert-state-bindings' works to use Emacs
;; keybindings in Evil's insert mode, we no longer need this code:
;; (setq evil-insert-state-map (make-sparse-keymap))
;; (define-key evil-insert-state-map (kbd "<escape >") 'evil-normal-state)
2021-11-24 00:41:17 +00:00
2022-05-17 17:29:30 +00:00
;; In insert mode, type C-o to execute a single Evil command:
(define-key evil-insert-state-map (kbd "C-o") 'evil-execute-in-normal-state)
2021-11-02 00:27:14 +00:00
2021-11-24 00:41:17 +00:00
(evil-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-07-07 20:44:41 +00:00
While I’ m pretty good with the VIM keybindings, I would like to play around with the [[https://evil.readthedocs.io/en/latest/extension.html#text-objects ][text objects ]] and how it compares to others (including the surround), for instance:
2022-04-28 05:09:03 +00:00
- ~diw~ :: deletes a word, but can be anywhere in it, while ~de~ deletes to the end of the word.
- ~daw~ :: deletes a word, plus the surrounding space, but not punctuation.
- ~xis~ :: changes a /sentence,/ and if ~i~ is ~a~ , it gets rid of the surrounding whitespace as well. Probably ~das~ and ~cis~ .
- ~xip~ :: changes a /paragraph/ .
2022-07-07 20:44:41 +00:00
- ~xio~ :: changes a /symbol/ , which can change for each mode, but works with =snake_case= and other larger-than-word variables.
- Surrounding punctuation, like quotes, parenthesis, brackets, etc. also work, so ~ci)~ changes all the parameters to a function call, for instance
- ~a”~ :: a double quoted string
- ~i”~ :: inner double quoted string
- ~a'~ :: a single quoted string
- ~i'~ :: inner single quoted string
- ~a`~ :: a back quoted string
- ~i`~ :: inner back quoted string
*Note:* The ~x~ in the above examples are ~d~ for delete, ~v~ for select, ~y~ for copying and ~c~ for changing.
*** Key Chord
2021-11-02 00:27:14 +00:00
Using the key-chord project allows me to make Escape be on two key combo presses on both sides of my keyboard:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package key-chord
:config
(key-chord-mode t)
(key-chord-define-global "fd" 'evil-normal-state)
(key-chord-define-global "jk" 'evil-normal-state)
(key-chord-define-global "JK" 'evil-normal-state))
2022-06-18 00:25:47 +00:00
#+end_src
2022-07-07 20:46:26 +00:00
*** Evil Easy Motion
The [[https://github.com/PythonNut/evil-easymotion ][evil-easymotion ]] project combines [[Jump with Avy ][avy ]] and evil keybindings, where ~SPC j~ shows labels for all the lines below the cursor, so that you can jump right there. This doesn’ t work well with a leader, but what about using Key Chords?
#+begin_src emacs-lisp
(use-package evil-easymotion
:config (evilem-default-keybindings "<f19 >"))
#+end_src
My ~F19~ key is within easy reach of my [[https://configure.zsa.io/moonlander/layouts/L4laD/latest/0 ][Moonlander configuration ]], so this might be a good, if somewhat distracting, feature. Perhaps a better solution is to use [[Jump with Avy ][avy ]] (see below).
2021-11-02 00:27:14 +00:00
** General Leader Key Sequences
The one thing that both Spacemacs and Doom taught me, is how much I like the /key sequences/ that begin with a leader key. In both of those systems, the key sequences begin in the /normal state/ with a space key. This means, while typing in /insert state/ , I have to escape to /normal state/ and then hit the space.
I'm not trying an experiment where specially-placed function keys on my fancy ergodox keyboard can kick these off using [[https://github.com/noctuid/general.el ][General Leader ]] project. Essentially, I want a set of leader keys for Evil's /normal state/ as well as a global leader in all modes.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-06 00:07:33 +00:00
(use-package general
2022-05-14 16:23:41 +00:00
:custom
2022-05-16 20:34:05 +00:00
(general-use-package-emit-autoloads t)
2022-05-14 16:23:41 +00:00
2021-11-06 00:07:33 +00:00
:config
(general-evil-setup t)
2022-05-14 16:23:41 +00:00
2021-11-06 00:07:33 +00:00
(general-create-definer ha-leader
2021-12-08 21:57:42 +00:00
:states '(normal visual motion)
:keymaps 'override
2021-11-06 00:07:33 +00:00
:prefix "SPC"
:non-normal-prefix "M-SPC"
2021-11-24 00:34:48 +00:00
:global-prefix "<f13 >")
(general-create-definer ha-local-leader
2021-12-08 21:57:42 +00:00
:states '(normal visual motion)
2021-12-14 19:26:11 +00:00
:prefix "SPC m"
:global-prefix "<f17 >"
2021-12-14 19:42:26 +00:00
:non-normal-prefix "S-SPC"))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Top-Level Operations
2021-11-10 01:22:13 +00:00
Let's try this general "space" prefix by defining some top-level operations, including hitting ~space~ twice to bring up the =M-x= collection of functions:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(ha-leader
"SPC" '("M-x" . execute-extended-command)
2021-11-06 00:07:33 +00:00
"." '("repeat" . repeat)
2022-08-09 16:59:47 +00:00
"!" '("shell command" . dwim-shell-command)
"|" 'piper
2022-06-30 18:58:31 +00:00
"X" '("org capture" . org-capture)
"L" '("store org link" . org-store-link)
2021-11-06 00:07:33 +00:00
"RET" 'bookmark-jump
2021-11-10 01:28:58 +00:00
"a" '(:ignore t :which-key "apps")
"o" '(:ignore t :which-key "org/open")
2022-03-11 18:55:39 +00:00
"o i" 'imenu
2022-03-24 17:43:08 +00:00
"m" '(:ignore t :which-key "mode")
"u" 'universal-argument)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
And ways to stop the system:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-24 17:43:08 +00:00
(ha-leader
"q" '(:ignore t :which-key "quit/session")
"q b" '("bury buffer" . bury-buffer)
"q w" '("close window" . delete-window)
"q K" '("kill emacs (and dæmon)" . save-buffers-kill-emacs)
"q q" '("quit emacs" . save-buffers-kill-terminal)
"q Q" '("quit without saving" . evil-quit-all-with-error-code))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** File Operations
2022-06-18 00:25:47 +00:00
While =find-file= is still my bread and butter, I like getting information about the file associated with the buffer. For instance, the file path:
#+begin_src emacs-lisp
(defun ha-relative-filepath (filepath)
"Return the FILEPATH without the HOME directory and typical filing locations.
The expectation is that this will return a filepath with the proejct name."
(let* ((home-re (rx (literal (getenv "HOME")) "/"))
(work-re (rx (regexp home-re)
(or "work" "other" "projects") ; Typical organization locations
"/"
(optional (or "4" "5" "xway") "/") ; Sub-organization locations
)))
(cond
((string-match work-re filepath) (substring filepath (match-end 0)))
((string-match home-re filepath) (substring filepath (match-end 0)))
(t filepath))))
(defun ha-yank-buffer-path (&optional root)
"Copy the file path of the buffer relative to my 'work' directory, ROOT."
(interactive)
(if-let (filename (buffer-file-name (buffer-base-buffer)))
(message "Copied path to clipboard: %s"
(kill-new (abbreviate-file-name
(if root
(file-relative-name filename root)
(ha-relative-filepath filename)))))
(error "Couldn't find filename in current buffer")))
(defun ha-yank-project-buffer-path (&optional root)
"Copy the file path of the buffer relative to the file's project.
When given ROOT, this copies the filepath relative to that."
(interactive)
(if-let (filename (buffer-file-name (buffer-base-buffer)))
(message "Copied path to clipboard: %s"
(kill-new
(f-relative filename (or root (projectile-project-root filename)))))
(error "Couldn't find filename in current buffer")))
#+end_src
Perhaps my OCD is out-of-control, but I want to load a file in another window, but want to control which window.
#+begin_src emacs-lisp
2021-12-29 19:08:27 +00:00
(defmacro ha-create-find-file-window (winum)
(let ((func-name (intern (format "ha-find-file-window-%s" winum)))
(call-func (intern (format "winum-select-window-%s" winum))))
`(defun ,func-name ()
"Call `find-file' in the particular `winum' window."
(interactive)
(,call-func)
(call-interactively 'find-file))))
(dolist (winum (number-sequence 1 9))
(ha-create-find-file-window winum))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-29 19:08:27 +00:00
2021-11-02 00:27:14 +00:00
With these helper functions in place, I can create a leader collection for file-related functions:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-29 19:08:27 +00:00
(ha-leader
"f" '(:ignore t :which-key "files")
"f f" '("load" . find-file)
"f F" '("load new window" . find-file-other-window)
"f s" '("save" . save-buffer)
"f S" '("save as" . write-buffer)
"f SPC" '("project" . projectile-find-file)
"f r" '("recent" . recentf-open-files)
"f c" '("copy" . copy-file)
"f R" '("rename" . rename-file)
"f D" '("delete" . delete-file)
"f y" '("yank path" . ha-yank-buffer-path)
"f Y" '("yank path from project" . ha-yank-project-buffer-path)
"f d" '("dired" . dired)
"f 1" '("load win-1" . ha-find-file-window-1)
"f 2" '("load win-2" . ha-find-file-window-2)
"f 3" '("load win-3" . ha-find-file-window-3)
"f 4" '("load win-4" . ha-find-file-window-4)
"f 5" '("load win-5" . ha-find-file-window-5)
"f 6" '("load win-6" . ha-find-file-window-6)
"f 7" '("load win-7" . ha-find-file-window-7)
"f 8" '("load win-8" . ha-find-file-window-8)
"f 9" '("load win-9" . ha-find-file-window-9))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Buffer Operations
This section groups buffer-related operations under the "SPC b" sequence.
Putting the entire visible contents of the buffer on the clipboard is often useful:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-29 17:34:48 +00:00
(defun ha-yank-buffer-contents ()
2021-11-02 00:27:14 +00:00
"Copy narrowed contents of the buffer to the clipboard."
(interactive)
(kill-new (buffer-substring-no-properties
(point-min) (point-max))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
And the collection of useful operations:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(ha-leader
"b" '(:ignore t :which-key "buffers")
2022-04-28 15:56:57 +00:00
"b B" '("switch" . persp-switch-to-buffer)
"b o" '("switch" . switch-to-buffer-other-window)
"b O" '("other" . projectile-switch-buffer-to-other-window)
2021-11-02 00:27:14 +00:00
"b i" '("ibuffer" . ibuffer)
"b I" '("ibuffer" . ibuffer-other-window)
"b k" '("persp remove" . persp-remove-buffer)
"b N" '("new" . evil-buffer-new)
"b d" '("delete" . persp-kill-buffer*)
"b r" '("revert" . revert-buffer)
"b s" '("save" . save-buffer)
"b S" '("save all" . evil-write-all)
"b n" '("next" . next-buffer)
"b p" '("previous" . previous-buffer)
2021-12-29 17:34:48 +00:00
"b y" '("copy contents" . ha-yank-buffer-contents)
2021-11-02 00:27:14 +00:00
"b z" '("bury" . bury-buffer)
"b Z" '("unbury" . unbury-buffer)
;; And double up on the bookmarks:
"b m" '("set bookmark" . bookmark-set)
"b M" '("delete mark" . bookmark-delete))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Toggle Switches
The goal here is toggle switches and other miscellaneous settings.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(ha-leader
2021-11-12 05:02:58 +00:00
"t" '(:ignore t :which-key "toggles")
"t a" '("abbrev" . abbrev-mode)
"t d" '("debug" . toggle-debug-on-error)
"t f" '("auto-fill" . auto-fill-mode)
"t l" '("line numbers" . display-line-numbers-mode)
"t t" '("truncate" . toggle-truncate-lines)
"t v" '("visual" . visual-line-mode)
"t w" '("whitespace" . whitespace-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-25 18:02:02 +00:00
**** Line Numbers
2022-06-18 00:25:47 +00:00
Since we can't automatically toggle between relative and absolute line numbers, we create this function:
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(defun ha-toggle-relative-line-numbers ()
(interactive)
(if (eq display-line-numbers 'relative)
(setq display-line-numbers t)
(setq display-line-numbers 'relative)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-25 18:02:02 +00:00
Add it to the toggle menu:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-25 18:02:02 +00:00
(ha-leader
"t r" '("relative lines" . ha-toggle-relative-line-numbers))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-25 18:02:02 +00:00
**** Narrowing
I like the focus the [[info:emacs#Narrowing ][Narrowing features ]] offer, but what a /dwim/ aspect:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-25 18:02:02 +00:00
(defun ha-narrow-dwim ()
"Narrow to region or org-tree or widen if already narrowed."
(interactive)
(cond
((buffer-narrowed-p) (widen))
((region-active-p) (narrow-to-region (region-beginning) (region-end)))
((and (fboundp 'logos-focus-mode)
(seq-contains local-minor-modes 'logos-focus-mode 'eq))
(logos-narrow-dwim))
((eq major-mode 'org-mode) (org-narrow-to-subtree))
(t (narrow-to-defun))))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-25 18:02:02 +00:00
And put it on the toggle menu:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-25 18:02:02 +00:00
(ha-leader "t n" '("narrow" . ha-narrow-dwim))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Window Operations
While it comes with Emacs, I use [[https://www.emacswiki.org/emacs/WinnerMode ][winner-mode ]] to undo window-related changes:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package winner
:custom
(winner-dont-bind-my-keys t)
:config
(winner-mode +1))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
Use the [[https://github.com/abo-abo/ace-window ][ace-window ]] project to jump to any window you see:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package ace-window)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
This package, bound to ~SPC w w~ , also allows operations specified before choosing the window:
- ~x~ - delete window
- ~m~ - swap windows
- ~M~ - move window
- ~c~ - copy window
- ~j~ - select buffer
- ~n~ - select the previous window
- ~u~ - select buffer in the other window
2022-06-18 00:25:47 +00:00
- ~c~ - split window, either vertically or horizontally
2021-11-02 00:27:14 +00:00
- ~v~ - split window vertically
- ~b~ - split window horizontally
- ~o~ - maximize current window
- ~?~ - show these command bindings
2022-06-18 00:25:47 +00:00
Keep in mind, these shortcuts work with more than two windows open. For instance, ~SPC w w x 3~ closes the "3" window.
2021-11-02 00:27:14 +00:00
To jump to a window even quicker, use the [[https://github.com/deb0ch/emacs-winum ][winum package ]]:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package winum
:config
(winum-mode +1))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-29 17:35:39 +00:00
And when creating new windows, why isn't the new window selected?
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-29 17:35:39 +00:00
(defun jump-to-new-window (&rest _arg)
"Advice function to jump to newly spawned window."
(other-window 1))
(dolist (command '(split-window-below split-window-right
evil-window-split evil-window-vsplit))
(advice-add command :after #'jump-to-new-window))
2022-06-18 00:25:47 +00:00
#+end_src
This is nice since the window numbers are always present on a Doom modeline, but they order the window numbers /differently/ than =ace-window= . Let's see which I end up liking better.
2021-11-12 05:05:41 +00:00
2021-11-02 00:27:14 +00:00
The ~0~ key/window should be always associated with a project-specific tree window:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(add-to-list 'winum-assign-functions
(lambda ()
(when (string-match-p (buffer-name) ".*\\*NeoTree\\* .*") 10)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
Let's try this out with a Hydra since some I can /repeat/ some commands (e.g. enlarge window). It also allows me to organize the helper text.
#+begin_src emacs-lisp
2022-03-03 23:08:09 +00:00
(use-package hydra
:config
(defhydra hydra-window-resize (:color blue :hint nil) "
_w_ : select _n_ : new _^_ : taller (t) _z_ : Swap _+_ : text larger
_c_ : cycle _d_ : delete _V_ : shorter (T) _u_ : undo _-_ : text smaller
_j_ : go up _=_ : balance _>_ : wider _U_ : undo+ _F_ : font larger
_k_ : down _m_ : maximize _<_ : narrower _r_ : redo _f_ : font smaller
_h_ : left _s_ : h-split _e_ : balanced _R_ : redo+ _0_ : toggle neotree
_l_ : right _v_ : v-split _o_ : choose by number (also 1-9)
"
("w" ace-window)
("c" other-window)
("=" balance-windows)
("m" delete-other-windows)
("d" delete-window)
("D" ace-delete-window)
2022-06-18 00:25:47 +00:00
("z" ace-swap-window)
2022-03-03 23:08:09 +00:00
("u" winner-undo)
("U" winner-undo :color pink)
("C-r" winner-redo)
("r" winner-redo)
("R" winner-redo :color pink)
("n" evil-window-new)
("j" evil-window-up)
("k" evil-window-down)
("h" evil-window-left)
("l" evil-window-right)
("o" other-window)
("s" evil-window-split)
("v" evil-window-vsplit)
("F" font-size-increase :color pink)
("f" font-size-decrease :color pink)
("+" text-scale-increase :color pink)
("=" text-scale-increase :color pink)
("-" text-scale-decrease :color pink)
("^" evil-window-increase-height :color pink)
("V" evil-window-decrease-height :color pink)
("t" evil-window-increase-height :color pink)
("T" evil-window-decrease-height :color pink)
(">" evil-window-increase-width :color pink)
("<" evil-window-decrease-width :color pink)
("e" balance-windows)
("o" winum-select-window-by-number)
("1" winum-select-window-1)
("2" winum-select-window-2)
("3" winum-select-window-3)
("4" winum-select-window-4)
("5" winum-select-window-5)
("6" winum-select-window-6)
("7" winum-select-window-7)
("8" winum-select-window-8)
("9" winum-select-window-9)
("0" neotree-toggle)
;; Extra bindings:
("t" evil-window-increase-height :color pink)
("T" evil-window-decrease-height :color pink)
("." evil-window-increase-width :color pink)
("," evil-window-decrease-width :color pink)
("q" nil :color blue)))
(ha-leader "w" '("windows" . hydra-window-resize/body))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Search Operations
2021-11-09 16:22:43 +00:00
Ways to search for information goes under the ~s~ key. This primarily depends on the [[https://github.com/dajva/rg.el ][rg ]] package, which builds on the internal =grep= system, and creates a =*rg*= window with =compilation= mode, so ~C-j~ and ~C-k~ will move and show the results by loading those files.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-09 16:22:43 +00:00
(use-package rg
2022-08-02 18:13:48 +00:00
:init ; I sometimes call `grep`:
2022-01-06 23:36:39 +00:00
; (grep-apply-setting 'grep-command "rg -n -H --no-heading -e ")
2021-11-09 16:22:43 +00:00
:config
2022-05-11 17:52:12 +00:00
(rg-enable-default-bindings (kbd "M-R"))
2021-11-09 16:22:43 +00:00
(ha-leader
"s" '(:ignore t :which-key "search")
2021-12-13 18:46:51 +00:00
"s q" '("close" . ha-rg-close-results-buffer)
2021-11-09 16:22:43 +00:00
"s r" '("dwim" . rg-dwim)
"s s" '("search" . rg)
"s S" '("literal" . rg-literal)
"s p" '("project" . rg-project) ; or projectile-ripgrep
"s d" '("directory" . rg-dwim-project-dir)
"s f" '("file only" . rg-dwim-current-file)
"s j" '("next results" . ha-rg-go-next-results)
"s k" '("prev results" . ha-rg-go-previous-results)
"s b" '("results buffer" . ha-rg-go-results-buffer))
2021-12-13 18:46:51 +00:00
(defun ha-rg-close-results-buffer ()
"Close to the `*rg*' buffer that `rg' creates."
(interactive)
(kill-buffer "*rg*"))
2021-11-09 16:22:43 +00:00
(defun ha-rg-go-results-buffer ()
"Pop to the `*rg*' buffer that `rg' creates."
(interactive)
2021-11-10 01:18:52 +00:00
(pop-to-buffer "*rg*"))
2021-11-09 16:22:43 +00:00
(defun ha-rg-go-next-results ()
"Bring the next file results into view."
(interactive)
(ha-rg-go-results-buffer)
(next-error-no-select)
(compile-goto-error))
2021-11-02 00:27:14 +00:00
2021-11-09 16:22:43 +00:00
(defun ha-rg-go-previous-results ()
"Bring the previous file results into view."
(interactive)
(ha-rg-go-results-buffer)
(previous-error-no-select)
(compile-goto-error)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-29 17:34:48 +00:00
2022-06-18 00:25:47 +00:00
The [[https://github.com/mhayashi1120/Emacs-wgrep ][wgrep package ]] integrates with =ripgrep= . Typically, you hit ~i~ to automatically go into =wgrep-mode= and edit away, but since I typically want to edit everything at the same time, I have a toggle that should work as well:
2021-12-29 17:34:48 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-29 17:34:48 +00:00
(use-package wgrep
:after rg
:commands wgrep-rg-setup
:hook (rg-mode-hook . wgrep-rg-setup)
:config
(ha-leader
2022-06-18 00:25:47 +00:00
:keymaps 'rg-mode-map ; Actually, `i` works!
2021-12-29 17:34:48 +00:00
"s w" '("wgrep-mode" . wgrep-change-to-wgrep-mode)
"t w" '("wgrep-mode" . wgrep-change-to-wgrep-mode)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Text Operations
Stealing much of this from Spacemacs.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-12 05:02:58 +00:00
(ha-leader
"x" '(:ignore t :which-key "text")
"x a" '("align" . align-regexp)
"x q" '("fill paragraph" . fill-paragraph)
"x p" '("unfill paragraph" . unfill-paragraph))
2022-06-18 00:25:47 +00:00
#+end_src
Unfilling a paragraph joins all the lines in a paragraph into a single line. Taken [[http://www.emacswiki.org/UnfillParagraph ][from here ]] … I use this all the time:
2021-11-10 01:26:42 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-10 01:26:42 +00:00
(defun unfill-paragraph ()
"Convert a multi-line paragraph into a single line of text."
(interactive)
(let ((fill-column (point-max)))
(fill-paragraph nil)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*** Help Operations
While the ~C-h~ is easy enough, I am now in the habit of typing ~SPC h~ instead.
2022-06-18 00:25:47 +00:00
Since I tweaked the help menu, I craft my own menu:
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(ha-leader
"h" '(:ignore t :which-key "help")
2022-03-24 17:43:08 +00:00
"h a" '("apropos" . apropos-command)
2022-04-09 16:02:37 +00:00
"h c" '("elisp cheatsheet" . shortdoc-display-group)
2021-11-09 16:22:43 +00:00
"h e" '("errors" . view-echo-area-messages)
2022-03-24 17:43:08 +00:00
"h E" '("emacs-lisp" . (lambda () (interactive) (info "elisp")))
2021-11-02 00:27:14 +00:00
"h f" '("function" . describe-function)
2022-03-18 20:50:40 +00:00
"h F" '("font" . describe-font)
"h =" '("face" . describe-face)
2021-11-02 00:27:14 +00:00
"h k" '("key binding" . describe-key)
2022-04-09 16:02:37 +00:00
"h K" '("key map" . describe-keymap)
2022-03-24 17:43:08 +00:00
"h m" '("mode" . describe-mode)
"h p" '("package" . describe-package)
"h s" '("symbol" . info-lookup-symbol)
"h v" '("variable" . describe-variable)
"h i" '("info" . info)
2022-04-09 16:02:37 +00:00
"h I" '("info manual" . info-display-manual)
"h j" '("info jump" . info-apropos))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-04-09 16:02:37 +00:00
Remember these keys in the *Help* buffer:
- ~s~ :: view source of the function
- ~i~ :: view info manual of the function
2021-12-08 21:57:42 +00:00
Let's make Info behave a little more VI-like:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-08 21:57:42 +00:00
(use-package info
:straight (:type built-in)
:general
(:states 'normal :keymaps 'Info-mode-map
2022-03-24 17:43:08 +00:00
"B" 'Info-bookmark-jump
"Y" 'org-store-link
"H" 'Info-history-back
"L" 'Info-history-forward
"u" 'Info-up
"U" 'Info-directory
"T" 'Info-top-node
2021-12-08 21:57:42 +00:00
"p" 'Info-backward-node
"n" 'Info-forward-node)) ; Old habit die hard
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-28 15:56:57 +00:00
*** Consult
2022-06-18 00:25:47 +00:00
The [[https://github.com/minad/consult ][consult project ]] aims to use libraries like [[*Vertico ][Vertico ]] to enhance specific, built-in, Emacs functions. I appreciate this project that when selecting an element in the minibuffer, it displays what you are looking at… for instance, it previews a buffer before choosing it. Unlike /Vertico/ and /Orderless/ , you need to bind keys to its special functions (or rebind existing keys that do something similar).
#+begin_src emacs-lisp
2022-04-28 15:56:57 +00:00
(use-package consult
2022-05-31 18:49:21 +00:00
:after general
2022-04-28 15:56:57 +00:00
;; Enable automatic preview at point in the *Completions* buffer. This is
;; relevant when you use the default completion UI.
:hook (completion-list-mode . consult-preview-at-point-mode)
:init
;; Use Consult to select xref locations with preview
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
2022-05-31 18:49:21 +00:00
(ha-leader
"RET" '("bookmark" . consult-bookmark)
"o i" '("imenu" . consult-imenu)
"x y" '("preview yank" . consult-yank-pop))
2022-05-16 20:34:05 +00:00
:bind ("s-v" . consult-yank-pop)
2022-04-28 15:56:57 +00:00
2022-05-11 17:52:12 +00:00
:general
2022-05-16 20:34:05 +00:00
(:states 'normal
2022-05-11 17:52:12 +00:00
"gp" 'consult-yank-pop
2022-05-31 18:49:21 +00:00
"gs" 'consult-line))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-28 15:56:57 +00:00
*** Consult for Projects
2022-06-18 00:25:47 +00:00
One of the reasons that Consult hasn’ t been too important to me, is that I often narrow my searching based on projectile. The [[https://gitlab.com/OlMon/consult-projectile ][consult-projectile ]] can help with this.
#+begin_src emacs-lisp
2022-04-28 15:56:57 +00:00
(use-package consult-projectile
:after consult general
2022-08-09 16:57:20 +00:00
:straight (:host gitlab :repo "OlMon/consult-projectile" :branch "master")
2022-04-28 15:56:57 +00:00
:config
(ha-leader
"p ." '("switch to..." . consult-projectile)
"b b" '("switch buffer" . consult-projectile-switch-to-buffer)
"p p" '("switch project" . consult-projectile-switch-project)
"p f" '("find file" . consult-projectile-find-file)
"p r" '("find recent file" . consult-projectile-recentf)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-28 15:56:57 +00:00
The advantage of [[help:persp-switch-to-buffer ][persp-switch-to-buffer ]] over =consult-projectile-switch-to-buffer= is that is shows non-file buffers.
2021-11-02 00:27:14 +00:00
*** Embark
2022-06-18 00:25:47 +00:00
The [[https://github.com/oantolin/embark/ ][embark ]] project offers /actions/ on /targets/ . I'm primarily thinking of acting on selected items in the minibuffer, but these commands act anywhere. I need an easy-to-use keybinding that doesn't conflict. Hey, that is what the Super key is for, right?
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package embark
:bind
2022-08-02 18:13:48 +00:00
(("s-;" . embark-act) ; Work in minibuffer and elsewhere
2021-11-02 00:27:14 +00:00
("s-/" . embark-dwim))
:init
;; Optionally replace the key help with a completing-read interface
2022-04-30 04:57:22 +00:00
(setq prefix-help-command #'embark-prefix-help-command)
:config
(ha-leader "h K" '("keybindings" . embark-bindings)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-04-30 04:59:47 +00:00
According to [[https://elpa.gnu.org/packages/embark-consult.html#orgc76b5de ][this essay ]], Embark cooperates well with the [[https://github.com/minad/marginalia ][Marginalia ]] and [[https://github.com/minad/consult ][Consult ]] packages. Neither of those packages is a dependency of Embark, but Embark supplies a hook for Consult where Consult previews can be done from Embark Collect buffers:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-30 04:59:47 +00:00
(use-package embark-consult
:after (embark consult)
:demand t ; only necessary if you have the hook below
;; if you want to have consult previews as you move around an
;; auto-updating embark collect buffer
:hook
(embark-collect-mode . consult-preview-at-point-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-30 04:59:47 +00:00
2022-05-02 23:29:26 +00:00
According to the [[https://elpa.gnu.org/packages/embark-consult.html ][Embark-Consult page ]]:
#+begin_quote
Users of the popular [[https://github.com/justbur/emacs-which-key ][which-key ]] package may prefer to use the =embark-which-key-indicator= from the [[https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt ][Embark wiki ]]. Just copy its definition from the wiki into your configuration and customize the =embark-indicators= user option to exclude the mixed and verbose indicators and to include =embark-which-key-indicator= .
#+end_quote
In other words, typing ~s-;~ to call Embark, specifies the options in a buffer, but the following code puts them in a smaller configuration directly above the selections.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-02 23:29:26 +00:00
(defun embark-which-key-indicator ()
"An embark indicator that displays keymaps using which-key.
The which-key help message will show the type and value of the
current target followed by an ellipsis if there are further
targets."
(lambda (&optional keymap targets prefix)
(if (null keymap)
(which-key--hide-popup-ignore-command)
(which-key--show-keymap
(if (eq (plist-get (car targets) :type) 'embark-become)
"Become"
(format "Act on %s '%s'%s"
(plist-get (car targets) :type)
(embark--truncate-target (plist-get (car targets) :target))
(if (cdr targets) "…" "")))
(if prefix
(pcase (lookup-key keymap prefix 'accept-default)
((and (pred keymapp) km) km)
(_ (key-binding prefix 'accept-default)))
keymap)
nil nil t (lambda (binding)
(not (string-suffix-p "-argument" (cdr binding))))))))
(setq embark-indicators
'(embark-which-key-indicator
embark-highlight-indicator
embark-isearch-highlight-indicator))
(defun embark-hide-which-key-indicator (fn &rest args)
"Hide the which-key indicator immediately when using the completing-read prompter."
(which-key--hide-popup-ignore-command)
(let ((embark-indicators
(remq #'embark-which-key-indicator embark-indicators)))
(apply fn args)))
(advice-add #'embark-completing-read-prompter
:around #'embark-hide-which-key-indicator)
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-14 16:23:41 +00:00
** Evil Extensions
*** Evil Exchange
I often use the Emacs commands, ~M-t~ and whatnot to exchange words and whatnot, but this requires a drop out of normal state mode. The [[https://github.com/Dewdrops/evil-exchange ][evil-exchange ]] project attempts to do something similar, but in a VI-way.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-16 20:34:05 +00:00
(use-package evil-exchange
;; While I normally just use `link-hint', the gx keybinding is used by evil-exchange:
:general (:states 'normal "gz" 'browse-url-at-point)
:config (evil-exchange-install))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-14 16:23:41 +00:00
Let’ s explain how this works as the documentation assumes some previous knowledge. If you had a sentence:
The ball was red and the boy was blue.
Move the point to the word, /red/ , and type ~g x i w~ (anywhere since we are using the inner text object). Next, jump to the word /blue/ , and type the sequence, ~g x i w~ again, and you have:
The ball was blue and the boy was red.
The idea is that you can exchange anything. The ~g x~ marks something (like what we would normally do in /visual mode/ ), and then by marking something else with a ~g x~ sequence, it swaps them.
*** Evil Commentary
The [[https://github.com/linktohack/evil-commentary ][evil-commentary ]] is a VI-like way of commenting text. Yeah, I typically type ~M-;~ to call Emacs’ originally functionality, but in this case, ~g c c~ comments out a line(s), and ~g c~ takes text objects and whatnot. For instance, ~g c $~ comments to the end of the line.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-14 16:23:41 +00:00
(use-package evil-commentary
:config (evil-commentary-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-14 16:23:41 +00:00
*** Evil Collection
Dropping into Emacs state is better than pure Evil state for applications, however, [[https://github.com/emacs-evil/evil-collection ][the evil-collection package ]] creates a hybrid between the two, that I like.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-14 16:23:41 +00:00
(use-package evil-collection
:after evil
:config
(evil-collection-init))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-14 16:23:41 +00:00
Do I want to specify the list of modes to change for =evil-collection-init= , e.g.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :tangle no :eval no
2022-05-14 16:23:41 +00:00
'(eww magit dired notmuch term wdired)
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-14 16:23:41 +00:00
*** Evil Owl
Not sure what is in a register? Have it show you when you hit ~”~ or ~@~ with [[https://github.com/mamapanda/evil-owl ][evil-owl ]]:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-08-25 17:42:01 +00:00
(use-package posframe)
2022-05-14 16:23:41 +00:00
(use-package evil-owl
2022-08-25 17:42:01 +00:00
:after posframe
2022-05-14 16:23:41 +00:00
:config
(setq evil-owl-display-method 'posframe
evil-owl-extra-posframe-args '(:width 50 :height 20 :background-color "#444")
evil-owl-max-string-length 50)
(evil-owl-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-14 16:23:41 +00:00
*** Evil Snipe
2022-07-07 20:44:41 +00:00
Doom introduced me to [[https://github.com/hlissner/evil-snipe ][evil-snipe ]], like =f= and =t= , but with two characters, and can, when configured, search more than the current line. My issue is that [[Evil Surround ]] uses the same keybindings. Since surround doesn’ t work in /normal/ and /visual/ states, we’ ll bind snipe only for those:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-10 01:25:32 +00:00
(use-package evil-snipe
:after evil
:init
(setq evil-snipe-scope 'visible)
2022-05-11 17:52:12 +00:00
:general
2022-07-07 20:44:41 +00:00
(:states '(normal visual)
2022-05-16 20:34:05 +00:00
"s" 'evil-snipe-s
"S" 'evil-snipe-S)
:config
2022-05-17 17:29:30 +00:00
(evil-snipe-mode +1))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
It highlights all potential matches, use ~;~ to skip to the next match, and ~,~ to jump back.
2022-05-14 16:23:41 +00:00
*** Evil Surround
2022-07-07 20:44:41 +00:00
I like both [[https://github.com/emacs-evil/evil-surround ][evil-surround ]] and Henrik's [[https://github.com/hlissner/evil-snipe ][evil-snipe ]], but they both start with ~s~ , and conflict, and getting them to work together means I have to remember when does ~s~ call sniper and when it calls surround. As an original Emacs person, I am not bound by that key history, but I do need them consistent, so I’ m choosing the ~s~ to be /surround/ .
2022-04-28 05:09:03 +00:00
2022-07-07 20:44:41 +00:00
#+begin_src emacs-lisp
2021-11-09 01:27:09 +00:00
(use-package evil-surround
:config
2022-07-07 20:44:41 +00:00
(defun evil-surround-elisp ()
(push '(?\` . ("`" . "'")) evil-surround-pairs-alist))
(defun evil-surround-org ()
(push '(?\" . ("“" . "”")) evil-surround-pairs-alist)
(push '(?\' . ("‘ " . "’ ")) evil-surround-pairs-alist)
(push '(?b . ("*" . "* ")) evil-surround-pairs-alist)
(push '(?* . ("*" . "* ")) evil-surround-pairs-alist)
(push '(?i . ("/" . "/ ")) evil-surround-pairs-alist)
(push '(?/ . ("/ " . "/")) evil-surround-pairs-alist)
(push '(?= . ("= " . "=")) evil-surround-pairs-alist)
(push '(?~ . ("~ " . "~")) evil-surround-pairs-alist))
(global-evil-surround-mode 1)
:hook
(org-mode . evil-surround-org)
(emacs-lisp-mode . evil-surround-elisp))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 01:27:09 +00:00
Notes:
2022-07-07 20:44:41 +00:00
- ~cs'"~ :: to convert surrounding single quote string to double quotes.
- ~ds"~ :: to delete the surrounding double quotes.
- ~yse"~ :: puts single quotes around the next word.
- ~ysiw'~ :: puts single quotes around the word, no matter the points position.
- ~yS$<p>~ :: surrouds the line with HTML =<p>= tag (with extra carriage returns).
- ~ysiw'~ :: puts single quotes around the word, no matter the points position.
2021-11-09 01:27:09 +00:00
- ~(~ :: puts spaces /inside/ the surrounding parens, but ~)~ doesn't. Same with ~[~ and ~]~ .
2022-05-16 20:34:05 +00:00
** Additional Global Packages
*** Visual Replace with Visual Regular Expressions
2022-06-18 00:25:47 +00:00
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
2022-05-16 20:34:05 +00:00
(use-package visual-regexp
:bind (("C-c r" . vr/replace)
("C-c q" . vr/query-replace))
:general (:states 'normal "gR" '("replace" . vr/replace)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-16 20:34:05 +00:00
*** Jump with Avy
2022-04-28 05:09:03 +00:00
While I grew up on =Control S= , I am liking the /mental model/ associated with the [[https://github.com/abo-abo/avy ][avy project ]] that allows a /jump/ among matches across all visible windows. I use the ~F18~ key on my keyboard that should be easy to use, but ~g o~ seems obvious.
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-28 05:09:03 +00:00
(use-package avy
:init
(setq avy-all-windows t
avy-single-candidate-jump t
avy-orders-alist
'((avy-goto-char . avy-order-closest)
(avy-goto-word-0 . avy-order-closest)))
2022-05-11 17:52:12 +00:00
2022-04-28 05:09:03 +00:00
:config (ha-leader "j" '("jump" . avy-goto-char-timer))
2022-05-11 17:52:12 +00:00
:general
2022-05-16 20:34:05 +00:00
(:states 'normal "go" 'avy-goto-char-timer)
2022-05-11 17:52:12 +00:00
:bind ("<f18 >" . avy-goto-char-timer))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
*Note:* The links should be shorter near the point as opposed to starting from the top of the window.
2022-05-16 20:34:05 +00:00
2022-06-18 00:25:47 +00:00
If you hit the following keys /before/ you select a target, you get a special action:
2022-05-16 20:34:05 +00:00
- ~n~ :: copies the matching target word
*** Link Hint, the Link Jumper
2022-06-18 00:25:47 +00:00
I originally appreciated [[https://github.com/abo-abo/ace-link ][ace-link ]] to work with hyperlinks on Org, EWW and Info pages, but the [[https://github.com/noctuid/link-hint.el ][link-hint ]] project works with more types of links:
#+begin_src emacs-lisp
2022-04-29 17:28:11 +00:00
(use-package link-hint
:bind
("s-o" . link-hint-open-link)
("C-c l o" . link-hint-open-link)
("C-c l c" . link-hint-copy-link)
:general
2022-05-16 20:34:05 +00:00
(:states 'normal
"gl" 'link-hint-open-link
"gL" 'link-hint-copy-link)
2022-04-29 17:28:11 +00:00
(:states 'normal :keymaps 'eww-mode-map
"o" 'link-hint-open-link)
(:states 'normal :keymaps 'Info-mode-map
2022-05-16 20:34:05 +00:00
"o" 'link-hint-open-link))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-29 17:28:11 +00:00
2022-05-16 20:34:05 +00:00
*** Expand Region
2022-06-18 00:25:47 +00:00
Magnar Sveen's [[https://github.com/magnars/expand-region.el ][expand-region ]] project allows me to hit ~v~ in =visual= mode, and have the selection grow by syntactical units.
#+begin_src emacs-lisp
2022-05-16 20:34:05 +00:00
(use-package expand-region
:bind ("C-=" . er/expand-region)
2021-11-02 00:27:14 +00:00
2022-05-16 20:34:05 +00:00
:general
;; Use escape to get out of visual mode, but hitting v again expands the selection.
(:states 'visual "v" 'er/expand-region))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* Working Layout
While editing any file on disk is easy enough, I like the mental context switch associated with a full-screen window frame showing all the buffers of a /project task/ (often a direct link to a repository project, but not always).
** Projects
2022-06-18 00:25:47 +00:00
While I don't /need/ all the features that [[https://github.com/bbatsov/projectile ][projectile ]] provides, it has all the features I do need, and is easy enough to install. I am referring to the fact that I /could/ use the built-in =project.el= system (see [[https://cestlaz.github.io/post/using-emacs-79-project/ ][this essay ]] for details on what I mean as an alternative).
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-13 21:47:01 +00:00
(use-package projectile
:custom
(projectile-sort-order 'recentf)
(projectile-project-root-functions '(projectile-root-bottom-up))
:config
(ha-leader
"p" '(:ignore t :which-key "projects")
"p W" '("initialize workspace" . ha-workspace-initialize)
"p n" '("new project space" . ha-project-persp)
"p !" '("run cmd in project root" . projectile-run-shell-command-in-root)
"p &" '("async cmd in project root" . projectile-run-async-shell-command-in-root)
"p a" '("add new project" . projectile-add-known-project)
"p b" '("switch to project buffer" . projectile-switch-to-buffer)
"p c" '("compile in project" . projectile-compile-project)
"p C" '("repeat last command" . projectile-repeat-last-command)
"p d" '("remove known project" . projectile-remove-known-project)
"p e" '("edit project .dir-locals" . projectile-edit-dir-locals)
"p f" '("find file in project" . projectile-find-file)
"p g" '("configure project" . projectile-configure-project)
"p i" '("invalidate project cache" . projectile-invalidate-cache)
"p k" '("kill project buffers" . projectile-kill-buffers)
"p o" '("find other file" . projectile-find-other-file)
"p p" '("switch project" . projectile-switch-project)
"p r" '("find recent project files" . projectile-recentf)
"p R" '("run project" . projectile-run-project)
"p S" '("save project files" . projectile-save-project-buffers)
"p T" '("test project" . projectile-test-project)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
** Workspaces
A /workspace/ (at least to me) requires a quick jump to a collection of buffer windows organized around a project or task. For this, I'm basing my work on the [[https://github.com/nex3/perspective-el ][perspective.el ]] project.
I build a Hydra to dynamically list the current projects as well as select the project.
To do this, we need a way to generate a string of the perspectives in alphabetical order:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(defun ha--persp-label (num names)
"Return string of numbered elements. NUM is the starting
number and NAMES is a list of strings."
(when names
(concat
2021-11-09 00:02:39 +00:00
(format " %d: %s%s" ; Shame that the following doesn't work:
2021-11-02 00:27:14 +00:00
num ; (propertize (number-to-string num) :foreground "#00a0")
2021-11-09 00:02:39 +00:00
(car names) ; Nor does surrounding the number with underbars.
(if (equal (car names) (projectile-project-name)) "*" ""))
2021-11-02 00:27:14 +00:00
(ha--persp-label (1+ num) (cdr names)))))
(defun ha-persp-labels ()
"Return a string of numbered elements from a list of names."
(ha--persp-label 1 (sort (hash-table-keys (perspectives-hash)) 's-less?)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
Build the hydra as well as configure the =perspective= project.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package perspective
:custom
(persp-modestring-short t)
(persp-show-modestring t)
:config
(persp-mode +1)
2022-03-03 23:07:00 +00:00
2021-11-02 00:27:14 +00:00
(defhydra hydra-workspace-leader (:color blue :hint nil) "
Workspaces- %s(ha-persp-labels)
_n_ : new project _r_ : rename _a_ : add buffer _l_ : load worksp
_]_ : next worksp _d_ : delete _b_ : goto buffer _s_ : save worksp
2021-11-09 00:02:39 +00:00
_[_ : previous _W_ : init all _k_ : remove buffer _`_ : to last worksp "
2021-11-18 17:46:10 +00:00
("TAB" persp-switch-quick)
("RET" persp-switch)
2021-11-09 00:02:39 +00:00
("`" persp-switch-last)
2021-11-02 00:27:14 +00:00
("1" (persp-switch-by-number 1))
("2" (persp-switch-by-number 2))
("3" (persp-switch-by-number 3))
("4" (persp-switch-by-number 4))
("5" (persp-switch-by-number 5))
("6" (persp-switch-by-number 6))
("7" (persp-switch-by-number 7))
("8" (persp-switch-by-number 8))
("9" (persp-switch-by-number 9))
("0" (persp-switch-by-number 0))
("n" ha-project-persp)
2021-12-01 19:04:02 +00:00
("N" ha-new-persp)
2021-11-02 00:27:14 +00:00
("]" persp-next :color pink)
("[" persp-prev :color pink)
("r" persp-rename)
("d" persp-kill)
("W" ha-workspace-initialize)
("a" persp-add-buffer)
("b" persp-switch-to-buffer)
("k" persp-remove-buffer)
("K" persp-kill-buffer)
("s" persp-state-save)
("l" persp-state-load)
2022-04-28 05:14:17 +00:00
("w" ha-switch-to-special) ; The most special perspective
2021-11-02 00:27:14 +00:00
("q" nil)
("C-g" nil))
2022-03-03 23:07:00 +00:00
:bind ("C-<tab >" . hydra-workspace-leader/body))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-03-09 04:43:25 +00:00
I have no idea why this binding doesn’ t work /within/ the =use-package= declaration, but oh well…
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-09 04:43:25 +00:00
(ha-leader "TAB" '("workspaces" . hydra-workspace-leader/body))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-28 05:14:17 +00:00
The /special/ perspective is a nice shortcut to the one I use the most:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-28 05:14:17 +00:00
(defun ha-switch-to-special ()
"Change to the projects perspective."
(interactive)
(persp-switch "projects"))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 04:43:25 +00:00
*** Predefined Workspaces
2021-11-02 00:27:14 +00:00
Let's describe a list of startup project workspaces. This way, I don't need the clutter of the recent state, but also get back to a state of mental normality.
Granted, this list is essentially a list of projects that I'm currently developing, so I expect this to change often.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-01-05 00:12:20 +00:00
(defvar ha-workspace-projects-personal nil "List of default projects with a name.")
(add-to-list 'ha-workspace-projects-personal
'("projects" "~/projects" ("breathe.org" "tasks.org")))
(add-to-list 'ha-workspace-projects-personal
'("personal" "~/personal" ("general.org")))
(add-to-list 'ha-workspace-projects-personal
'("technical" "~/technical" ("ansible.org")))
(add-to-list 'ha-workspace-projects-personal
'("hamacs" "~/other/hamacs" ("README.org" "ha-config.org")))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
Given a list of information about project-workspaces, can we create them all?
#+begin_src emacs-lisp
2021-11-06 00:07:33 +00:00
(defun ha-persp-exists? (name)
2022-06-18 00:25:47 +00:00
"Return non-nill if a perspective of NAME exists."
2022-03-09 04:43:25 +00:00
(when (fboundp 'perspectives-hash)
(seq-contains (hash-table-keys (perspectives-hash)) name)))
2021-11-06 00:07:33 +00:00
(defun ha-workspace-initialize (&optional projects)
"Precreate workspace projects from a PROJECTS list.
Each entry in the list is a list containing:
- name (as a string)
- project root directory
- a optional list of files to display"
(interactive)
(unless projects
(setq projects ha-workspace-projects-personal))
2021-11-02 00:27:14 +00:00
2021-11-06 00:07:33 +00:00
(dolist (project projects)
(-let (((name root files) project))
(unless (ha-persp-exists? name)
(message "Creating workspace: %s (from %s)" name root)
(ha-project-persp root name files)))))
2022-06-18 00:25:47 +00:00
#+end_src
Often, but not always, I want a perspective based on an actual Git repository, e.g. a project. Projectile keeps state of a "project" based on the current file loaded, so we /combine/ the two projects by first choosing from a list of /known projects/ and then creating a perspective based on the name. To pin the perspective to a project, we load a file from it, e.g. Like a README or something.
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-06 00:07:33 +00:00
(defun ha-project-persp (project &optional name files)
"Create a new perspective, and then switch to the PROJECT using projectile.
If NAME is not given, then figure it out based on the name of the
PROJECT. If FILES aren't specified, then see if there is a
2022-02-11 07:16:24 +00:00
README. Otherwise, pull up Dired."
2021-11-06 00:07:33 +00:00
(interactive (list (projectile-completing-read "Project: " projectile-known-projects)))
(when (f-directory-p project)
(unless name
(setq name (f-filename project)))
(persp-switch name)
;; Unclear if the following is actually necessary.
(ignore-errors
(projectile-add-known-project root)
(let ((projectile-switch-project-action nil))
(projectile-switch-project-by-name root)))
;; To pin a project in projectile to the perspective, we need to load a file
;; from that project. The README will do, or at least, the dired of it.
2022-02-04 22:38:56 +00:00
(let ((readme-org (f-join project "README.org"))
(readme-md (f-join project "README.md")))
(cond
(files (ha--project-show-files project files))
((f-exists? readme-org) (find-file readme-org))
((f-exists? readme-md) (find-file readme-md))
(t (dired project))))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
When starting a new perspective, and I specify more than one file, this function splits the window horizontally for each file.
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(defun ha--project-show-files (root files)
"Display a list of FILES in a project ROOT directory.
Each file gets its own window (so don't make the list of files
long)."
(message "Loading files from %s ... %s" root files)
(let* ((file (car files))
(more (cdr files))
(filename (format "%s/%s" root file)))
(find-file filename)
(when more
(split-window-horizontally)
(ha--project-show-files root more))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-01 19:04:02 +00:00
The =persp-switch= allows me to select or create a new project, but what if we insisted on a new workspace?
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-01 19:04:02 +00:00
(defun ha-new-persp (name)
(interactive "sNew Workspace: ")
(persp-switch name)
(cond
((s-ends-with? "mail" name) (notmuch))
((s-starts-with? "twit" name) (twit))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-01 19:04:02 +00:00
Once we create the new perspective workspace, if it matches a particular name, I pretty much know what function I would like to call.
2021-11-02 00:27:14 +00:00
* Applications
2022-06-18 00:25:47 +00:00
Can we call these /applications/ ?
2021-11-02 00:27:14 +00:00
** Magit
Can not live without [[https://magit.vc/ ][Magit ]], a Git porcelain for Emacs. I stole the bulk of this work from Doom Emacs.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package magit
:config
2021-11-08 20:06:04 +00:00
;; The following code re-instates my General Leader key in Magit.
(general-unbind magit-mode-map "SPC")
2021-11-02 00:27:14 +00:00
(ha-leader
"g" '(:ignore t :which-key "git")
"g /" '("Magit dispatch" . magit-dispatch)
"g ." '("Magit file dispatch" . magit-file-dispatch)
"g b" '("Magit switch branch" . magit-branch-checkout)
"g g" '("Magit status" . magit-status)
"g s" '("Magit status here" . magit-status-here)
"g D" '("Magit file delete" . magit-file-delete)
"g B" '("Magit blame" . magit-blame-addition)
"g C" '("Magit clone" . magit-clone)
"g F" '("Magit fetch" . magit-fetch)
"g L" '("Magit buffer log" . magit-log-buffer-file)
"g R" '("Revert file" . vc-revert)
"g S" '("Git stage file" . magit-stage-file)
"g U" '("Git unstage file" . magit-unstage-file)
"g f" '(:ignore t :which-key "find")
"g f f" '("Find file" . magit-find-file)
"g f g" '("Find gitconfig file" . magit-find-git-config-file)
"g f c" '("Find commit" . magit-show-commit)
"g l" '(:ignore t :which-key "list")
"g l r" '("List repositories" . magit-list-repositories)
"g l s" '("List submodules" . magit-list-submodules)
"g o" '(:ignore t :which-key "open")
"g c" '(:ignore t :which-key "create")
2021-11-09 00:02:39 +00:00
"g c R" '("Initialize repo" . magit-init)
"g c C" '("Clone repo" . magit-clone)
2021-11-02 00:27:14 +00:00
"g c c" '("Commit" . magit-commit-create)
"g c f" '("Fixup" . magit-commit-fixup)
"g c b" '("Branch" . magit-branch-and-checkout)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-07-07 16:25:53 +00:00
*** Git Delta
The [[https://scripter.co/using-git-delta-with-magit ][magit-delta ]] project uses [[https://github.com/dandavison/delta ][git-delta ]] for colorized diffs.
#+begin_src emacs-lisp
(use-package magit-delta
:ensure t
:hook (magit-mode . magit-delta-mode))
#+end_src
I also need to append the following to my [[file:~/.gitconfig ][~/.gitconfig ]] file:
#+begin_src conf
[delta]
2022-08-02 18:13:48 +00:00
minus-style = normal "#8f0001"
minus-non-emph-style = normal "#8f0001"
minus-emph-style = normal bold "#d01011"
minus-empty-line-marker-style = normal "#8f0001"
zero-style = syntax
plus-style = syntax "#006800"
plus-non-emph-style = syntax "#006800"
plus-emph-style = syntax "#009000"
plus-empty-line-marker-style = normal "#006800"
#+end_src
2022-08-10 04:27:29 +00:00
*** Git with Difftastic
I’ m stealing the code for this section from [[https://tsdh.org/posts/2022-08-01-difftastic-diffing-with-magit.html ][this essay ]] by Tassilo Horn, and in fact, I’ m going to lift a lot of his explanation too, as I may need to remind myself how this works. The idea is based on using Wilfred’ s excellent [[https://github.com/Wilfred/difftastic ][difftastic ]] tool to do a structural/syntax comparison of code changes in git. To begin, install the binary:
#+begin_src sh
brew install difftastic # and the equivalent on Linux
#+end_src
Next, we can do this, to use this as a diff tool for everything.
#+begin_src emacs-lisp
(setenv "GIT_EXTERNAL_DIFF" "difft")
#+end_src
But perhaps integrating it into Magit and selectively calling it (as it is slow). Tassilo suggests making the call to =difft= optional by first creating a helper function to set the =GIT_EXTERNAL_DIFF= to =difft= :
#+begin_src emacs-lisp :tangle no
(defun th/magit--with-difftastic (buffer command)
"Run COMMAND with GIT_EXTERNAL_DIFF=difft then show result in BUFFER."
(let ((process-environment
(cons (concat "GIT_EXTERNAL_DIFF=difft --width= "
(number-to-string (frame-width)))
process-environment)))
;; Clear the result buffer (we might regenerate a diff, e.g., for
;; the current changes in our working directory).
(with-current-buffer buffer
(setq buffer-read-only nil)
(erase-buffer))
;; Now spawn a process calling the git COMMAND.
(make-process
:name (buffer-name buffer)
:buffer buffer
:command command
;; Don't query for running processes when emacs is quit.
:noquery t
;; Show the result buffer once the process has finished.
:sentinel (lambda (proc event)
(when (eq (process-status proc) 'exit)
(with-current-buffer (process-buffer proc)
(goto-char (point-min))
(ansi-color-apply-on-region (point-min) (point-max))
(setq buffer-read-only t)
(view-mode)
(end-of-line)
;; difftastic diffs are usually 2-column side-by-side,
;; so ensure our window is wide enough.
(let ((width (current-column)))
(while (zerop (forward-line 1))
(end-of-line)
(setq width (max (current-column) width)))
;; Add column size of fringes
(setq width (+ width
(fringe-columns 'left)
(fringe-columns 'right)))
(goto-char (point-min))
(pop-to-buffer
(current-buffer)
`(;; If the buffer is that wide that splitting the frame in
;; two side-by-side windows would result in less than
;; 80 columns left, ensure it's shown at the bottom.
,(when (> 80 (- (frame-width) width))
#'display-buffer-at-bottom)
(window-width . ,(min width (frame-width))))))))))))
#+end_src
The crucial parts of this helper function are that we "wash" the result using =ansi-color-apply-on-region= so that the function can transform the difftastic highlighting using shell escape codes to Emacs faces. Also, note the need to possibly change the width, as difftastic makes a side-by-side comparison.
The functions below depend on [[help:magit-thing-at-point ][magit-thing-at-point ]], and this depends on the [[https://sr.ht/~pkal/compat/ ][compat ]] library, so let’ s grab that stuff:
#+begin_src emacs-lisp :tangle no
(use-package compat
:straight (:host github :repo "emacs-straight/compat"))
(use-package magit-section
:commands magit-thing-at-point)
#+end_src
Next, let's define our first command basically doing a =git show= for some revision which defaults to the commit or branch at point or queries the user if there's none.
#+begin_src emacs-lisp :tangle no
(defun th/magit-show-with-difftastic (rev)
"Show the result of \"git show REV\" with GIT_EXTERNAL_DIFF=difft."
(interactive
(list (or
;; Use if given the REV variable:
(when (boundp 'rev) rev)
;; If not invoked with prefix arg, try to guess the REV from
;; point's position.
(and (not current-prefix-arg)
(or (magit-thing-at-point 'git-revision t)
(magit-branch-or-commit-at-point)))
;; Otherwise, query the user.
(magit-read-branch-or-commit "Revision"))))
(if (not rev)
(error "No revision specified")
(th/magit--with-difftastic
(get-buffer-create (concat "*git show difftastic " rev "* "))
(list "git" "--no-pager" "show" "--ext-diff" rev))))
#+end_src
And here the second command which basically does a =git diff= . It tries to guess what one wants to diff, e.g., when point is on the Staged changes section in a magit buffer, it will run =git diff --cached= to show a diff of all staged changes. If it can not guess the context, it'll query the user for a range or commit for diffing.
#+begin_src emacs-lisp :tangle no
(defun th/magit-diff-with-difftastic (arg)
"Show the result of \"git diff ARG\" with GIT_EXTERNAL_DIFF=difft."
(interactive
(list (or
;; Use If RANGE is given, just use it.
(when (boundp 'range) range)
;; If prefix arg is given, query the user.
(and current-prefix-arg
(magit-diff-read-range-or-commit "Range"))
;; Otherwise, auto-guess based on position of point, e.g., based on
;; if we are in the Staged or Unstaged section.
(pcase (magit-diff--dwim)
('unmerged (error "unmerged is not yet implemented"))
('unstaged nil)
('staged "--cached")
(`(stash . ,value) (error "stash is not yet implemented"))
(`(commit . ,value) (format "%s^..%s" value value))
((and range (pred stringp)) range)
(_ (magit-diff-read-range-or-commit "Range/Commit"))))))
(let ((name (concat "*git diff difftastic"
(if arg (concat " " arg) "")
"*")))
(th/magit--with-difftastic
(get-buffer-create name)
`("git" "--no-pager" "diff" "--ext-diff" ,@(when arg (list arg))))))
#+end_src
What's left is integrating the new show and diff commands in Magit. For that purpose, Tasillo created a new transient prefix for all personal commands. Intriguing, but I have a hack that I can use on a leader:
#+begin_src emacs-lisp :tangle no
(defun ha-difftastic-here ()
(interactive)
(call-interactively
(if (eq major-mode 'magit-log-mode)
'th/magit-show-with-difftastic
'th/magit-diff-with-difftastic)))
(ha-leader "g d" '("difftastic" . ha-difftastic-here))
2022-07-07 16:25:53 +00:00
#+end_src
*** Time Machine
The [[https://github.com/emacsmirror/git-timemachine ][git-timemachine ]] project visually shows how a code file changes with each iteration:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package git-timemachine
:config
(ha-leader "g t" '("git timemachine" . git-timemachine)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-27 18:15:07 +00:00
*** Gist
2021-11-06 00:07:33 +00:00
Using the [[https://github.com/emacsmirror/gist ][gist package ]] to write code snippets on [[https://gist.github.com/ ][Github ]] seems like it can be useful, but I'm not sure how often.
2022-07-07 16:25:53 +00:00
#+begin_src emacs-lisp :tangle no
2021-11-02 00:27:14 +00:00
(use-package gist
:config
(ha-leader
"g G" '(:ignore t :which-key "gists")
"g l g" '("gists" . gist-list)
"g G l" '("list" . gist-list) ; Lists your gists in a new buffer.
"g G r" '("region" . gist-region) ; Copies Gist URL into the kill ring.
"g G R" '("private region" . gist-region-private) ; Explicitly create a private gist.
"g G b" '("buffer" . gist-buffer) ; Copies Gist URL into the kill ring.
"g G B" '("private buffer" . gist-buffer-private) ; Explicitly create a private gist.
"g c g" '("gist" . gist-region-or-buffer) ; Post either the current region, or buffer
"g c G" '("private gist" . gist-region-or-buffer-private))) ; create private gist from region or buffer
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-02 16:48:16 +00:00
The gist project depends on the [[https://github.com/sigma/gh.el ][gh library ]]. There seems to be a problem with it.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :tangle no
2022-05-02 16:48:16 +00:00
(use-package gh
2022-08-09 16:57:20 +00:00
:straight (:host github :repo "sigma/gh.el"))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-02 16:48:16 +00:00
2021-12-27 18:15:07 +00:00
*** Forge
Let's extend Magit with [[https://github.com/magit/forge ][Magit Forge ]] for working with Github and Gitlab:
2022-07-27 04:10:25 +00:00
#+begin_src emacs-lisp :tangle no
2021-12-27 18:15:07 +00:00
(use-package forge
:after magit
:config
(ha-leader
"g '" '("Forge dispatch" . forge-dispatch)
"g f i" '("Find issue" . forge-visit-issue)
"g f p" '("Find pull request" . forge-visit-pullreq)
"g l i" '("List issues" . forge-list-issues)
"g l p" '("List pull requests" . forge-list-pullreqs)
"g l n" '("List notifications" . forge-list-notifications)
"g o r" '("Browse remote" . forge-browse-remote)
"g o c" '("Browse commit" . forge-browse-commit)
"g o i" '("Browse an issue" . forge-browse-issue)
"g o p" '("Browse a pull request" . forge-browse-pullreq)
"g o i" '("Browse issues" . forge-browse-issues)
"g o P" '("Browse pull requests" . forge-browse-pullreqs)
"g c i" '("Issue" . forge-create-issue)
"g c p" '("Pull request" . forge-create-pullreq)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-27 18:15:07 +00:00
2022-03-18 20:50:40 +00:00
Every /so often/ , pop over to the following URLs and generate a new token where the *Note* is =forge= , and then copy that into the [[file:~/.authinfo.gpg ][~/.authinfo.gpg ]]:
2021-12-27 18:15:07 +00:00
- [[https://gitlab.com/-/profile/personal_access_tokens ][Gitlab ]]
- [[https://github.com/settings/tokens ][Github ]]
and make sure this works:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :tangle no :results replace
2021-12-27 18:15:07 +00:00
(ghub-request "GET" "/user" nil
:forge 'github
:host "api.github.com"
:username "howardabrams"
:auth 'forge)
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-27 18:15:07 +00:00
*** Pushing is Bad
Pushing directly to the upstream branch is /bad form/ , as one should create a pull request, etc. To prevent an accidental push, we /double-check/ first:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-27 18:15:07 +00:00
(define-advice magit-push-current-to-upstream (:before (args) query-yes-or-no)
"Prompt for confirmation before permitting a push to upstream."
(when-let ((branch (magit-get-current-branch)))
(unless (yes-or-no-p (format "Push %s branch upstream to %s? "
branch
(or (magit-get-upstream-branch branch)
(magit-get "branch" branch "remote"))))
(user-error "Push to upstream aborted by user"))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
** Web Browsing
*** EWW
Web pages look pretty good with EWW, but I'm having difficulty getting it to render a web search from DuckDuck.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-11 07:16:24 +00:00
(use-package eww
2021-11-02 00:27:14 +00:00
:init
(setq browse-url-browser-function 'eww-browse-url
2022-03-24 17:43:08 +00:00
browse-url-secondary-browser-function 'browse-url-default-browser
eww-browse-url-new-window-is-tab nil
shr-use-colors nil
shr-use-fonts t ; I go back and forth on this one
;; shr-discard-aria-hidden t
shr-bullet "• "
shr-inhibit-images nil ; Gotta see the images?
;; shr-blocked-images '(svg)
;; shr-folding-mode nil
url-privacy-level '(email))
2021-11-02 00:27:14 +00:00
:config
2022-02-11 07:16:24 +00:00
(ha-leader "a b" '("eww browser" . eww))
2022-03-24 17:43:08 +00:00
:general
(:states 'normal :keymaps 'eww-mode-map
"B" 'eww-list-bookmarks
"Y" 'eww-copy-page-url
"H" 'eww-back-url
"L" 'eww-forward-url
"u" 'eww-top-url
"p" 'eww-previous-url
"n" 'eww-next-url
2022-05-16 20:34:05 +00:00
"q" 'bury-buffer)
2022-03-24 17:43:08 +00:00
(:states 'normal :keymaps 'eww-buffers-mode-map
2022-05-16 20:34:05 +00:00
"q" 'bury-buffer))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-30 02:51:25 +00:00
2022-06-18 00:25:47 +00:00
This function allows Imenu to offer HTML headings in EWW buffers, helpful for navigating long, technical documents.
#+begin_src emacs-lisp
2022-04-29 17:28:11 +00:00
(use-package eww
2021-11-02 00:27:14 +00:00
:config
2022-04-29 17:28:11 +00:00
(defun unpackaged/imenu-eww-headings ()
"Return alist of HTML headings in current EWW buffer for Imenu.
Suitable for `imenu-create-index-function'."
(let ((faces '(shr-h1 shr-h2 shr-h3 shr-h4 shr-h5 shr-h6 shr-heading)))
(save-excursion
(save-restriction
(widen)
(goto-char (point-min))
(cl-loop for next-pos = (next-single-property-change (point) 'face)
while next-pos
do (goto-char next-pos)
for face = (get-text-property (point) 'face)
when (cl-typecase face
(list (cl-intersection face faces))
(symbol (member face faces)))
collect (cons (buffer-substring (point-at-bol) (point-at-eol)) (point))
and do (forward-line 1))))))
:hook (eww-mode .
(lambda ()
(setq-local imenu-create-index-function #'unpackaged/imenu-eww-headings))))
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-11 07:16:50 +00:00
*** Get Pocket
The [[https://github.com/alphapapa/pocket-reader.el ][pocket-reader ]] project connects to the [[https://getpocket.com/en/ ][Get Pocket ]] service.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-11 07:16:50 +00:00
(use-package pocket-reader
:init
(setq org-web-tools-pandoc-sleep-time 1)
:config
(ha-leader "o p" '("get pocket" . pocket-reader))
;; Instead of jumping into Emacs mode to get the `pocket-mode-map',
2022-06-18 00:25:47 +00:00
;; we add the keybindings to the normal mode that makes sense.
2022-05-16 20:34:05 +00:00
:general
(:states 'normal :keymaps 'pocket-reader-mode-map
"RET" 'pocket-reader-open-url
"TAB" 'pocket-reader-pop-to-url
"*" 'pocket-reader-toggle-favorite
"B" 'pocket-reader-open-in-external-browser
"D" 'pocket-reader-delete
"E" 'pocket-reader-excerpt-all
"F" 'pocket-reader-show-unread-favorites
"M" 'pocket-reader-mark-all
"R" 'pocket-reader-random-item
"S" 'tabulated-list-sort
"a" 'pocket-reader-toggle-archived
"c" 'pocket-reader-copy-url
"d" 'pocket-reader
"e" 'pocket-reader-excerpt
"f" 'pocket-reader-toggle-favorite
"l" 'pocket-reader-limit
"m" 'pocket-reader-toggle-mark
"o" 'pocket-reader-more
"q" 'quit-window
"s" 'pocket-reader-search
"u" 'pocket-reader-unmark-all
"t a" 'pocket-reader-add-tags
"t r" 'pocket-reader-remove-tags
"t s" 'pocket-reader-tag-search
"t t" 'pocket-reader-set-tags
"g s" 'pocket-reader-resort
"g r" 'pocket-reader-refresh))
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-11 07:16:50 +00:00
Use these special keywords when searching:
2022-06-18 00:25:47 +00:00
- =:*= , =:favorite= Return favorited items.
- =:archive= Return archived items.
- =:unread= Return unread items (default).
2022-02-11 07:16:50 +00:00
- =:all= Return all items.
2022-06-18 00:25:47 +00:00
- =:COUNT= Return at most /COUNT/ (a number) items. This limit persists until you start a new search.
- =:t:TAG= , =t:TAG= Return items with /TAG/ (you can search for one tag at a time, a limitation of the Pocket API).
2021-11-14 06:14:55 +00:00
** Neotree
I primarily use [[https://github.com/jaypei/emacs-neotree ][Neotree ]] when I am screen-sharing my Emacs session with collegues as it shows a /project/ like an IDE.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-26 01:14:50 +00:00
(use-package neotree
2022-05-16 20:34:05 +00:00
:general ; evil-collection forgot a couple:
(:states 'normal :keymaps 'neotree-mode-map
"TAB" 'neotree-enter
"SPC" 'neotree-quick-look
"RET" 'neotree-enter
"H" 'neotree-hidden-file-toggle))
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-11 07:17:46 +00:00
** Annotations
Let's try [[https://github.com/bastibe/annotate.el ][annotate-mode ]], which allows you to drop "notes" and then move to them (yes, serious overlap with bookmarks, which we will return to).
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-11 07:17:46 +00:00
(use-package annotate
:config
(ha-leader
"t A" '("annotations" . annotate-mode)
"n" '(:ignore t :which-key "notes")
"n a" '("toggle mode" . annotate-mode)
"n n" '("annotate" . annotate-annotate)
"n d" '("delete" . annotate-delete)
"n s" '("summary" . annotate-show-annotation-summary)
"n j" '("next" . annotate-goto-next-annotation)
2022-06-18 00:25:47 +00:00
"n k" '("prev" . annotate-goto-previous-annotation)
;; If a shift binding isn't set, it defaults to non-shift version
;; Use SPC N N to jump to the next error:
"n N" '("next error" . flycheck-next-error)))
#+end_src
2022-02-11 07:17:46 +00:00
Keep the annotations simple, almost /tag-like/ , and then the summary allows you to display them.
2022-06-15 23:13:40 +00:00
** Keepass
Use the [[https://github.com/ifosch/keepass-mode ][keepass-mode ]] to view a /read-only/ version of my Keepass file in Emacs:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-06-15 23:13:40 +00:00
(use-package keepass-mode)
2022-06-18 00:25:47 +00:00
#+end_src
2022-06-15 23:13:40 +00:00
When having your point on a key entry, you can copy fields to kill-ring using:
- ~u~ :: URL
- ~b~ :: user name
- ~c~ :: password
2022-01-06 23:36:39 +00:00
** Demo It
2022-06-18 00:25:47 +00:00
Making demonstrations /within/ Emacs with my [[https://github.com/howardabrams/demo-it ][demo-it ]] project. While on MELPA, I want to use my own cloned version to make sure I can keep debugging it.
#+begin_src emacs-lisp
2022-01-06 23:36:39 +00:00
(use-package demo-it
2022-08-09 16:57:20 +00:00
:straight (:host github :repo "howardabrams/demo-it")
2022-01-06 23:36:39 +00:00
:commands (demo-it-create demo-it-start))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-10 19:03:40 +00:00
** PDF Viewing
Why not [[https://github.com/politza/pdf-tools ][view PDF files ]] better? To do this, first install the following on a Mac:
2022-06-18 00:25:47 +00:00
#+begin_src sh
2022-05-10 19:03:40 +00:00
brew install poppler automake
2022-06-18 00:25:47 +00:00
#+end_src
Instead run [[help:pdf-tools-install ][pdf-tools-install ]], as this command will do the above for the system.
2022-05-10 19:03:40 +00:00
2022-06-18 00:25:47 +00:00
Let’ s install the Emacs connection to the =pdfinfo= program:
#+begin_src emacs-lisp
2022-05-10 19:03:40 +00:00
(use-package pdf-tools
:mode ("\\.pdf\\'" . pdf-view-mode)
:init
(setq pdf-info-epdfinfo-program "/usr/local/bin/epdfinfo")
:general
(:states 'normal :keymaps 'pdf-view-mode-map
"gp" 'pdf-view-goto-page
">" 'doc-view-fit-window-to-page))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-18 20:50:40 +00:00
2022-05-10 19:03:40 +00:00
Make sure the [[help:pdf-info-check-epdfinfo ][pdf-info-check-epdfinfo ]] function works.
2021-11-02 00:27:14 +00:00
* Technical Artifacts :noexport:
2022-06-18 00:25:47 +00:00
Let's provide a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
2021-11-02 00:27:14 +00:00
(provide 'ha-config)
;;; ha-config.el ends here
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
#+DESCRIPTION : A literate programming file for configuring Emacs.
#+PROPERTY : header-args:sh :tangle no
#+PROPERTY : header-args:emacs-lisp :tangle yes
#+PROPERTY : header-args :results none :eval no-export :comments no
#+OPTIONS : num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS : skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT : view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js