ca4799e524
Seems that the leader has a bug when it comes to local mode maps, and the major-mode hydra is an attractive alternative. Granted, I do feel the need to group things for the tabular view, but that isn't a bad thing. Need to take a pass at my more-complex code in my programming stuff.
964 lines
42 KiB
Org Mode
964 lines
42 KiB
Org Mode
#+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 <http://gitlab.com/howardabrams>
|
||
;; 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 "<f13>")
|
||
|
||
(general-create-definer ha-local-leader
|
||
:states '(normal visual motion)
|
||
:global-prefix "<f17>")
|
||
|
||
(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 <up>") nil
|
||
(kbd "g <down>") nil
|
||
(kbd "g <left>") nil
|
||
(kbd "g <right>") nil
|
||
(kbd "g <home>") nil
|
||
(kbd "g <end>") 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 <left>") '("scroll left" . evil-scroll-column-left)
|
||
(kbd "z <right>") '("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 <return>") 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)
|
||
"<escape>" '(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 <escape>" '(keyboard-escape-quit :which-key t)
|
||
"m" '(:ignore t :which-key "mode")
|
||
"m <escape>" '(keyboard-escape-quit :which-key t)
|
||
"o" '(:ignore t :which-key "org/open")
|
||
"o <escape>" '(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 <escape>" '(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 <escape>" '(keyboard-escape-quit :which-key t)
|
||
"h h f" '("features" . ha-hamacs-features)
|
||
"h h e" '("edit" . ha-hamacs-find-file)
|
||
"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)))
|
||
(relative (f-relative filename (or nil (project-root (project-current))))))
|
||
(progn
|
||
(kill-new relative)
|
||
(message "Copied path to clipboard: %s" relative))
|
||
(message "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))
|
||
(project-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 <escape>" '(keyboard-escape-quit :which-key t)
|
||
"f a" '("load any" . find-file)
|
||
"f f" '("load" . project-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 <escape>" '(keyboard-escape-quit :which-key t)
|
||
"b B" '("switch" . project-switch-to-buffer)
|
||
"b o" '("other" . project-switch-buffer-to-other-window)
|
||
"b O" '("switch" . switch-to-buffer-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 use [[https://codeberg.org/ideasman42/emacs-bookmark-in-project][bookmark-in-project]] package to make a =project=-specific bookmarks and use that to jump to only bookmarks in the current project.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package bookmark-in-project
|
||
:config
|
||
(ha-leader
|
||
;; Set or delete a bookmark associated with project:
|
||
"b m" '("set proj mark" . bookmark-in-project-toggle)
|
||
"b M" '("set global mark" . bookmark-set)
|
||
"b X" '("delete mark" . bookmark-delete)
|
||
"b g" '("goto proj mark" . bookmark-in-project-jump)
|
||
"b <down>" '("next mark" . bookmark-in-project-jump-next)
|
||
"b <up>" '("next mark" . bookmark-in-project-jump-previous)))
|
||
#+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 <escape>" '(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 (project-root (project-current)))))
|
||
#+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 'project-find-file))
|
||
(:buffer (call-interactively 'project-switch-to-buffer))
|
||
(:term (ha-shell (project-root (project-current))))))
|
||
#+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 <escape>" '(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)
|
||
"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 <escape>" '(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 <escape>" '(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
|
||
** 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
|