From 778bfd468533f8c78d6a039f27c1efb434a9c5d2 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Fri, 9 Feb 2024 12:05:13 -0800 Subject: [PATCH] Made Evil optional Probably one doesn't need to hedge any bets, but I want to be able to eventually swap out Evil for Meow (or something of my own crafting), so wrapping evil-specific calls with a condition doesn't sound like a bad idea. --- bootstrap.org | 5 +- ha-aux-apps.org | 3 +- ha-config.org | 9 +- ha-display.org | 56 ++- ha-eshell.org | 3 +- ha-evil.org | 957 +----------------------------------- ha-feed-reader.org | 2 +- ha-general.org | 993 ++++++++++++++++++++++++++++++++++++++ ha-org.org | 29 +- ha-programming-elisp.org | 87 ++-- ha-programming-python.org | 7 +- ha-programming.org | 26 +- ha-remoting.org | 12 +- 13 files changed, 1143 insertions(+), 1046 deletions(-) create mode 100644 ha-general.org diff --git a/bootstrap.org b/bootstrap.org index c677239..832100c 100644 --- a/bootstrap.org +++ b/bootstrap.org @@ -191,11 +191,14 @@ And now start the server with an appropriate tag name: (server-start) #+end_src * Load the Rest -The following loads the rest of my org-mode literate files. I add new filesas they are /ready/: +The following /defines/ the rest of my org-mode literate files, that I load later with the =ha-hamacs-load= function: #+begin_src emacs-lisp (defvar ha-hamacs-files (flatten-list `("ha-private.org" "ha-config.org" + ;; "ha-leader.org" + "ha-evil.org" + ;; "ha-meow.org" "ha-applications.org" ,(when (display-graphic-p) "ha-display.org") diff --git a/ha-aux-apps.org b/ha-aux-apps.org index a79e14c..03db5fc 100644 --- a/ha-aux-apps.org +++ b/ha-aux-apps.org @@ -137,7 +137,8 @@ I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be ; telega-completing-read-function #'ivy-completing-read telega-msg-rainbow-title nil) - (add-hook 'telega-chat-mode-hook 'evil-insert-state) + (when (fboundp 'evil-insert-state) + (add-hook 'telega-chat-mode-hook 'evil-insert-state)) (ha-leader "a t" 'telega)) #+end_src diff --git a/ha-config.org b/ha-config.org index ed8cba3..9db894d 100644 --- a/ha-config.org +++ b/ha-config.org @@ -301,11 +301,12 @@ Why use [[https://gitlab.com/ideasman42/emacs-undo-fu][undo-fu]] instead of the (global-set-key (kbd "s-z") 'undo-fu-only-undo) (global-set-key (kbd "s-S-z") 'undo-fu-only-redo)) #+end_src -** On the Subject of Being Evil -I’m currently using the [[https://github.com/emacs-evil/evil][Extensible VI Layer]] for Emacs, aka /evil/. This configuration happens in [[file:ha-eviljjjj.org][ha-evil.org]], but because much of my configuration requires access to =ha-leader=, =general=, and other features, we need to include it here: +** Leader Sequences +Pressing the ~SPACE~ can activate a /leader key sequence/ that I define with [[file:ha-leader.org][general]] package. #+begin_src emacs-lisp - (ha-hamacs-load "ha-evil.org") + (ha-hamacs-load "ha-general.org") #+end_src +This extends the =use-package= to include a =:general= keybinding section. ** Additional Global Packages The following defines my use of the Emacs completion system. I’ve decided my /rules/ will be: - Nothing should automatically appear; that is annoying and distracting. @@ -581,7 +582,7 @@ While I don't /need/ all the features that [[https://github.com/bbatsov/projecti "p T" '("test project" . projectile-test-project))) #+end_src ** 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/said that I shouldsaid that I shouldsaid that I should perspective-el][perspective.el]] project. +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: diff --git a/ha-display.org b/ha-display.org index b0ee69c..a564ad2 100644 --- a/ha-display.org +++ b/ha-display.org @@ -61,33 +61,11 @@ Large screen, lots of windows, so where is the cursor? While I used to use =hl-l other-window delete-window delete-other-windows - aw-delete-window forward-page backward-page scroll-up-command scroll-down-command - evil-window-right - evil-window-left - evil-window-up - evil-window-down - aw-move-window - aw-swap-window - aw-copy-window - aw-split-window-vert - aw-split-window-horz - aw-split-window-fair ha-new-window - winum-select-window-1 - winum-select-window-2 - winum-select-window-3 - winum-select-window-4 - winum-select-window-5 - winum-select-window-6 - winum-select-window-7 - winum-select-window-8 - winum-select-window-9 - winner-undo - winner-redo tab-new tab-close tab-next @@ -103,7 +81,39 @@ Large screen, lots of windows, so where is the cursor? While I used to use =hl-l (pulsar-face 'pulsar-generic) (pulsar-delay 0.05) :bind ("" . pulsar-pulse-line) - :config (pulsar-global-mode 1)) + + :config + (when (fboundp 'winner-undo) + (add-to-list 'pulsar-pulse-functions 'winner-undo) + (add-to-list 'pulsar-pulse-functions 'winner-redo)) + + (when (fboundp 'winum-select-window-1) + (add-to-list 'pulsar-pulse-functions 'winum-select-window-1) + (add-to-list 'pulsar-pulse-functions 'winum-select-window-2) + (add-to-list 'pulsar-pulse-functions 'winum-select-window-3) + (add-to-list 'pulsar-pulse-functions 'winum-select-window-4) + (add-to-list 'pulsar-pulse-functions 'winum-select-window-5) + (add-to-list 'pulsar-pulse-functions 'winum-select-window-6) + (add-to-list 'pulsar-pulse-functions 'winum-select-window-7) + (add-to-list 'pulsar-pulse-functions 'winum-select-window-8) + (add-to-list 'pulsar-pulse-functions 'winum-select-window-9)) + + (when (fboundp 'aw-delete-window) + (add-to-list 'pulsar-pulse-functions aw-move-window) + (add-to-list 'pulsar-pulse-functions aw-swap-window) + (add-to-list 'pulsar-pulse-functions aw-copy-window) + (add-to-list 'pulsar-pulse-functions aw-split-window-vert) + (add-to-list 'pulsar-pulse-functions aw-split-window-horz) + (add-to-list 'pulsar-pulse-functions aw-split-window-fair) + (add-to-list 'pulsar-pulse-functions aw-delete-window)) + + (when (fboundp 'evil-window-right) + (add-to-list 'pulsar-pulse-functions evil-window-right) + (add-to-list 'pulsar-pulse-functions evil-window-left) + (add-to-list 'pulsar-pulse-functions evil-window-up) + (add-to-list 'pulsar-pulse-functions evil-window-down)) + + (pulsar-global-mode 1)) #+end_src * Themes One does get used to a particular collection of colors. Mine is Tomorrow: diff --git a/ha-eshell.org b/ha-eshell.org index e4a8b1b..76295cf 100644 --- a/ha-eshell.org +++ b/ha-eshell.org @@ -465,7 +465,8 @@ This buffer has a minor-mode that binds ~C-c C-q~ to close the window and return #+end_src Since I use Evil, I also add ~Q~ to call this function: #+begin_src emacs-lisp - (evil-define-key 'normal ebbflow-mode-map "Q" 'ha-eshell-ebbflow-return) + (when (fboundp 'evil-define-key) + (evil-define-key 'normal ebbflow-mode-map "Q" 'ha-eshell-ebbflow-return)) #+end_src *** flow (or Buffer Cat) Eshell can send the output of a command sequence to a buffer: diff --git a/ha-evil.org b/ha-evil.org index 033e42b..dfa16ec 100644 --- a/ha-evil.org +++ b/ha-evil.org @@ -38,6 +38,17 @@ Some advice that I followed: - [[https://nathantypanski.com/blog/2014-08-03-a-vim-like-emacs-config.html][A Vim-like Emacs Configuration from Nathan Typanski]] - [[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]] +My Evil configuration should be /optionally/ included, so I define this variable: +#+begin_src emacs-lisp + (defvar ha-evil-on t + "If non-nil, it means that Evil configuration has been turned on.") +#+end_src +Then code through over files (but not this one), would have something like: +#+begin_src emacs-lisp :tangle no + (when (and (boundp 'ha-evil-on) ha-evil-one) + ;; ...access evil... + ) +#+end_src * Evil-Specific Keybindings I split the configuration of Evil mode into sections. First, global settings: #+begin_src emacs-lisp @@ -299,952 +310,6 @@ Using the key-chord project allows me to make Escape be on two key combo presses (key-chord-define-global "JK" 'evil-normal-state)) #+end_src This has been a frustrating feature that doesn’t always work, and usually just when I get really used to it. -* 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. - -#+begin_src emacs-lisp - (use-package general - :config - (setq general-use-package-emit-autoloads t) - - (general-evil-setup t) - - (general-create-definer ha-leader - :states '(normal visual motion) - :keymaps 'override - :prefix "SPC" - :non-normal-prefix "M-SPC" - :global-prefix "") - - (general-create-definer ha-local-leader - :states '(normal visual motion) - :prefix "," - :global-prefix "" - :non-normal-prefix "S-SPC") - - (general-nmap "SPC m" (general-simulate-key "," :which-key "major mode"))) -#+end_src -** Relabel the G Keys -Can’t remember all the shortcuts on the ~g~ key, and =which-key= displays the entire function, so let’s /re-add/ those keybindings, but with labels. The ~g~ is extemely convenient, yet I realize that I will never use some of the default keybindings (like ~g m~ to go to the middle of the line? Too imprecise). So I am also going to delete some of them. - -#+begin_src emacs-lisp - (use-package evil - :general - (:states '(normal visual motion operator) - ;; These go into operator mode, so the key sequence, g U i o - ;; upper cases the symbol at point: - "g u" '("downcase" . evil-downcase) - "g U" '("upcase" . evil-upcase) - "g ~" '("invert case" . evil-invert-case) - - ;; Use this ALL the time: - "g ;" '("last change →" . evil-goto-last-change) - "g :" '("last change ←" . evil-goto-last-change-reverse) - "g d" '("goto def" . evil-goto-definition) - "g i" '("resume insert" . evil-insert-resume) - "g v" '("resume visual" . evil-visual-restore) - - "g g" '("goto first line" . evil-goto-first-line) - "g f" '("find file" . find-file-at-point) - - "g e" '("← WORD end" . evil-backward-WORD-end) ; like b - "g E" '("← word end" . evil-backward-word-end) ; like B - "g w" '("→ WORD end" . evil-forward-WORD-end) - "g W" '("→ word end" . evil-forward-word-end) - - ;; Not sure how to use these two as they need text objs - "g n" '("next match" , evil-next-match) - "g N" '("prev match" , evil-previous-match) - - "g P" '("paste after" . evil-paste-before-cursor-after) - - ;; Let's clean out keybindings already in normal mode - ;; without the initial g: - "g #" nil ; evil-search-unbounded-word-backward - "g *" nil ; evil-search-unbounded-word-forward - "g ^" nil ; evil-first-non-blank - "g $" nil ; evil-end-of-line - "g _" nil ; evil-last-non-blank ... eh - "g 0" nil ; evil-beginning-of-line - "g &" nil ; evil-ex-repeat-global-substitute - "g 8" nil ; what-cursor-position - "g F" nil ; evil-find-file-at-point-with-line - "g J" nil ; evil-join-whitespace - "g I" nil ; evil-insert-0-line ... just use I - "g m" nil ; evil-middle-of-visual-line - "g M" nil ; evil-percentage-of-line ... middle? - "g T" nil ; tab-bar-switch-to-prev-tab - "g t" nil ; tab-bar-switch-to-next-tab - - "g j" nil ; This will be a major-mode-specific keybinding - "g k" nil - - (kbd "g C-]") nil - (kbd "g ") nil - (kbd "g ") nil - (kbd "g ") nil - (kbd "g ") nil - (kbd "g ") nil - (kbd "g ") nil)) -#+end_src - -While we are at it, let’s readd, and relabel the ~z~ command functions: -#+begin_src emacs-lisp - (use-package evil - :general - (:states '(normal visual motion operator) - "z q" '("fill para" . fill-paragraph) - "z Q" '("unfill para" . unfill-paragraph) - "z p" '("unfill para" . unfill-paragraph) - - "z m" '("scroll to center" . evil-scroll-line-to-center) - "z t" '("scroll to top" . evil-scroll-line-to-top) - "z b" '("scroll to bottom" . evil-scroll-line-to-bottom) - (kbd "z ") '("scroll left" . evil-scroll-column-left) - (kbd "z ") '("scroll right" . evil-scroll-column-right) - - "z a" '("toggle fold" . evil-toggle-fold) - "z f" '("close fold" . evil-close-fold) - "z o" '("open fold" . evil-open-fold) - "z F" '("close all folds" . evil-close-folds) - "z O" '("open all folds" . evil-open-folds) - ;; Open a fold at point recursively? Never see a need: - - ;; Since I have overridden z-l and whatnot, why have z-h? - "z e" nil ; evil-scroll-end-column - "z h" nil ; evil-scroll-column-left - "z l" nil ; evil-scroll-column-right - "z r" nil - "z s" nil ; evil-scroll-start-column - "z ^" nil ; evil-scroll-top-line-to-bottom - "z +" nil ; evil-scroll-bottom-line-to-top - "z -" nil ; evil-scroll-line-to-bottom-first-non-blank - "z ." nil ; evil-scroll-line-to-center-first-non-blank - (kbd "z RET") nil ; evil-scroll-line-to-top - (kbd "z ") nil)) ; evil-scroll-line-to-top -#+end_src -** Top-Level Operations -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: -#+begin_src emacs-lisp - (ha-leader - "SPC" '("M-x" . execute-extended-command) - "" '(keyboard-escape-quit :which-key t) - "." '("repeat" . repeat) - "!" '("shell command" . shell-command) - "|" 'piper - "X" '("org capture" . org-capture) - "L" '("store org link" . org-store-link) - "RET" 'bookmark-jump - "a" '(:ignore t :which-key "apps") - "a " '(keyboard-escape-quit :which-key t) - "m" '(:ignore t :which-key "mode") - "m " '(keyboard-escape-quit :which-key t) - "o" '(:ignore t :which-key "org/open") - "o " '(keyboard-escape-quit :which-key t) - "o i" 'imenu - "u" 'universal-argument) -#+end_src -And ways to stop the system: -#+begin_src emacs-lisp - (ha-leader - "q" '(:ignore t :which-key "quit/session") - "q " '(keyboard-escape-quit :which-key t) - "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)) -#+end_src -And ways to load my tangled org-files: -#+begin_src emacs-lisp - (ha-leader - "h h" '(:ignore t :which-key "hamacs") - "h h " '(keyboard-escape-quit :which-key t) - "h h f" '("features" . ha-hamacs-features) - "h h h" '("reload" . ha-hamacs-load) - "h h a" '("reload all" . ha-hamacs-reload-all)) -#+end_src -** File Operations -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 - -This simple function allows me to load a project-specific file in a numbered window, based on winum: -#+begin_src emacs-lisp - (defun find-file-in-window (win) - "Change the buffer in a particular window number." - (interactive) - (if (windowp win) - (aw-switch-to-window win) - (winum-select-window-by-number win)) - (consult-projectile-find-file)) -#+end_src - -With these helper functions in place, I can create a leader collection for file-related functions: -#+begin_src emacs-lisp - (ha-leader - "f" '(:ignore t :which-key "files") - "f " '(keyboard-escape-quit :which-key t) - "f a" '("load any" . find-file) - "f f" '("load" . consult-projectile-find-file) - "f F" '("load new window" . find-file-other-window) - "f l" '("locate" . locate) - "f s" '("save" . save-buffer) - "f S" '("save as" . write-buffer) - "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" . dirvish) - - "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)) -#+end_src - -On Unix systems, the =locate= command is faster than =find= when searching the whole system, since it uses a pre-computed database, and =find= is faster if you need to search a specific directory instead of the whole system. On the Mac, we need to change the =locate= command: - -#+begin_src emacs-lisp - (when (ha-running-on-macos?) - (setq locate-command "mdfind")) -#+end_src - -The advantage of =mdfind= is that is searches for filename /and/ its contents of your search string. - -Trying the [[https://github.com/benmaughan/spotlight.el][spotlight]] project, as it has a slick interface for selecting files: - -#+begin_src emacs-lisp - (use-package spotlight - :config (ha-leader "f /" '("search files" . spotlight))) -#+end_src -** 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: -#+begin_src emacs-lisp - (defun ha-yank-buffer-contents () - "Copy narrowed contents of the buffer to the clipboard." - (interactive) - (kill-new (buffer-substring-no-properties - (point-min) (point-max)))) -#+end_src - -This simple function allows me to switch to a buffer in a numbered window, based on winum: -#+begin_src emacs-lisp - (defun switch-buffer-in-window (win) - "Change the buffer in a particular window number." - (interactive) - (if (windowp win) - (aw-switch-to-window win) - (winum-select-window-by-number win)) - (consult-project-buffer)) -#+end_src - -And the collection of useful operations: -#+begin_src emacs-lisp - (ha-leader - "b" '(:ignore t :which-key "buffers") - "b " '(keyboard-escape-quit :which-key t) - "b B" '("switch" . persp-switch-to-buffer) - "b o" '("switch" . switch-to-buffer-other-window) - "b O" '("other" . projectile-switch-buffer-to-other-window) - "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) - "b y" '("copy contents" . ha-yank-buffer-contents) - "b z" '("bury" . bury-buffer) - "b Z" '("unbury" . unbury-buffer) - - "b 1" '("load win-1" . (lambda () (interactive) (switch-buffer-in-window 1))) - "b 2" '("load win-2" . (lambda () (interactive) (switch-buffer-in-window 2))) - "b 3" '("load win-3" . (lambda () (interactive) (switch-buffer-in-window 3))) - "b 4" '("load win-4" . (lambda () (interactive) (switch-buffer-in-window 4))) - "b 5" '("load win-5" . (lambda () (interactive) (switch-buffer-in-window 5))) - "b 6" '("load win-6" . (lambda () (interactive) (switch-buffer-in-window 6))) - "b 7" '("load win-7" . (lambda () (interactive) (switch-buffer-in-window 7))) - "b 8" '("load win-8" . (lambda () (interactive) (switch-buffer-in-window 8))) - "b 9" '("load win-9" . (lambda () (interactive) (switch-buffer-in-window 9)))) -#+end_src -** Bookmarks -I like the idea of dropping returnable bookmarks, however, the built-in behavior doesn’t honor either /projects/ or /perspectives/, but I can make a =projectile=-specific filter and use that to jump to only bookmarks in the current project. Likewise, if I want to jump to /any/ bookmark, I can switch to that buffer’s perspective. - -#+begin_src emacs-lisp - (defun projectile-bookmark-jump (bmark) - "Jump to the bookmark, BMARK, showing a filtered list based on current project." - (interactive (list (completing-read "Jump to Bookmark: " (projectile-bookmarks)))) - (bookmark-jump bmark)) - - (defun projectile-bookmarks () - "Return a list of bookmarks associated with the current projectile project." - (let ((bmarks (bookmark-all-names))) - (cl-remove-if-not #'projectile-bookmark-p bmarks))) - - (defun projectile-bookmark-p (bmark) - "Use as a filter to compare bookmark, BMARK with current project." - (let ((bmark-path (expand-file-name (bookmark-location bmark)))) - (string-prefix-p (projectile-project-root) bmark-path))) - - (defun persp-bookmark-jump (bmark) - "Jump to bookmkar, BMARK, but switch to its perspective first." - (interactive (list (completing-read "Jump to Bookmark:" (bookmark-all-names)))) - (bookmark-jump bmark 'persp-switch-to-buffer)) - - (ha-leader - "b m" '("set bookmark" . bookmark-set) - "b g" '("goto proj bookmark" . projectile-bookmark-jump) - "b G" '("goto any bookmark" . persp-bookmark-jump) - "b M" '("delete mark" . bookmark-delete)) -#+end_src -** Toggle Switches -The goal here is toggle switches and other miscellaneous settings. -#+begin_src emacs-lisp - (ha-leader - "t" '(:ignore t :which-key "toggles") - "t " '(keyboard-escape-quit :which-key t) - "t a" '("abbrev" . abbrev-mode) - "t d" '("debug" . toggle-debug-on-error) - "t F" '("show functions" . which-function-mode) - "t f" '("auto-fill" . auto-fill-mode) - "t o" '("overwrite" . overwrite-mode) - "t l" '("line numbers" . display-line-numbers-mode) - "t R" '("read only" . read-only-mode) - "t t" '("truncate" . toggle-truncate-lines) - "t v" '("visual" . visual-line-mode) - "t w" '("whitespace" . whitespace-mode)) -#+end_src -*** Line Numbers -Since we can't automatically toggle between relative and absolute line numbers, we create this function: -#+begin_src emacs-lisp - (defun ha-toggle-relative-line-numbers () - (interactive) - (if (eq display-line-numbers 'relative) - (setq display-line-numbers t) - (setq display-line-numbers 'relative))) -#+end_src -Add it to the toggle menu: -#+begin_src emacs-lisp - (ha-leader - "t r" '("relative lines" . ha-toggle-relative-line-numbers)) -#+end_src -*** Narrowing -I like the focus the [[info:emacs#Narrowing][Narrowing features]] offer, but what a /dwim/ aspect: -#+begin_src emacs-lisp - (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)))) -#+end_src -And put it on the toggle menu: -#+begin_src emacs-lisp - (ha-leader "t n" '("narrow" . ha-narrow-dwim)) -#+end_src -** Window Operations -While it comes with Emacs, I use [[https://www.emacswiki.org/emacs/WinnerMode][winner-mode]] to undo window-related changes: -#+begin_src emacs-lisp - (use-package winner - :custom - (winner-dont-bind-my-keys t) - :config - (winner-mode +1)) -#+end_src -*** Ace Window -Use the [[https://github.com/abo-abo/ace-window][ace-window]] project to jump to any window you see. - -Often transient buffers show in other windows, obscuring my carefully crafted display. Instead of jumping into a window, typing ~q~ (to either call [[help:quit-buffer][quit-buffer]]) if available, or [[help:bury-buffer][bury-buffer]] otherwise. This function hooks to =ace-window= -#+begin_src emacs-lisp - (defun ha-quit-buffer (window) - "Quit or bury buffer in a given WINDOW." - (interactive) - (aw-switch-to-window window) - (unwind-protect - (condition-case nil - (quit-buffer) - (error - (bury-buffer)))) - (aw-flip-window)) -#+end_src - -Since I use numbers for the window, I can make the commands more mnemonic, and add my own: -#+begin_src emacs-lisp - (use-package ace-window - :init - (setq aw-dispatch-alist - '((?d aw-delete-window "Delete Window") - (?m aw-swap-window "Swap Windows") - (?M aw-move-window "Move Window") - (?c aw-copy-window "Copy Window") - (?b switch-buffer-in-window "Select Buffer") - (?f find-file-in-window "Find File") - (?n aw-flip-window) - (?c aw-split-window-fair "Split Fair Window") - (?s aw-split-window-vert "Split Vert Window") - (?v aw-split-window-horz "Split Horz Window") - (?o delete-other-windows "Delete Other Windows") - (?q ha-quit-buffer "Quit Buffer") - (?w aw-execute-command-other-window "Execute Command") - (?? aw-show-dispatch-help))) - - :bind ("s-w" . ace-window)) -#+end_src -Keep in mind, these shortcuts work with more than two windows open. For instance, ~SPC w w d 3~ closes the "3" window. -*** Transpose Windows -My office at work has a monitor oriented vertically, and to move an Emacs with “three columned format” to a “stacked format” I use the [[https://www.emacswiki.org/emacs/TransposeFrame][transpose-frame]] package: -#+begin_src emacs-lisp - (use-package transpose-frame) -#+end_src -*** Winum -To jump to a window even quicker, use the [[https://github.com/deb0ch/emacs-winum][winum package]]: -#+begin_src emacs-lisp - (use-package winum - :bind (("s-1" . winum-select-window-1) - ("s-2" . winum-select-window-2) - ("s-3" . winum-select-window-3) - ("s-4" . winum-select-window-4) - ("s-5" . winum-select-window-5) - ("s-6" . winum-select-window-6) - ("s-7" . winum-select-window-7) - ("s-8" . winum-select-window-8) - ("s-9" . winum-select-window-9))) -#+end_src - -This is nice since the window numbers are always present on a Doom modeline, but they sometime order the window numbers /differently/ than =ace-window=. - -The ~0~ key/window should be always associated with a project-specific tree window of =dired= (or [[Dirvish][Dirvish]]): -#+begin_src emacs-lisp - (use-package winum - :config - (winum-mode +1) - (add-to-list 'winum-assign-functions - (lambda () (when (eq major-mode 'dired-mode) 10)))) -#+end_src - -I’d like to have dirvish show in Window 0: -#+begin_src emacs-lisp - (defun dirvish-show-or-switch () - "As it says on the tin. Show or start Dirvish. - If `divish' is showing, that is, is window 0 is showing, - switch to it, otherwise, start 'er up." - (interactive) - (if (seq-contains (winum--available-numbers) 0) - (winum-select-window-0-or-10) - (dirvish-side (projectile-project-root)))) -#+end_src - -And let’s bind Command-0 to select the window that shows dirvish, or open drvish: -#+begin_src emacs-lisp - (use-package winum - :bind ("s-0" . dirvish-show-or-switch)) -#+end_src -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 - (use-package hydra - :config - (defhydra hydra-window-resize (:color blue :hint nil) " - _w_: select _m_: move/swap _u_: undo _^_: taller (t) _+_: text larger - _j_: go up _d_: delete _U_: undo+ _v_: shorter (T) _-_: text smaller - _k_: down _e_: balance _r_: redo _>_: wider _F_: font larger - _h_: left _n_: v-split _R_: redo+ _<_: narrower _f_: font smaller - _l_: right _s_: split _o_: only this window _c_: choose (also 1-9)" - ("w" ace-window) - ("c" other-window :color pink) ; change window - ("o" delete-other-windows) ; “Only” this window - ("d" delete-window) ("x" delete-window) - - ;; Ace Windows ... select the window to affect: - ("m" ace-swap-window) - ("D" ace-delete-window) - ("O" ace-delete-other-windows) - - ("u" winner-undo) - ("U" winner-undo :color pink) - ("C-r" winner-redo) - ("r" winner-redo) - ("R" winner-redo :color pink) - - ("J" evil-window-down :color pink) - ("K" evil-window-up :color pink) - ("H" evil-window-left :color pink) - ("L" evil-window-right :color pink) - - ("j" evil-window-down) - ("k" evil-window-up) - ("h" evil-window-left) - ("l" evil-window-right) - - ("x" transpose-frame) - ("s" hydra-window-split/body) - ("n" hydra-window-split/body) - - ("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) - ("." evil-window-increase-width :color pink) - ("," evil-window-decrease-width :color pink) - ("e" balance-windows) - - ("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" dirvish-dwim) - - ;; Extra bindings: - ("q" nil :color blue))) - - (ha-leader "w" '("windows" . hydra-window-resize/body)) -#+end_src -*** Window Splitting -When I split a window, I have a following intentions: - - Split and open a file from the prespective/project in the new window - - Split and change to a buffer from the prespective in the new window - - Split and move focus to the new window … you know, to await a new command - -And when creating new windows, why isn't the new window selected? Also, when I create a new window, I typically want a different buffer or file shown. -#+begin_src emacs-lisp - (defun ha-new-window (side file-or-buffer) - (pcase side - (:left (split-window-horizontally)) - (:right (split-window-horizontally) - (other-window 1)) - (:above (split-window-vertically)) - (:below (split-window-vertically) - (other-window 1))) - (pcase file-or-buffer - (:file (call-interactively 'consult-projectile-find-file)) - (:buffer (call-interactively 'consult-projectile-switch-to-buffer)) - (:term (ha-shell (projectile-project-root))))) -#+end_src - -Shame that hydra doesn’t have an /ignore-case/ feature. -#+begin_src emacs-lisp - (use-package hydra - :config - (defhydra hydra-window-split (:color blue :hint nil) - ("s" hydra-window-split-below/body "below") - ("j" hydra-window-split-below/body "below") - ("k" hydra-window-split-above/body "above") - ("h" hydra-window-split-left/body "left") - ("l" hydra-window-split-right/body "right") - ("n" hydra-window-split-right/body "right")) - - (defhydra hydra-window-split-above (:color blue :hint nil) - ("b" (lambda () (interactive) (ha-new-window :above :buffer)) "switch buffer") - ("f" (lambda () (interactive) (ha-new-window :above :file)) "load file") - ("t" (lambda () (interactive) (ha-new-window :above :term)) "terminal") - ("k" split-window-below "split window")) - - (defhydra hydra-window-split-below (:color blue :hint nil) - ("b" (lambda () (interactive) (ha-new-window :below :buffer)) "switch buffer") - ("f" (lambda () (interactive) (ha-new-window :below :file)) "load file ") - ("t" (lambda () (interactive) (ha-new-window :below :term)) "terminal") - ("j" (lambda () (interactive) (split-window-below) (other-window 1)) "split window ") - ("s" (lambda () (interactive) (split-window-below) (other-window 1)) "split window ")) - - (defhydra hydra-window-split-right (:color blue :hint nil) - ("b" (lambda () (interactive) (ha-new-window :right :buffer)) "switch buffer") - ("f" (lambda () (interactive) (ha-new-window :right :file)) "load file") - ("t" (lambda () (interactive) (ha-new-window :right :term)) "terminal") - ("l" (lambda () (interactive) (split-window-right) (other-window 1)) "split window ") - ("n" (lambda () (interactive) (split-window-right) (other-window 1)) "split window ")) - - (defhydra hydra-window-split-left (:color blue :hint nil) - ("b" (lambda () (interactive) (ha-new-window :left :buffer)) "switch buffer") - ("f" (lambda () (interactive) (ha-new-window :left :file)) "load file ") - ("t" (lambda () (interactive) (ha-new-window :left :term)) "terminal") - ("h" split-window-right "split window"))) -#+end_src -This means that, without thinking, the following just works: - - ~SPC w s s s~ :: creates a window directly below this. - - ~SPC w n n n~ :: creates a window directly to the right. -But, more importantly, the prefix ~w s~ gives me more precision to view what I need. -** Search Operations -Ways to search for information goes under the ~s~ key. The venerable sage has always been =grep=, but we now have new-comers, like [[https://github.com/BurntSushi/ripgrep][ripgrep]], which are really fast. -*** ripgrep -Install 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. - -#+begin_src emacs-lisp - (use-package rg - :config - ;; Make an interesting Magit-like menu of options, which I don't use much: - (rg-enable-default-bindings (kbd "M-R")) - - ;; Old habits die hard ... - (define-key global-map [remap xref-find-references] 'rg-dwim) - - (ha-leader - "s" '(:ignore t :which-key "search") - "s " '(keyboard-escape-quit :which-key t) - "s q" '("close" . ha-rg-close-results-buffer) - "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)) - - (defun ha-rg-close-results-buffer () - "Close to the `*rg*' buffer that `rg' creates." - (interactive) - (kill-buffer "*rg*")) - - (defun ha-rg-go-results-buffer () - "Pop to the `*rg*' buffer that `rg' creates." - (interactive) - (pop-to-buffer "*rg*")) - - (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)) - - (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))) -#+end_src -Note we bind the key ~M-R~ to the [[help:rg-menu][rg-menu]], which is a Magit-like interface to =ripgrep=. - -I don’t understand the bug associated with the =:general= extension to =use-package=, but it /works/, but stops everything else from working, so pulling it out into its own =use-package= section addresses that issue: -#+begin_src emacs-lisp - (use-package rg - :general (:states 'normal "gS" 'rg-dwim)) -#+end_src -*** wgrep -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: -#+begin_src emacs-lisp - (use-package wgrep - :after rg - :commands wgrep-rg-setup - :hook (rg-mode-hook . wgrep-rg-setup) - :config - (ha-leader - :keymaps 'rg-mode-map ; Actually, `i' works! - "s w" '("wgrep-mode" . wgrep-change-to-wgrep-mode) - "t w" '("wgrep-mode" . wgrep-change-to-wgrep-mode))) -#+end_src -** Text Operations -Stealing much of this from Spacemacs. -#+begin_src emacs-lisp - (ha-leader - "x" '(:ignore t :which-key "text") - "x " '(keyboard-escape-quit :which-key t) - "x a" '("align" . align-regexp) - "x q" '("fill paragraph" . fill-paragraph) - "x p" '("unfill paragraph" . unfill-paragraph)) -#+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: -#+begin_src emacs-lisp - (defun unfill-paragraph () - "Convert a multi-line paragraph into a single line of text." - (interactive) - (let ((fill-column (point-max))) - (fill-paragraph nil))) -#+end_src -** Help Operations -While the ~C-h~ is easy enough, I am now in the habit of typing ~SPC h~ instead. -Since I tweaked the help menu, I craft my own menu: -#+begin_src emacs-lisp - (ha-leader - "h" '(:ignore t :which-key "help") - "h " '(keyboard-escape-quit :which-key t) - "h ." '("cursor position" . what-cursor-position) - "h a" '("apropos" . apropos-command) - "h c" '("elisp cheatsheet" . shortdoc-display-group) - "h e" '("errors" . view-echo-area-messages) - "h f" '("function" . helpful-callable) - "h F" '("font" . describe-font) - "h =" '("face" . describe-face) - "h k" '("key binding" . helpful-key) - "h K" '("key map" . describe-keymap) - "h m" '("mode" . describe-mode) - "h o" '("symbol" . describe-symbol) - "h p" '("package" . describe-package) - "h s" '("info symbol" . info-lookup-symbol) - "h v" '("variable" . helpful-variable) - "h i" '("info" . info) - "h I" '("info manual" . info-display-manual) - "h j" '("info jump" . info-apropos) - - "h E" '("emacs info" . (lambda () (interactive) (info "emacs"))) - "h L" '("emacs-lisp" . (lambda () (interactive) (info "elisp"))) - "h O" '("org info" . (lambda () (interactive) (info "org"))) - ;; Since I do a lot of literate programming, I appreciate a quick - ;; jump directly into the Info manual... - "h B" '("org babel" . (lambda () (interactive) - (org-info-open "org#Working with Source Code" nil)))) -#+end_src - -Remember these keys in the *Help* buffer: - - ~s~ :: view source of the function - - ~i~ :: view info manual of the function - -Let's make Info behave a little more VI-like: -#+begin_src emacs-lisp - (use-package info - :straight (:type built-in) - :general - (:states 'normal :keymaps 'Info-mode-map - "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 - "p" 'Info-backward-node - "n" 'Info-forward-node)) ; Old habit die hard -#+end_src -** Consult -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 - (use-package consult - :after general - ;; 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) - - (ha-leader - "RET" '("bookmark" . consult-bookmark) - "o i" '("imenu" . consult-imenu) - "x y" '("preview yank" . consult-yank-pop)) - - :bind ("s-v" . consult-yank-pop) - - :general - (:states 'normal - "gp" '("preview paste" . 'consult-yank-pop) - "gs" '("go to line" . 'consult-line))) -#+end_src -** Consult for Projects -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 - (use-package consult-projectile - :after (consult general projectile) - :straight (:host gitlab :repo "OlMon/consult-projectile" :branch "master") - :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))) -#+end_src -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. -** Embark -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 - (use-package embark - :bind - (("s-." . embark-act) ; Work in minibuffer and elsewhere - ("s-/" . embark-dwim)) - - :init - ;; Optionally replace the key help with a completing-read interface - (setq prefix-help-command #'embark-prefix-help-command) - - :config - (ha-leader "h K" '("keybindings" . embark-bindings))) -#+end_src - -In [[https://karthinks.com/software/fifteen-ways-to-use-embark/][15 Ways to Use Embark]], Karthik Chikmagalur suggests a nifty macro for integrating Embark with [[Ace Window][Ace Window]]: -#+begin_src emacs-lisp - (use-package embark - :after ace-window - :config - (defmacro my/embark-ace-action (fn) - `(defun ,(intern (concat "my/embark-ace-" (symbol-name fn))) () - (interactive) - (with-demoted-errors "%s" - (require 'ace-window) - (let ((aw-dispatch-always t)) - (aw-switch-to-window (aw-select nil)) - (call-interactively (symbol-function ',fn)))))) - - (defmacro my/embark-split-action (fn split-type) - `(defun ,(intern (concat "my/embark-" - (symbol-name fn) - "-" - (car (last (split-string - (symbol-name split-type) "-"))))) () - (interactive) - (funcall #',split-type) - (call-interactively #',fn))) - - ;; Use the macros to define some helper functions: - (my/embark-ace-action find-file) ; --> my/embark-ace-find-file - (my/embark-ace-action switch-to-buffer) ; --> my/embark-ace-switch-to-buffer - (my/embark-ace-action bookmark-jump) ; --> my/embark-ace-bookmark-jump - (my/embark-split-action find-file split-window-below) ; --> my/embark-find-file-below - (my/embark-split-action find-file split-window-right) ; --> my/embark-find-file-right - (my/embark-split-action switch-to-buffer split-window-below) ; --> my/embark-switch-to-buffer-below - (my/embark-split-action switch-to-buffer split-window-right) ; --> my/embark-switch-to-buffer-right - (my/embark-split-action bookmark-jump split-window-below) ; --> my/embark-bookmark-jump-below - (my/embark-split-action bookmark-jump split-window-right)) ; --> my/embark-bookmark-jump-right -#+end_src - -We can rebind the various =embark-xyz-map= with calls to our macroized functions: -#+begin_src emacs-lisp - (use-package embark - :bind - (:map embark-file-map - ("y" . embark-copy-as-kill) - ("Y" . embark-save-relative-path) - ("W" . nil) - ("w" . my/embark-ace-find-file) - ("2" . my/embark-find-file-below) - ("3" . my/embark-find-file-right) - :map embark-buffer-map - ("y" . embark-copy-as-kill) - ("w" . my/embark-ace-switch-to-buffer) - ("2" . my/embark-switch-to-buffer-below) - ("3" . my/embark-switch-to-buffer-right) - :map embark-file-map - ("y" . embark-copy-as-kill) - ("w" . my/embark-ace-bookmark-jump) - ("2" . my/embark-bookmark-jump-below) - ("3" . my/embark-bookmark-jump-right))) -#+end_src - -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: -#+begin_src emacs-lisp - (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)) -#+end_src - -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. - -#+begin_src emacs-lisp - (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) -#+end_src * 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, and the /objects/ do not need to be adjacent. diff --git a/ha-feed-reader.org b/ha-feed-reader.org index 5fe9d05..a48b640 100644 --- a/ha-feed-reader.org +++ b/ha-feed-reader.org @@ -42,7 +42,7 @@ Let's get our feeds from a collection of org mode files. By default, Doom config "n" 'elfeed-show-next "p" 'elfeed-show-prev "y" 'elfeed-show-yank - "q" 'evil-delete-buffer + "q" 'kill-buffer "o" 'link-hint-open-link ; This is why this package depends on link-hint: "Q" 'delete-window) (:states 'normal :keymaps 'elfeed-search-mode-map diff --git a/ha-general.org b/ha-general.org new file mode 100644 index 0000000..8a4c6e4 --- /dev/null +++ b/ha-general.org @@ -0,0 +1,993 @@ +#+TITLE: Leader Key Sequences +#+AUTHOR: Howard X. Abrams +#+DATE: 2024-01-31 +#+FILETAGS: :emacs: + +A literate programming file for defining leaders with general + +#+begin_src emacs-lisp :exports none + ;;; ha-leader --- defining leaders with general -*- lexical-binding: t; -*- + ;; + ;; © 2024 Howard X. Abrams + ;; This work is licensed under a Creative Commons Attribution 4.0 International License. + ;; See http://creativecommons.org/licenses/by/4.0/ + ;; + ;; Author: Howard X. Abrams + ;; Maintainer: Howard X. Abrams + ;; Created: January 31, 2024 + ;; + ;; While obvious, GNU Emacs does not include this file or project. + ;; + ;; *NB:* Do not edit this file. Instead, edit the original literate file at: + ;; /Users/howard.abrams/other/hamacs/ha-leader.org + ;; And tangle the file to recreate this one. + ;; + ;;; Code: +#+end_src + +* Introduction +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. + +#+begin_src emacs-lisp + (use-package general + :config + (setq general-use-package-emit-autoloads t) + + (general-evil-setup t) + + (general-create-definer ha-leader + :states '(normal visual motion) + :keymaps 'override + :prefix "SPC" + :non-normal-prefix "M-SPC" + :prefix "") + + (general-create-definer ha-local-leader + :states '(normal visual motion) + :prefix "," + :global-prefix "" + :non-normal-prefix "S-SPC") + + (general-nmap "SPC m" (general-simulate-key "," :which-key "major mode"))) +#+end_src +** Relabel the G Keys +Can’t remember all the shortcuts on the ~g~ key, and =which-key= displays the entire function, so let’s /re-add/ those keybindings, but with labels. The ~g~ is extemely convenient, yet I realize that I will never use some of the default keybindings (like ~g m~ to go to the middle of the line? Too imprecise). So I am also going to delete some of them. + +#+begin_src emacs-lisp + (use-package evil + :general + (:states '(normal visual motion operator) + ;; These go into operator mode, so the key sequence, g U i o + ;; upper cases the symbol at point: + "g u" '("downcase" . evil-downcase) + "g U" '("upcase" . evil-upcase) + "g ~" '("invert case" . evil-invert-case) + + ;; Use this ALL the time: + "g ;" '("last change →" . evil-goto-last-change) + "g :" '("last change ←" . evil-goto-last-change-reverse) + "g d" '("goto def" . evil-goto-definition) + "g i" '("resume insert" . evil-insert-resume) + "g v" '("resume visual" . evil-visual-restore) + + "g g" '("goto first line" . evil-goto-first-line) + "g f" '("find file" . find-file-at-point) + + "g e" '("← WORD end" . evil-backward-WORD-end) ; like b + "g E" '("← word end" . evil-backward-word-end) ; like B + "g w" '("→ WORD end" . evil-forward-WORD-end) + "g W" '("→ word end" . evil-forward-word-end) + + ;; Not sure how to use these two as they need text objs + "g n" '("next match" , evil-next-match) + "g N" '("prev match" , evil-previous-match) + + "g P" '("paste after" . evil-paste-before-cursor-after) + + ;; Let's clean out keybindings already in normal mode + ;; without the initial g: + "g #" nil ; evil-search-unbounded-word-backward + "g *" nil ; evil-search-unbounded-word-forward + "g ^" nil ; evil-first-non-blank + "g $" nil ; evil-end-of-line + "g _" nil ; evil-last-non-blank ... eh + "g 0" nil ; evil-beginning-of-line + "g &" nil ; evil-ex-repeat-global-substitute + "g 8" nil ; what-cursor-position + "g F" nil ; evil-find-file-at-point-with-line + "g J" nil ; evil-join-whitespace + "g I" nil ; evil-insert-0-line ... just use I + "g m" nil ; evil-middle-of-visual-line + "g M" nil ; evil-percentage-of-line ... middle? + "g T" nil ; tab-bar-switch-to-prev-tab + "g t" nil ; tab-bar-switch-to-next-tab + + "g j" nil ; This will be a major-mode-specific keybinding + "g k" nil + + (kbd "g C-]") nil + (kbd "g ") nil + (kbd "g ") nil + (kbd "g ") nil + (kbd "g ") nil + (kbd "g ") nil + (kbd "g ") nil)) +#+end_src + +While we are at it, let’s readd, and relabel the ~z~ command functions: +#+begin_src emacs-lisp + (use-package evil + :general + (:states '(normal visual motion operator) + "z q" '("fill para" . fill-paragraph) + "z Q" '("unfill para" . unfill-paragraph) + "z p" '("unfill para" . unfill-paragraph) + + "z m" '("scroll to center" . evil-scroll-line-to-center) + "z t" '("scroll to top" . evil-scroll-line-to-top) + "z b" '("scroll to bottom" . evil-scroll-line-to-bottom) + (kbd "z ") '("scroll left" . evil-scroll-column-left) + (kbd "z ") '("scroll right" . evil-scroll-column-right) + + "z a" '("toggle fold" . evil-toggle-fold) + "z f" '("close fold" . evil-close-fold) + "z o" '("open fold" . evil-open-fold) + "z F" '("close all folds" . evil-close-folds) + "z O" '("open all folds" . evil-open-folds) + ;; Open a fold at point recursively? Never see a need: + + ;; Since I have overridden z-l and whatnot, why have z-h? + "z e" nil ; evil-scroll-end-column + "z h" nil ; evil-scroll-column-left + "z l" nil ; evil-scroll-column-right + "z r" nil + "z s" nil ; evil-scroll-start-column + "z ^" nil ; evil-scroll-top-line-to-bottom + "z +" nil ; evil-scroll-bottom-line-to-top + "z -" nil ; evil-scroll-line-to-bottom-first-non-blank + "z ." nil ; evil-scroll-line-to-center-first-non-blank + (kbd "z RET") nil ; evil-scroll-line-to-top + (kbd "z ") nil)) ; evil-scroll-line-to-top +#+end_src +** Top-Level Operations +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: +#+begin_src emacs-lisp + (ha-leader + "SPC" '("M-x" . execute-extended-command) + "" '(keyboard-escape-quit :which-key t) + "." '("repeat" . repeat) + "!" '("shell command" . shell-command) + "|" 'piper + "X" '("org capture" . org-capture) + "L" '("store org link" . org-store-link) + "RET" 'bookmark-jump + "a" '(:ignore t :which-key "apps") + "a " '(keyboard-escape-quit :which-key t) + "m" '(:ignore t :which-key "mode") + "m " '(keyboard-escape-quit :which-key t) + "o" '(:ignore t :which-key "org/open") + "o " '(keyboard-escape-quit :which-key t) + "o i" 'imenu + "u" 'universal-argument) +#+end_src +And ways to stop the system: +#+begin_src emacs-lisp + (ha-leader + "q" '(:ignore t :which-key "quit/session") + "q " '(keyboard-escape-quit :which-key t) + "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)) +#+end_src +And ways to load my tangled org-files: +#+begin_src emacs-lisp + (ha-leader + "h h" '(:ignore t :which-key "hamacs") + "h h " '(keyboard-escape-quit :which-key t) + "h h f" '("features" . ha-hamacs-features) + "h h h" '("reload" . ha-hamacs-load) + "h h a" '("reload all" . ha-hamacs-reload-all)) +#+end_src +** File Operations +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 + +This simple function allows me to load a project-specific file in a numbered window, based on winum: +#+begin_src emacs-lisp + (defun find-file-in-window (win) + "Change the buffer in a particular window number." + (interactive) + (if (windowp win) + (aw-switch-to-window win) + (winum-select-window-by-number win)) + (consult-projectile-find-file)) +#+end_src + +With these helper functions in place, I can create a leader collection for file-related functions: +#+begin_src emacs-lisp + (ha-leader + "f" '(:ignore t :which-key "files") + "f " '(keyboard-escape-quit :which-key t) + "f a" '("load any" . find-file) + "f f" '("load" . consult-projectile-find-file) + "f F" '("load new window" . find-file-other-window) + "f l" '("locate" . locate) + "f s" '("save" . save-buffer) + "f S" '("save as" . write-buffer) + "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" . dirvish) + + "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)) +#+end_src + +On Unix systems, the =locate= command is faster than =find= when searching the whole system, since it uses a pre-computed database, and =find= is faster if you need to search a specific directory instead of the whole system. On the Mac, we need to change the =locate= command: + +#+begin_src emacs-lisp + (when (ha-running-on-macos?) + (setq locate-command "mdfind")) +#+end_src + +The advantage of =mdfind= is that is searches for filename /and/ its contents of your search string. + +Trying the [[https://github.com/benmaughan/spotlight.el][spotlight]] project, as it has a slick interface for selecting files: + +#+begin_src emacs-lisp + (use-package spotlight + :config (ha-leader "f /" '("search files" . spotlight))) +#+end_src +** 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: +#+begin_src emacs-lisp + (defun ha-yank-buffer-contents () + "Copy narrowed contents of the buffer to the clipboard." + (interactive) + (kill-new (buffer-substring-no-properties + (point-min) (point-max)))) +#+end_src + +This simple function allows me to switch to a buffer in a numbered window, based on winum: +#+begin_src emacs-lisp + (defun switch-buffer-in-window (win) + "Change the buffer in a particular window number." + (interactive) + (if (windowp win) + (aw-switch-to-window win) + (winum-select-window-by-number win)) + (consult-project-buffer)) +#+end_src + +And the collection of useful operations: +#+begin_src emacs-lisp + (ha-leader + "b" '(:ignore t :which-key "buffers") + "b " '(keyboard-escape-quit :which-key t) + "b B" '("switch" . persp-switch-to-buffer) + "b o" '("switch" . switch-to-buffer-other-window) + "b O" '("other" . projectile-switch-buffer-to-other-window) + "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) + "b y" '("copy contents" . ha-yank-buffer-contents) + "b z" '("bury" . bury-buffer) + "b Z" '("unbury" . unbury-buffer) + + "b 1" '("load win-1" . (lambda () (interactive) (switch-buffer-in-window 1))) + "b 2" '("load win-2" . (lambda () (interactive) (switch-buffer-in-window 2))) + "b 3" '("load win-3" . (lambda () (interactive) (switch-buffer-in-window 3))) + "b 4" '("load win-4" . (lambda () (interactive) (switch-buffer-in-window 4))) + "b 5" '("load win-5" . (lambda () (interactive) (switch-buffer-in-window 5))) + "b 6" '("load win-6" . (lambda () (interactive) (switch-buffer-in-window 6))) + "b 7" '("load win-7" . (lambda () (interactive) (switch-buffer-in-window 7))) + "b 8" '("load win-8" . (lambda () (interactive) (switch-buffer-in-window 8))) + "b 9" '("load win-9" . (lambda () (interactive) (switch-buffer-in-window 9)))) +#+end_src +** Bookmarks +I like the idea of dropping returnable bookmarks, however, the built-in behavior doesn’t honor either /projects/ or /perspectives/, but I can make a =projectile=-specific filter and use that to jump to only bookmarks in the current project. Likewise, if I want to jump to /any/ bookmark, I can switch to that buffer’s perspective. + +#+begin_src emacs-lisp + (defun projectile-bookmark-jump (bmark) + "Jump to the bookmark, BMARK, showing a filtered list based on current project." + (interactive (list (completing-read "Jump to Bookmark: " (projectile-bookmarks)))) + (bookmark-jump bmark)) + + (defun projectile-bookmarks () + "Return a list of bookmarks associated with the current projectile project." + (let ((bmarks (bookmark-all-names))) + (cl-remove-if-not #'projectile-bookmark-p bmarks))) + + (defun projectile-bookmark-p (bmark) + "Use as a filter to compare bookmark, BMARK with current project." + (let ((bmark-path (expand-file-name (bookmark-location bmark)))) + (string-prefix-p (projectile-project-root) bmark-path))) + + (defun persp-bookmark-jump (bmark) + "Jump to bookmkar, BMARK, but switch to its perspective first." + (interactive (list (completing-read "Jump to Bookmark:" (bookmark-all-names)))) + (bookmark-jump bmark 'persp-switch-to-buffer)) + + (ha-leader + "b m" '("set bookmark" . bookmark-set) + "b g" '("goto proj bookmark" . projectile-bookmark-jump) + "b G" '("goto any bookmark" . persp-bookmark-jump) + "b M" '("delete mark" . bookmark-delete)) +#+end_src +** Toggle Switches +The goal here is toggle switches and other miscellaneous settings. +#+begin_src emacs-lisp + (ha-leader + "t" '(:ignore t :which-key "toggles") + "t " '(keyboard-escape-quit :which-key t) + "t a" '("abbrev" . abbrev-mode) + "t d" '("debug" . toggle-debug-on-error) + "t F" '("show functions" . which-function-mode) + "t f" '("auto-fill" . auto-fill-mode) + "t o" '("overwrite" . overwrite-mode) + "t l" '("line numbers" . display-line-numbers-mode) + "t R" '("read only" . read-only-mode) + "t t" '("truncate" . toggle-truncate-lines) + "t v" '("visual" . visual-line-mode) + "t w" '("whitespace" . whitespace-mode)) +#+end_src +*** Line Numbers +Since we can't automatically toggle between relative and absolute line numbers, we create this function: +#+begin_src emacs-lisp + (defun ha-toggle-relative-line-numbers () + (interactive) + (if (eq display-line-numbers 'relative) + (setq display-line-numbers t) + (setq display-line-numbers 'relative))) +#+end_src +Add it to the toggle menu: +#+begin_src emacs-lisp + (ha-leader + "t r" '("relative lines" . ha-toggle-relative-line-numbers)) +#+end_src +*** Narrowing +I like the focus the [[info:emacs#Narrowing][Narrowing features]] offer, but what a /dwim/ aspect: +#+begin_src emacs-lisp + (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)))) +#+end_src +And put it on the toggle menu: +#+begin_src emacs-lisp + (ha-leader "t n" '("narrow" . ha-narrow-dwim)) +#+end_src +** Window Operations +While it comes with Emacs, I use [[https://www.emacswiki.org/emacs/WinnerMode][winner-mode]] to undo window-related changes: +#+begin_src emacs-lisp + (use-package winner + :custom + (winner-dont-bind-my-keys t) + :config + (winner-mode +1)) +#+end_src +*** Ace Window +Use the [[https://github.com/abo-abo/ace-window][ace-window]] project to jump to any window you see. + +Often transient buffers show in other windows, obscuring my carefully crafted display. Instead of jumping into a window, typing ~q~ (to either call [[help:quit-buffer][quit-buffer]]) if available, or [[help:bury-buffer][bury-buffer]] otherwise. This function hooks to =ace-window= +#+begin_src emacs-lisp + (defun ha-quit-buffer (window) + "Quit or bury buffer in a given WINDOW." + (interactive) + (aw-switch-to-window window) + (unwind-protect + (condition-case nil + (quit-buffer) + (error + (bury-buffer)))) + (aw-flip-window)) +#+end_src + +Since I use numbers for the window, I can make the commands more mnemonic, and add my own: +#+begin_src emacs-lisp + (use-package ace-window + :init + (setq aw-dispatch-alist + '((?d aw-delete-window "Delete Window") + (?m aw-swap-window "Swap Windows") + (?M aw-move-window "Move Window") + (?c aw-copy-window "Copy Window") + (?b switch-buffer-in-window "Select Buffer") + (?f find-file-in-window "Find File") + (?n aw-flip-window) + (?c aw-split-window-fair "Split Fair Window") + (?s aw-split-window-vert "Split Vert Window") + (?v aw-split-window-horz "Split Horz Window") + (?o delete-other-windows "Delete Other Windows") + (?q ha-quit-buffer "Quit Buffer") + (?w aw-execute-command-other-window "Execute Command") + (?? aw-show-dispatch-help))) + + :bind ("s-w" . ace-window)) +#+end_src +Keep in mind, these shortcuts work with more than two windows open. For instance, ~SPC w w d 3~ closes the "3" window. +*** Transpose Windows +My office at work has a monitor oriented vertically, and to move an Emacs with “three columned format” to a “stacked format” I use the [[https://www.emacswiki.org/emacs/TransposeFrame][transpose-frame]] package: +#+begin_src emacs-lisp + (use-package transpose-frame) +#+end_src +*** Winum +To jump to a window even quicker, use the [[https://github.com/deb0ch/emacs-winum][winum package]]: +#+begin_src emacs-lisp + (use-package winum + :bind (("s-1" . winum-select-window-1) + ("s-2" . winum-select-window-2) + ("s-3" . winum-select-window-3) + ("s-4" . winum-select-window-4) + ("s-5" . winum-select-window-5) + ("s-6" . winum-select-window-6) + ("s-7" . winum-select-window-7) + ("s-8" . winum-select-window-8) + ("s-9" . winum-select-window-9))) +#+end_src + +This is nice since the window numbers are always present on a Doom modeline, but they sometime order the window numbers /differently/ than =ace-window=. + +The ~0~ key/window should be always associated with a project-specific tree window of =dired= (or [[Dirvish][Dirvish]]): +#+begin_src emacs-lisp + (use-package winum + :config + (winum-mode +1) + (add-to-list 'winum-assign-functions + (lambda () (when (eq major-mode 'dired-mode) 10)))) +#+end_src + +I’d like to have dirvish show in Window 0: +#+begin_src emacs-lisp + (defun dirvish-show-or-switch () + "As it says on the tin. Show or start Dirvish. + If `divish' is showing, that is, is window 0 is showing, + switch to it, otherwise, start 'er up." + (interactive) + (if (seq-contains (winum--available-numbers) 0) + (winum-select-window-0-or-10) + (dirvish-side (projectile-project-root)))) +#+end_src + +And let’s bind Command-0 to select the window that shows dirvish, or open drvish: +#+begin_src emacs-lisp + (use-package winum + :bind ("s-0" . dirvish-show-or-switch)) +#+end_src +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 + (use-package hydra + :config + (defhydra hydra-window-resize (:color blue :hint nil) " + _w_: select _m_: move/swap _u_: undo _^_: taller (t) _+_: text larger + _j_: go up _d_: delete _U_: undo+ _v_: shorter (T) _-_: text smaller + _k_: down _e_: balance _r_: redo _>_: wider _F_: font larger + _h_: left _n_: v-split _R_: redo+ _<_: narrower _f_: font smaller + _l_: right _s_: split _o_: only this window _c_: choose (also 1-9)" + ("w" ace-window) + ("c" other-window :color pink) ; change window + ("o" delete-other-windows) ; “Only” this window + ("d" delete-window) ("x" delete-window) + + ;; Ace Windows ... select the window to affect: + ("m" ace-swap-window) + ("D" ace-delete-window) + ("O" ace-delete-other-windows) + + ("u" winner-undo) + ("U" winner-undo :color pink) + ("C-r" winner-redo) + ("r" winner-redo) + ("R" winner-redo :color pink) + + ("J" evil-window-down :color pink) + ("K" evil-window-up :color pink) + ("H" evil-window-left :color pink) + ("L" evil-window-right :color pink) + + ("j" evil-window-down) + ("k" evil-window-up) + ("h" evil-window-left) + ("l" evil-window-right) + + ("x" transpose-frame) + ("s" hydra-window-split/body) + ("n" hydra-window-split/body) + + ("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) + ("." evil-window-increase-width :color pink) + ("," evil-window-decrease-width :color pink) + ("e" balance-windows) + + ("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" dirvish-dwim) + + ;; Extra bindings: + ("q" nil :color blue))) + + (ha-leader "w" '("windows" . hydra-window-resize/body)) +#+end_src +*** Window Splitting +When I split a window, I have a following intentions: + - Split and open a file from the prespective/project in the new window + - Split and change to a buffer from the prespective in the new window + - Split and move focus to the new window … you know, to await a new command + +And when creating new windows, why isn't the new window selected? Also, when I create a new window, I typically want a different buffer or file shown. +#+begin_src emacs-lisp + (defun ha-new-window (side file-or-buffer) + (pcase side + (:left (split-window-horizontally)) + (:right (split-window-horizontally) + (other-window 1)) + (:above (split-window-vertically)) + (:below (split-window-vertically) + (other-window 1))) + (pcase file-or-buffer + (:file (call-interactively 'consult-projectile-find-file)) + (:buffer (call-interactively 'consult-projectile-switch-to-buffer)) + (:term (ha-shell (projectile-project-root))))) +#+end_src + +Shame that hydra doesn’t have an /ignore-case/ feature. +#+begin_src emacs-lisp + (use-package hydra + :config + (defhydra hydra-window-split (:color blue :hint nil) + ("s" hydra-window-split-below/body "below") + ("j" hydra-window-split-below/body "below") + ("k" hydra-window-split-above/body "above") + ("h" hydra-window-split-left/body "left") + ("l" hydra-window-split-right/body "right") + ("n" hydra-window-split-right/body "right")) + + (defhydra hydra-window-split-above (:color blue :hint nil) + ("b" (lambda () (interactive) (ha-new-window :above :buffer)) "switch buffer") + ("f" (lambda () (interactive) (ha-new-window :above :file)) "load file") + ("t" (lambda () (interactive) (ha-new-window :above :term)) "terminal") + ("k" split-window-below "split window")) + + (defhydra hydra-window-split-below (:color blue :hint nil) + ("b" (lambda () (interactive) (ha-new-window :below :buffer)) "switch buffer") + ("f" (lambda () (interactive) (ha-new-window :below :file)) "load file ") + ("t" (lambda () (interactive) (ha-new-window :below :term)) "terminal") + ("j" (lambda () (interactive) (split-window-below) (other-window 1)) "split window ") + ("s" (lambda () (interactive) (split-window-below) (other-window 1)) "split window ")) + + (defhydra hydra-window-split-right (:color blue :hint nil) + ("b" (lambda () (interactive) (ha-new-window :right :buffer)) "switch buffer") + ("f" (lambda () (interactive) (ha-new-window :right :file)) "load file") + ("t" (lambda () (interactive) (ha-new-window :right :term)) "terminal") + ("l" (lambda () (interactive) (split-window-right) (other-window 1)) "split window ") + ("n" (lambda () (interactive) (split-window-right) (other-window 1)) "split window ")) + + (defhydra hydra-window-split-left (:color blue :hint nil) + ("b" (lambda () (interactive) (ha-new-window :left :buffer)) "switch buffer") + ("f" (lambda () (interactive) (ha-new-window :left :file)) "load file ") + ("t" (lambda () (interactive) (ha-new-window :left :term)) "terminal") + ("h" split-window-right "split window"))) +#+end_src +This means that, without thinking, the following just works: + - ~SPC w s s s~ :: creates a window directly below this. + - ~SPC w n n n~ :: creates a window directly to the right. +But, more importantly, the prefix ~w s~ gives me more precision to view what I need. +** Search Operations +Ways to search for information goes under the ~s~ key. The venerable sage has always been =grep=, but we now have new-comers, like [[https://github.com/BurntSushi/ripgrep][ripgrep]], which are really fast. +*** ripgrep +Install 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. + +#+begin_src emacs-lisp + (use-package rg + :config + ;; Make an interesting Magit-like menu of options, which I don't use much: + (rg-enable-default-bindings (kbd "M-R")) + + ;; Old habits die hard ... + (define-key global-map [remap xref-find-references] 'rg-dwim) + + (ha-leader + "s" '(:ignore t :which-key "search") + "s " '(keyboard-escape-quit :which-key t) + "s q" '("close" . ha-rg-close-results-buffer) + "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)) + + (defun ha-rg-close-results-buffer () + "Close to the `*rg*' buffer that `rg' creates." + (interactive) + (kill-buffer "*rg*")) + + (defun ha-rg-go-results-buffer () + "Pop to the `*rg*' buffer that `rg' creates." + (interactive) + (pop-to-buffer "*rg*")) + + (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)) + + (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))) +#+end_src +Note we bind the key ~M-R~ to the [[help:rg-menu][rg-menu]], which is a Magit-like interface to =ripgrep=. + +I don’t understand the bug associated with the =:general= extension to =use-package=, but it /works/, but stops everything else from working, so pulling it out into its own =use-package= section addresses that issue: +#+begin_src emacs-lisp + (use-package rg + :general (:states 'normal "gS" 'rg-dwim)) +#+end_src +*** wgrep +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: +#+begin_src emacs-lisp + (use-package wgrep + :after rg + :commands wgrep-rg-setup + :hook (rg-mode-hook . wgrep-rg-setup) + :config + (ha-leader + :keymaps 'rg-mode-map ; Actually, `i' works! + "s w" '("wgrep-mode" . wgrep-change-to-wgrep-mode) + "t w" '("wgrep-mode" . wgrep-change-to-wgrep-mode))) +#+end_src +** Text Operations +Stealing much of this from Spacemacs. +#+begin_src emacs-lisp + (ha-leader + "x" '(:ignore t :which-key "text") + "x " '(keyboard-escape-quit :which-key t) + "x a" '("align" . align-regexp) + "x q" '("fill paragraph" . fill-paragraph) + "x p" '("unfill paragraph" . unfill-paragraph)) +#+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: +#+begin_src emacs-lisp + (defun unfill-paragraph () + "Convert a multi-line paragraph into a single line of text." + (interactive) + (let ((fill-column (point-max))) + (fill-paragraph nil))) +#+end_src +** Help Operations +While the ~C-h~ is easy enough, I am now in the habit of typing ~SPC h~ instead. +Since I tweaked the help menu, I craft my own menu: +#+begin_src emacs-lisp + (ha-leader + "h" '(:ignore t :which-key "help") + "h " '(keyboard-escape-quit :which-key t) + "h ." '("cursor position" . what-cursor-position) + "h a" '("apropos" . apropos-command) + "h c" '("elisp cheatsheet" . shortdoc-display-group) + "h e" '("errors" . view-echo-area-messages) + "h f" '("function" . helpful-callable) + "h F" '("font" . describe-font) + "h =" '("face" . describe-face) + "h k" '("key binding" . helpful-key) + "h K" '("key map" . describe-keymap) + "h m" '("mode" . describe-mode) + "h o" '("symbol" . describe-symbol) + "h p" '("package" . describe-package) + "h s" '("info symbol" . info-lookup-symbol) + "h v" '("variable" . helpful-variable) + "h i" '("info" . info) + "h I" '("info manual" . info-display-manual) + "h j" '("info jump" . info-apropos) + + "h E" '("emacs info" . (lambda () (interactive) (info "emacs"))) + "h L" '("emacs-lisp" . (lambda () (interactive) (info "elisp"))) + "h O" '("org info" . (lambda () (interactive) (info "org"))) + ;; Since I do a lot of literate programming, I appreciate a quick + ;; jump directly into the Info manual... + "h B" '("org babel" . (lambda () (interactive) + (org-info-open "org#Working with Source Code" nil)))) +#+end_src + +Remember these keys in the *Help* buffer: + - ~s~ :: view source of the function + - ~i~ :: view info manual of the function + +Let's make Info behave a little more VI-like: +#+begin_src emacs-lisp + (use-package info + :straight (:type built-in) + :general + (:states 'normal :keymaps 'Info-mode-map + "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 + "p" 'Info-backward-node + "n" 'Info-forward-node)) ; Old habit die hard +#+end_src +** Consult +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 + (use-package consult + :after general + ;; 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) + + (ha-leader + "RET" '("bookmark" . consult-bookmark) + "o i" '("imenu" . consult-imenu) + "x y" '("preview yank" . consult-yank-pop)) + + :bind ("s-v" . consult-yank-pop) + + :general + (:states 'normal + "gp" '("preview paste" . 'consult-yank-pop) + "gs" '("go to line" . 'consult-line))) +#+end_src +** Consult for Projects +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 + (use-package consult-projectile + :after (consult general projectile) + :straight (:host gitlab :repo "OlMon/consult-projectile" :branch "master") + :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))) +#+end_src +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. +** Embark +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 + (use-package embark + :bind + (("s-." . embark-act) ; Work in minibuffer and elsewhere + ("s-/" . embark-dwim)) + + :init + ;; Optionally replace the key help with a completing-read interface + (setq prefix-help-command #'embark-prefix-help-command) + + :config + (ha-leader "h K" '("keybindings" . embark-bindings))) +#+end_src + +In [[https://karthinks.com/software/fifteen-ways-to-use-embark/][15 Ways to Use Embark]], Karthik Chikmagalur suggests a nifty macro for integrating Embark with [[Ace Window][Ace Window]]: +#+begin_src emacs-lisp + (use-package embark + :after ace-window + :config + (defmacro my/embark-ace-action (fn) + `(defun ,(intern (concat "my/embark-ace-" (symbol-name fn))) () + (interactive) + (with-demoted-errors "%s" + (require 'ace-window) + (let ((aw-dispatch-always t)) + (aw-switch-to-window (aw-select nil)) + (call-interactively (symbol-function ',fn)))))) + + (defmacro my/embark-split-action (fn split-type) + `(defun ,(intern (concat "my/embark-" + (symbol-name fn) + "-" + (car (last (split-string + (symbol-name split-type) "-"))))) () + (interactive) + (funcall #',split-type) + (call-interactively #',fn))) + + ;; Use the macros to define some helper functions: + (my/embark-ace-action find-file) ; --> my/embark-ace-find-file + (my/embark-ace-action switch-to-buffer) ; --> my/embark-ace-switch-to-buffer + (my/embark-ace-action bookmark-jump) ; --> my/embark-ace-bookmark-jump + (my/embark-split-action find-file split-window-below) ; --> my/embark-find-file-below + (my/embark-split-action find-file split-window-right) ; --> my/embark-find-file-right + (my/embark-split-action switch-to-buffer split-window-below) ; --> my/embark-switch-to-buffer-below + (my/embark-split-action switch-to-buffer split-window-right) ; --> my/embark-switch-to-buffer-right + (my/embark-split-action bookmark-jump split-window-below) ; --> my/embark-bookmark-jump-below + (my/embark-split-action bookmark-jump split-window-right)) ; --> my/embark-bookmark-jump-right +#+end_src + +We can rebind the various =embark-xyz-map= with calls to our macroized functions: +#+begin_src emacs-lisp + (use-package embark + :bind + (:map embark-file-map + ("y" . embark-copy-as-kill) + ("Y" . embark-save-relative-path) + ("W" . nil) + ("w" . my/embark-ace-find-file) + ("2" . my/embark-find-file-below) + ("3" . my/embark-find-file-right) + :map embark-buffer-map + ("y" . embark-copy-as-kill) + ("w" . my/embark-ace-switch-to-buffer) + ("2" . my/embark-switch-to-buffer-below) + ("3" . my/embark-switch-to-buffer-right) + :map embark-file-map + ("y" . embark-copy-as-kill) + ("w" . my/embark-ace-bookmark-jump) + ("2" . my/embark-bookmark-jump-below) + ("3" . my/embark-bookmark-jump-right))) +#+end_src + +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: +#+begin_src emacs-lisp + (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)) +#+end_src + +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. + +#+begin_src emacs-lisp + (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) +#+end_src + + +* Technical Artifacts :noexport: + +Let's =provide= a name so we can =require= this file: + +#+begin_src emacs-lisp :exports none + (provide 'ha-leader) + ;;; ha-leader.el ends here +#+end_src + +#+DESCRIPTION: defining leaders with general + +#+PROPERTY: header-args:sh :tangle no +#+PROPERTY: header-args:emacs-lisp :tangle yes +#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes + +#+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 diff --git a/ha-org.org b/ha-org.org index 2800fc0..b241124 100644 --- a/ha-org.org +++ b/ha-org.org @@ -387,7 +387,8 @@ Since [[https://github.com/BurntSushi/ripgrep][ripgrep]] is pretty fast, I’ll (find-file file) (goto-line line)))) - (add-to-list 'evil-goto-definition-functions 'ha-org-noweb-block-jump) + (when (fboundp 'evil-goto-definition-functions) + (add-to-list 'evil-goto-definition-functions 'ha-org-noweb-block-jump)) #+end_src *** REST Web Services @@ -618,12 +619,13 @@ Global keybindings available to all file buffers: Bindings specific to org files: #+name: org-keybindings #+begin_src emacs-lisp :tangle no - (evil-define-key '(normal motion operator visual) - org-mode-map - "gj" '("next heading" . #'org-forward-heading-same-level) - "gk" '("prev heading" . #'org-backward-heading-same-level) - "gb" '("next block" . #'org-next-block) - "gB" '("prev block" . #'org-previous-block)) + (when (fboundp 'evil-define-key) + (evil-define-key '(normal motion operator visual) + org-mode-map + "gj" '("next heading" . #'org-forward-heading-same-level) + "gk" '("prev heading" . #'org-backward-heading-same-level) + "gb" '("next block" . #'org-next-block) + "gB" '("prev block" . #'org-previous-block))) #+end_src * Supporting Packages ** Exporters @@ -716,7 +718,9 @@ Next, I create a special /auto-correcting function/ that takes advantage of Evil other mistakes are automatically corrected." (interactive "p") (save-excursion - (evil-prev-flyspell-error count) + (when (fboundp 'evil-prev-flyspell-error) + (evil-prev-flyspell-error count)) + (when (looking-at (rx (one-or-more (any alnum "-" "_")))) (let ((start-word (match-beginning 0)) (bad-word (match-string 0))) @@ -747,9 +751,12 @@ For this to work, we use [[https://www.emacswiki.org/emacs/FlySpell][flyspell]] "S" '(:ignore t :which-key "spellcheck") "S s" '("correct last misspell" . ha-fix-last-spelling) "S b" '("check buffer" . flyspell-buffer) - "S c" '("correct word" . flyspell-auto-correct-word) - "S p" '("previous misspell" . evil-prev-flyspell-error) - "S n" '("next misspell" . evil-next-flyspell-error)) + "S c" '("correct word" . flyspell-auto-correct-word)) + + (when (fboundp 'evil-prev-flyspell-error) + (ha-leader :keymaps 'text-mode-map + "S p" '("previous misspell" . evil-prev-flyspell-error) + "S n" '("next misspell" . evil-next-flyspell-error))) ;; Let's use M-TAB for something else ... (define-key flyspell-mode-map (kbd "M-TAB") nil)) diff --git a/ha-programming-elisp.org b/ha-programming-elisp.org index cd38d44..b1799ea 100644 --- a/ha-programming-elisp.org +++ b/ha-programming-elisp.org @@ -112,7 +112,9 @@ While I love packages that add functionality and I don’t have to learn anythin (find-file file) (goto-line line)))) - (add-to-list 'evil-goto-definition-functions 'ha-org-code-block-jump) + (if (boundp 'evil-goto-definition-functions) + (add-to-list 'evil-goto-definition-functions 'ha-org-code-block-jump) + (add-to-list 'xref-backend-functions 'ha-org-code-block-jump)) #+end_src * Editing ** Lispy @@ -123,11 +125,12 @@ My primary use-case is for its refactoring and other unique features. For instan #+begin_src emacs-lisp (use-package lispy :config - (evil-define-key '(normal visual) lispyville-mode-map - ;; Jump to interesting places: - "gf" '("ace paren" . lispy-ace-paren) - "gF" '("ace symbol" . lispy-ace-symbol) - (kbd "M-v") '("mark s-exp" . lispy-mark)) ; Mark entire s-expression + (when (fboundp 'evil-define-key) + (evil-define-key '(normal visual) lispyville-mode-map + ;; Jump to interesting places: + "gf" '("ace paren" . lispy-ace-paren) + "gF" '("ace symbol" . lispy-ace-symbol) + (kbd "M-v") '("mark s-exp" . lispy-mark))) ; Mark entire s-expression (ha-local-leader :keymaps '(emacs-lisp-mode-map lisp-mode-map) "r" '(:ignore t :which-key "refactor") @@ -176,50 +179,52 @@ Use the ~>~ key to /slurp/ in outside objects into the current expression… in *Note:* I used to use the [[https://github.com/luxbock/evil-cleverparens][evil-cleverparens]] project to have similar keybindings but in all programming languages. I found that =lispyville= is a little more reliable, and that I don’t really use these types of code manipulation in my day-job programming languages of Python and YAML. #+begin_src emacs-lisp - (use-package lispyville - :hook ((emacs-lisp-mode lisp-mode) . lispyville-mode)) + (when (fboundp 'evil-define-key) + (use-package lispyville + :hook ((emacs-lisp-mode lisp-mode) . lispyville-mode))) #+end_src Now we need to define additional key movements: #+begin_src emacs-lisp - (use-package lispyville - :config - (lispyville-set-key-theme '(operators atom-movement - commentary slurp/barf-lispy additional-wrap - additional additional-insert)) + (when (fboundp 'evil-define-key) + (use-package lispyville + :config + (lispyville-set-key-theme '(operators atom-movement + commentary slurp/barf-lispy additional-wrap + additional additional-insert)) - (evil-define-key '(normal insert emacs) lispyville-mode-map - (kbd "M-h") 'lispyville-beginning-of-defun - (kbd "M-l") 'lispyville-beginning-of-next-defun - (kbd "M-i") 'lispyville-insert-at-beginning-of-list ; These are useful - (kbd "M-a") 'lispyville-insert-at-end-of-list ; and I want to use - (kbd "M-o") 'lispyville-open-below-list ; these in insert - (kbd "M-O") 'lispyville-open-above-list ; or Emacs state. + (evil-define-key '(normal insert emacs) lispyville-mode-map + (kbd "M-h") 'lispyville-beginning-of-defun + (kbd "M-l") 'lispyville-beginning-of-next-defun + (kbd "M-i") 'lispyville-insert-at-beginning-of-list ; These are useful + (kbd "M-a") 'lispyville-insert-at-end-of-list ; and I want to use + (kbd "M-o") 'lispyville-open-below-list ; these in insert + (kbd "M-O") 'lispyville-open-above-list ; or Emacs state. - ;; The c-w theme is VI-specific. I still use Emacs' M-Delete: - (kbd "M-DEL") 'lispyville-delete-backward-word) + ;; The c-w theme is VI-specific. I still use Emacs' M-Delete: + (kbd "M-DEL") 'lispyville-delete-backward-word) - ;; Sentence and paragraph movement doesn't make sense in a Lisp world, - ;; so I redefine these based on my own personal expectations: - (evil-define-key 'normal lispyville-mode-map - "H" 'lispyville-backward-sexp-begin - (kbd "M-H") 'lispyville-backward-sexp-end - "L" 'lispyville-forward-sexp-begin - (kbd "M-L") 'lispyville-forward-sexp-end - "(" 'lispyville-previous-opening - ")" 'lispyville-next-closing - "{" 'lispyville-backward-up-list - "}" 'lispyville-next-opening + ;; Sentence and paragraph movement doesn't make sense in a Lisp world, + ;; so I redefine these based on my own personal expectations: + (evil-define-key 'normal lispyville-mode-map + "H" 'lispyville-backward-sexp-begin + (kbd "M-H") 'lispyville-backward-sexp-end + "L" 'lispyville-forward-sexp-begin + (kbd "M-L") 'lispyville-forward-sexp-end + "(" 'lispyville-previous-opening + ")" 'lispyville-next-closing + "{" 'lispyville-backward-up-list + "}" 'lispyville-next-opening - "[ f" 'lispyville-beginning-of-defun - "] f" 'lispyville-beginning-of-next-defun - "] F" 'lispyville-end-of-next-defun) + "[ f" 'lispyville-beginning-of-defun + "] f" 'lispyville-beginning-of-next-defun + "] F" 'lispyville-end-of-next-defun) - ;; Visually high-light a region, just hit `(' to wrap it in parens. - ;; Without smartparens, we need to insert a pair of delimiters: - (evil-define-key '(visual insert emacs) lispyville-mode-map "(" 'lispy-parens) - (evil-define-key '(visual insert emacs) lispyville-mode-map "[" 'lispy-brackets) - (evil-define-key '(visual insert emacs) lispyville-mode-map "{" 'lispy-braces)) + ;; Visually high-light a region, just hit `(' to wrap it in parens. + ;; Without smartparens, we need to insert a pair of delimiters: + (evil-define-key '(visual insert emacs) lispyville-mode-map "(" 'lispy-parens) + (evil-define-key '(visual insert emacs) lispyville-mode-map "[" 'lispy-brackets) + (evil-define-key '(visual insert emacs) lispyville-mode-map "{" 'lispy-braces))) #+end_src Instead of converting /all keybindings/, the project supplies /key themes/ to grab specific keybinding groups. diff --git a/ha-programming-python.org b/ha-programming-python.org index 893c9bd..1c1c852 100644 --- a/ha-programming-python.org +++ b/ha-programming-python.org @@ -87,10 +87,9 @@ use_python() { ** Editing Python Code Let’s integrate this [[https://github.com/wbolster/evil-text-object-python][Python support for evil-text-object]] project: #+begin_src emacs-lisp - (use-package evil-text-object-python - :hook (python-mode . evil-text-object-python-add-bindings)) - - + (when (fboundp 'evil-define-text-object) + (use-package evil-text-object-python + :hook (python-mode . evil-text-object-python-add-bindings))) #+end_src This allows me to delete a Python “block” using ~dal~. ** Docker Environment diff --git a/ha-programming.org b/ha-programming.org index d4d3e71..0b718c0 100644 --- a/ha-programming.org +++ b/ha-programming.org @@ -189,9 +189,13 @@ We need to make sure we keep the [[https://github.com/Fuco1/smartparens][smartpa *** Move by Functions The =mark-paragraph= and =downcase-word= isn’t very useful in a programming context, and makes more sense to use them to jump around function-by-function: #+begin_src emacs-lisp - (evil-define-key '(normal insert emacs) prog-mode-map - (kbd "M-h") 'beginning-of-defun - (kbd "M-l") 'beginning-of-next-defun) + (global-set-key (kbd "M-h") 'beginning-of-defun) + (global-set-key (kbd "M-l") 'beginning-of-next-defun) + + (when (fboundp 'evil-define-key) + (evil-define-key '(normal insert emacs) prog-mode-map + (kbd "M-h") 'beginning-of-defun + (kbd "M-l") 'beginning-of-next-defun)) #+end_src But one of those functions doesn’t exist: #+begin_src emacs-lisp @@ -418,7 +422,7 @@ Mickey’s interface is the [[help:combobulate][combobulate]] function (or ~C-c *** Evil Text Object from Tree Sitter With Emacs version 29, we get a better approach to parsing languages, and this means that our [[https://github.com/nvim-treesitter/nvim-treesitter-textobjects#built-in-textobjects][text objects]] can be better too with the [[https://github.com/meain/evil-textobj-tree-sitter][evil-textobj-tree-sitter project]]: #+begin_src emacs-lisp :tangle no - (when (treesit-available-p) + (when (and (treesit-available-p) (fboundp 'evil-define-text-object)) (use-package evil-textobj-tree-sitter :config ;; We need to bind keys to the text objects found at: @@ -634,10 +638,11 @@ This creates both an =iedit= and =iedit-insert= states. Calling ~Escape~ from =i To use, highlight a region with ~v~, and continue to hit ~v~ until you’ve selected the variable/symbol, and then type ~e~. Or, highlight normally, e.g. ~v i o~, and hit ~E~: #+begin_src emacs-lisp - (use-package evil-iedit-state - :after iedit - :general - (:states 'visual "E" '("iedit" . evil-iedit-state/iedit-mode))) + (when (fboundp 'evil-mode) + (use-package evil-iedit-state + :after iedit + :general + (:states 'visual "E" '("iedit" . evil-iedit-state/iedit-mode)))) #+end_src The =iedit-insert= state is pretty much /regular/ =insert= state, so the interesting keys are in =iedit= state: @@ -959,7 +964,10 @@ However, what about taking a buffer of JSON data, and whittling it down with [[h (re-search-forward "{") (goto-char (match-beginning 0))) (setq s (point)) - (evil-jump-item) + ;; Jump forward using the evil-jump-item ... change this to one + ;; of the functions in thing-at-point? + (when (fboundp 'evil-jump-item) + (evil-jump-item)) (setq e (1+ (point)))) ;; (narrow-to-region s e) (shell-command-on-region s e (concat "jq " query) nil t "*jq errors*")))) diff --git a/ha-remoting.org b/ha-remoting.org index 148a78c..dae346a 100644 --- a/ha-remoting.org +++ b/ha-remoting.org @@ -124,18 +124,22 @@ VTerm has an issue (at least for me) with ~M-Backspace~ not deleting the previou (lambda () (interactive) (vterm-send-key (kbd "C-w"))))) ;; Enter copy mode? Go to Evil's normal state to move around: - (advice-add 'vterm-copy-mode :after 'evil-normal-state) + (when (fboundp 'evil-normal-state) + (advice-add 'vterm-copy-mode :after 'evil-normal-state)) :hook (vterm-mode . (lambda () - (setq-local evil-insert-state-cursor 'box) + (when (boundp 'evil-insert-state-cursor) + (setq-local evil-insert-state-cursor 'box)) (setq-local show-paren-mode nil) (setf truncate-lines nil vterm-use-vterm-prompt-detection-method nil term-prompt-regexp "^.* $ ") (flycheck-mode -1) - (yas-minor-mode -1) - (evil-insert-state)))) + (yas-minor-mode -1) ;; This actually code be interesting, but... + + (when (fboundp 'evil-insert-state) + (evil-insert-state))))) #+end_src The advantage of running terminals in Emacs is the ability to copy text without a mouse. For that, hit ~C-c C-t~ to enter a special copy-mode. If I go into this mode, I might as well also go into normal mode to move the cursor. To exit the copy-mode (and copy the selected text to the clipboard), hit ~Return~.