1145 lines
48 KiB
Org Mode
1145 lines
48 KiB
Org Mode
#+TITLE: General Emacs Configuration
|
||
#+AUTHOR: Howard X. Abrams
|
||
#+EMAIL: howard.abrams@gmail.com
|
||
#+DATE: 2020-09-10
|
||
#+FILETAGS: :emacs:
|
||
|
||
A literate programming file for configuring Emacs.
|
||
|
||
#+BEGIN_SRC emacs-lisp :exports none
|
||
;;; ha-config.org --- A literate programming file for configuring Emacs. -*- lexical-binding: t; -*-
|
||
;;
|
||
;; Copyright (C) 2020-2021 Howard X. Abrams
|
||
;;
|
||
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||
;; Maintainer: Howard X. Abrams <howard.abrams@gmail.com>
|
||
;; Created: September 10, 2020
|
||
;;
|
||
;; This file is not part of GNU Emacs.
|
||
;;
|
||
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
||
;; ~/other/hamacs/ha-config.org
|
||
;; Using `find-file-at-point', and tangle the file to recreate this one .
|
||
;;
|
||
;;; Code:
|
||
#+END_SRC
|
||
* Introduction
|
||
|
||
The following are general variable settings, and doesn't really belong to any specific purpose.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq user-full-name "Howard X. Abrams"
|
||
user-mail-address "howard.abrams@gmail.com")
|
||
#+END_SRC
|
||
|
||
New way to display line-numbers. I set mine to =relative= so that I can easily jump up and down by that value. Set this to =nil= to turn off, or =t= to be absolute.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq display-line-numbers t)
|
||
(setq display-line-numbers-type 'relative)
|
||
#+END_SRC
|
||
|
||
As [[https://tecosaur.github.io/emacs-config/config.html][tec wrote]], I want to use =~/.authsource.gpg= as I don’t want to accidentaly purge this file cleaning =~/.emacs.d=, and let's cache as much as possible, as my home machine is pretty safe, and my laptop is shutdown a lot.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq auth-sources '("~/.authinfo.gpg")
|
||
auth-source-cache-expiry nil)
|
||
#+END_SRC
|
||
|
||
More settings:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq truncate-string-ellipsis "…" ; Unicode ellispis are nicer than "..."
|
||
auto-save-default t)
|
||
#+END_SRC
|
||
|
||
And some Mac-specific settings:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(when (equal system-type 'darwin)
|
||
(setq mac-option-modifier 'meta)
|
||
(setq mac-command-modifier 'super)
|
||
(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
|
||
(add-to-list 'default-frame-alist '(ns-appearance . dark)))
|
||
#+END_SRC
|
||
* Support Packages
|
||
** Piper
|
||
|
||
Rewriting my shell scripts in Emacs Lisp uses my [[https://gitlab.com/howardabrams/emacs-piper][emacs-piper project]], and this code spills into my configuration code, so let's load it now:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package piper
|
||
:straight (:type git :protocol ssh :host gitlab :repo "howardabrams/emacs-piper")
|
||
:commands shell-command-to-list ; I use this function quite a bit
|
||
:bind (:map evil-normal-state-map
|
||
("|" . piper-user-interface)))
|
||
#+END_SRC
|
||
** Yet Another Snippet System (YASnippets)
|
||
Using [[https://github.com/joaotavora/yasnippet][yasnippet]] to convert templates into text:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package yasnippet
|
||
:config
|
||
(add-to-list 'yas-snippet-dirs (expand-file-name "snippets" user-emacs-directory))
|
||
(yas-global-mode +1))
|
||
#+END_SRC
|
||
Check out [[http://joaotavora.github.io/yasnippet/][the documentation]] for writing them.
|
||
|
||
Seems the best [[https://github.com/hlissner/doom-snippets][collection of snippets]] is what Henrik Lissner has made for Doom (otherwise, we should use [[http://github.com/AndreaCrotti/yasnippet-snippets][yasnippet-snippets]] package):
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package doom-snippets
|
||
:after yasnippet
|
||
:straight (doom-snippets :type git :host github :repo "hlissner/doom-snippets" :files ("*.el" "*")))
|
||
#+END_SRC
|
||
*Note:* Including his snippets also includes some [[https://github.com/hlissner/doom-snippets#snippets-api][helper functions]] and other features.
|
||
** Auto Insert Templates
|
||
The [[https://www.emacswiki.org/emacs/AutoInsertMode][auto-insert]] feature is a wee bit complicated. All I want is to associate a filename regular expression with a YASnippet template. I'm stealing some ideas from Henrik Lissner's [[https://github.com/hlissner/doom-emacs/blob/develop/modules/editor/file-templates/autoload.el][set-file-template!]] macro, but maybe simpler?
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package autoinsert
|
||
:init
|
||
(setq auto-insert-directory (expand-file-name "templates" user-emacs-directory))
|
||
;; Don't want to be prompted before insertion:
|
||
(setq auto-insert-query nil)
|
||
|
||
(add-hook 'find-file-hook 'auto-insert)
|
||
(auto-insert-mode t))
|
||
#+END_SRC
|
||
However, auto insertion requires entering data for particular fields, and for that Yasnippet is better, so in this case, we combine them:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defun ha-autoinsert-yas-expand()
|
||
"Replace text in yasnippet template."
|
||
(yas-expand-snippet (buffer-string) (point-min) (point-max)))
|
||
#+END_SRC
|
||
|
||
And since I'll be associating snippets with new files all over my configuration, let's make a helper function:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defun ha-auto-insert-file (filename-re snippet-name)
|
||
"Autofill file buffer matching FILENAME-RE regular expression.
|
||
The contents inserted from the YAS SNIPPET-NAME."
|
||
;; The define-auto-insert takes a regular expression and an ACTION:
|
||
;; ACTION may also be a vector containing several successive single
|
||
;; actions as described above, e.g. ["header.insert" author-update].
|
||
(define-auto-insert filename-re
|
||
(vector snippet-name 'ha-autoinsert-yas-expand)))
|
||
#+END_SRC
|
||
|
||
As an example of its use, any Org files loaded in /this project/ should insert my config file:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(ha-auto-insert-file (rx "hamacs/" (one-or-more any) ".org" eol) "hamacs-config")
|
||
#+END_SRC
|
||
** Request System
|
||
The above code (and other stuff) needs the [[https://github.com/tkf/emacs-request][request]] package:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package request
|
||
:init
|
||
(defvar ha-dad-joke nil "Holds the latest dad joke.")
|
||
|
||
:config
|
||
(defun ha-dad-joke ()
|
||
"Display a random dad joke."
|
||
(interactive)
|
||
(message (ha--dad-joke)))
|
||
|
||
(defun ha--dad-joke ()
|
||
"Return string containing a dad joke from www.icanhazdadjoke.com."
|
||
(setq ha-dad-joke nil) ; Clear out old joke
|
||
(ha--dad-joke-request)
|
||
(ha--dad-joke-wait))
|
||
|
||
(defun ha--dad-joke-wait ()
|
||
(while (not ha-dad-joke)
|
||
(sit-for 1))
|
||
(unless ha-dad-joke
|
||
(ha--dad-joke-wait))
|
||
ha-dad-joke)
|
||
|
||
(defun ha--dad-joke-request ()
|
||
(request "https://icanhazdadjoke.com"
|
||
:sync t
|
||
:complete (cl-function
|
||
(lambda (&key data &allow-other-keys)
|
||
(setq ha-dad-joke data))))))
|
||
#+END_SRC
|
||
*** Dad Jokes!
|
||
The /critical part/ here, is the [[https://icanhazdadjoke.com/][Dad Joke]] function, which is just a =curl= call:
|
||
#+BEGIN_SRC sh
|
||
curl -sH "Accept: text/plain" https://icanhazdadjoke.com/
|
||
#+END_SRC
|
||
For this, I use the =request= package, which is /asynchronous/
|
||
#+BEGIN_SRC emacs-lisp
|
||
#+END_SRC
|
||
|
||
* Configuration Changes
|
||
** Initial Settings and UI
|
||
Let's turn off the menu and other things:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(tool-bar-mode -1)
|
||
(scroll-bar-mode -1)
|
||
(horizontal-scroll-bar-mode -1)
|
||
(setq visible-bell 1)
|
||
#+END_SRC
|
||
I dislike forgetting to trim trailing white-space:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(add-hook 'before-save-hook 'delete-trailing-whitespace)
|
||
#+END_SRC
|
||
|
||
I like being able to enable local variables in =.dir-local.el= files:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq enable-local-variables t)
|
||
#+END_SRC
|
||
** Completing Read User Interface
|
||
After using Ivy, I am going the route of a =completing-read= interface that extends the original Emacs API, as opposed to implementing backend-engines or complete replacements.
|
||
*** Vertico
|
||
The [[https://github.com/minad/vertico][vertico]] package puts the completing read in a vertical format, and seems to fit the bill. It seems to be similar to [[https://github.com/raxod502/selectrum#vertico][Selectrum]], and I'll use it (at least for a while), however, I may be jumping between the two.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package vertico
|
||
:config (vertico-mode))
|
||
#+END_SRC
|
||
My only issue with using Vertico with =find-file= is that I really like having the Return key insert the directory at point, and not open =dired=. Seems like this is addressed with this extension /installed with Vertico/:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package vertico-directory
|
||
:straight (el-patch :files ("~/.emacs.d/straight/repos/vertico/extensions/vertico-directory.el"))
|
||
;; More convenient directory navigation commands
|
||
:bind (:map vertico-map
|
||
("RET" . vertico-directory-enter)
|
||
; ("DEL" . vertico-directory-delete-word)
|
||
("M-RET" . minibuffer-force-complete-and-exit)
|
||
("M-TAB" . minibuffer-complete))
|
||
;; Tidy shadowed file names
|
||
:hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
|
||
#+END_SRC
|
||
*** Selectrum
|
||
While I've been /dabbling/ in some of the alternates for =completing-read=, after watching [[https://youtu.be/lfgQC540sNM][Rari Comninos' overview]], I decided to try [[https://github.com/raxod502/selectrum][selectrum]] for better narrowing and selecting (instead of Ivy) and [[https://github.com/raxod502/prescient.el][prescient]] to order the selection from history.
|
||
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(use-package selectrum
|
||
:config
|
||
;; Optional performance optimization by highlighting only the visible candidates.
|
||
(setq selectrum-highlight-candidates-function #'orderless-highlight-matches
|
||
orderless-skip-highlighting (lambda () selectrum-is-active))
|
||
(selectrum-mode +1))
|
||
|
||
(use-package selectrum-prescient
|
||
:init
|
||
(setq selectrum-prescient-enable-filtering nil ; Use prescient on top of orderless
|
||
selectrum-prescient-enable-sorting t)
|
||
:config
|
||
(selectrum-prescient-mode +1)
|
||
(prescient-persist-mode +1))
|
||
#+END_SRC
|
||
Keybindings:
|
||
- ~RET~ :: Select the candidate (obviously), but if directory, opens =dired=
|
||
- ~M-# RET~ :: Select =#= candidate (where # is a number 0-9)
|
||
- ~C-j~ :: Submit what you've typed (even if it would select something else)
|
||
- ~TAB~ :: Move into a directory (for =find-file=)
|
||
- ~M-w~ :: Copy the candidate to the kill ring (clipboard)
|
||
- ~,~ :: Select multiple candidates
|
||
- ~M-BKSP~ :: To go up a directory
|
||
- ~M-p~ / ~M-n~ / ~M-r~ :: Select/Search the selection history
|
||
|
||
Wouldn't it be swell if we could quickly select one of the items visually shown.
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(define-key selectrum-minibuffer-map (kbd "C-l") 'selectrum-quick-select)
|
||
#+END_SRC
|
||
*** Orderless
|
||
While the space can be use to separate words (acting a bit like a =.*= regular expression), the
|
||
[[https://github.com/oantolin/orderless][orderless]] project allows those words to be in any order.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package orderless
|
||
:init
|
||
(setq completion-styles '(substring orderless)
|
||
completion-category-defaults nil
|
||
completion-category-overrides '((file (styles partial-completion)))))
|
||
#+END_SRC
|
||
*Note:* Multiple files can be opened at once with =find-file= if you enter a wildcard. We may also give the =initials= completion style a try.
|
||
|
||
*** Savehist
|
||
Persist history over Emacs restarts using the built-in [[https://www.emacswiki.org/emacs/SaveHist][savehist]] project. Since both Vertico and Selectrum sorts by history position, this should make the choice /smarter/ with time.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package savehist
|
||
:init
|
||
(savehist-mode))
|
||
#+END_SRC
|
||
*** Marginalia
|
||
The [[https://github.com/minad/marginalia][marginalia]] package gives a preview of =M-x= functions with a one line description, extra information when selecting files, etc. Nice enhancement without learning any new keybindings.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
;; Enable richer annotations using the Marginalia package
|
||
(use-package marginalia
|
||
:init
|
||
(setq marginalia-annotators-heavy t)
|
||
:config
|
||
(marginalia-mode))
|
||
#+END_SRC
|
||
* Key Bindings
|
||
To begin my binding changes, let's turn on [[https://github.com/justbur/emacs-which-key][which-key]]:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package which-key
|
||
:init (setq which-key-popup-type 'minibuffer)
|
||
:config (which-key-mode))
|
||
#+END_SRC
|
||
*** Undo-Fu
|
||
Configure the Evil session to use [[https://gitlab.com/ideasman42/emacs-undo-fu][undo-fu]], as this project is now maintained.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package undo-fu
|
||
:config
|
||
(global-unset-key (kbd "s-z"))
|
||
(global-set-key (kbd "s-z") 'undo-fu-only-undo)
|
||
(global-set-key (kbd "s-S-z") 'undo-fu-only-redo))
|
||
#+END_SRC
|
||
*** Expand Region
|
||
Magnar Sveen's [[https://github.com/magnars/expand-region.el][expand-region]] project allows me to hit ~v~ repeatedly, having the selection grow by syntactical units.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package expand-region
|
||
:bind ("C-=" . er/expand-region))
|
||
#+END_SRC
|
||
** Evil-Specific Keybindings
|
||
Can we change Evil at this point? Some tips:
|
||
- [[https://github.com/noctuid/evil-guide]]
|
||
- [[https://nathantypanski.com/blog/2014-08-03-a-vim-like-emacs-config.html]]
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package evil
|
||
:init
|
||
(setq evil-undo-system 'undo-fu
|
||
evil-disable-insert-state-bindings t
|
||
evil-want-keybinding nil
|
||
evil-want-integration t
|
||
evil-escape-key-sequence "fd"
|
||
evil-escape-unordered-key-sequence t)
|
||
|
||
:config
|
||
(add-to-list 'evil-normal-state-modes 'shell-mode)
|
||
(add-to-list 'evil-emacs-state-modes 'term-mode)
|
||
(add-to-list 'evil-emacs-state-modes 'elfeed-search-mode)
|
||
(add-to-list 'evil-emacs-state-modes 'elfeed-show-mode)
|
||
|
||
;; Use escape to get out of visual mode, eh?
|
||
(evil-define-key 'visual global-map (kbd "v") 'er/expand-region)
|
||
|
||
(evil-mode))
|
||
#+END_SRC
|
||
|
||
Using the key-chord project allows me to make Escape be on two key combo presses on both sides of my keyboard:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package key-chord
|
||
:config
|
||
(key-chord-mode t)
|
||
(key-chord-define-global "fd" 'evil-normal-state)
|
||
(key-chord-define-global "jk" 'evil-normal-state)
|
||
(key-chord-define-global "JK" 'evil-normal-state))
|
||
#+END_SRC
|
||
** Evil Collection
|
||
Dropping into Emacs state is better than pure Evil state for applications, however, [[https://github.com/emacs-evil/evil-collection][the evil-collection package]] creates a hybrid between the two, that I like.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package evil-collection
|
||
:after evil
|
||
:config
|
||
(evil-collection-init))
|
||
#+END_SRC
|
||
|
||
Do I want to specify the list of modes to change for =evil-collection-init=, e.g.
|
||
#+BEGIN_SRC emacs-lisp :tangle no :eval no
|
||
'(eww magit dired notmuch term wdired)
|
||
#+END_SRC
|
||
** 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
|
||
(general-evil-setup t)
|
||
(general-create-definer ha-leader
|
||
:keymaps '(normal visual)
|
||
:prefix "SPC"
|
||
:non-normal-prefix "M-SPC"
|
||
:global-prefix "<f13>"))
|
||
#+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)
|
||
"." '("repeat" . repeat)
|
||
"!" 'shell-command
|
||
"X" 'org-capture
|
||
"L" 'org-store-link
|
||
"RET" 'bookmark-jump
|
||
"a" '(:ignore t :which-key "apps")
|
||
"o" '(:ignore t :which-key "org/open")
|
||
"m" '(:ignore t :which-key "mode"))
|
||
#+END_SRC
|
||
And ways to stop the system:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(ha-leader
|
||
"q" '(:ignore t :which-key "quit/session")
|
||
"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
|
||
*** File Operations
|
||
Obviously, =find-file= is still my bread and butter, but I do 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.
|
||
If ROOT is given, they 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
|
||
|
||
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 f" '("load" . find-file)
|
||
"f s" '("save" . save-buffer)
|
||
"f S" '("save as" . write-buffer)
|
||
"f SPC" '("project" . projectile-find-file)
|
||
"f r" '("recent" . recentf-open-files)
|
||
"f c" '("copy" . copy-file)
|
||
"f R" '("rename" . rename-file)
|
||
"f D" '("delete" . delete-file)
|
||
"f y" '("yank path" . ha/yank-buffer-path)
|
||
"f Y" '("yank path from project" . ha/yank-project-buffer-path)
|
||
"f d" '("dired" . dired))
|
||
#+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
|
||
And the collection of useful operations:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(ha-leader
|
||
"b" '(:ignore t :which-key "buffers")
|
||
"b b" '("switch" . persp-switch-to-buffer)
|
||
"b B" '("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)
|
||
|
||
;; And double up on the bookmarks:
|
||
"b m" '("set bookmark" . bookmark-set)
|
||
"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 a" '("abbrev" . abbrev-mode)
|
||
"t d" '("debug" . toggle-debug-on-error)
|
||
"t f" '("auto-fill" . auto-fill-mode)
|
||
"t l" '("line numbers" . display-line-numbers-mode)
|
||
"t r" '("relative lines" . ha-toggle-relative-line-numbers)
|
||
"t t" '("truncate" . toggle-truncate-lines)
|
||
"t v" '("visual" . visual-line-mode)
|
||
"t w" '("whitespace" . whitespace-mode))
|
||
#+END_SRC
|
||
|
||
Really? We can't automatically toggle between relative and absolute line numbers?
|
||
#+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
|
||
*** 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
|
||
Use the [[https://github.com/abo-abo/ace-window][ace-window]] project to jump to any window you see:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package ace-window)
|
||
#+END_SRC
|
||
This package, bound to ~SPC w w~, also allows operations specified before choosing the window:
|
||
- ~x~ - delete window
|
||
- ~m~ - swap windows
|
||
- ~M~ - move window
|
||
- ~c~ - copy window
|
||
- ~j~ - select buffer
|
||
- ~n~ - select the previous window
|
||
- ~u~ - select buffer in the other window
|
||
- ~c~ - split window fairly, either vertically or horizontally
|
||
- ~v~ - split window vertically
|
||
- ~b~ - split window horizontally
|
||
- ~o~ - maximize current window
|
||
- ~?~ - show these command bindings
|
||
Keep in mind, these shortcuts only work with lots of windows open. For instance, ~SPC w w x 3~ closes the "3" window.
|
||
|
||
To jump to a window even quicker, use the [[https://github.com/deb0ch/emacs-winum][winum package]]:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package winum
|
||
:config
|
||
(winum-mode +1))
|
||
#+END_SRC
|
||
This is nice since the window numbers are always present on a Doom modeline, however, they order the window numbers /differently/ than =ace-window=. Let's see which I end up liking better.
|
||
|
||
The ~0~ key/window should be always associated with a project-specific tree window:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(add-to-list 'winum-assign-functions
|
||
(lambda ()
|
||
(when (string-match-p (buffer-name) ".*\\*NeoTree\\*.*") 10)))
|
||
#+END_SRC
|
||
|
||
Let's try this out with a Hydra since some commands (enlarge window), I want to repeatedly call. 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 _n_: new _^_: taller (t) _z_: Swap _+_: text larger
|
||
_c_: cycle _d_: delete _V_: shorter (T) _u_: undo _-_: text smaller
|
||
_j_: go up _=_: balance _>_: wider _U_: undo+ _F_: font larger
|
||
_k_: down _m_: maximize _<_: narrower _r_: redo _f_: font smaller
|
||
_h_: left _s_: h-split _e_: balanced _R_: redo+ _0_: toggle neotree
|
||
_l_: right _v_: v-split _o_: choose by number (also 1-9)
|
||
"
|
||
("w" ace-window)
|
||
("c" other-window)
|
||
("=" balance-windows)
|
||
("m" delete-other-windows)
|
||
("d" delete-window)
|
||
("D" ace-delete-window)
|
||
|
||
("z" ace-window-swap)
|
||
("u" winner-undo)
|
||
("U" winner-undo :color pink)
|
||
("C-r" winner-redo)
|
||
("r" winner-redo)
|
||
("R" winner-redo :color pink)
|
||
|
||
("n" evil-window-new)
|
||
("j" evil-window-up)
|
||
("k" evil-window-down)
|
||
("h" evil-window-left)
|
||
("l" evil-window-right)
|
||
|
||
("s" evil-window-split)
|
||
("v" evil-window-vsplit)
|
||
|
||
("F" font-size-increase :color pink)
|
||
("f" font-size-decrease :color pink)
|
||
("+" text-scale-increase :color pink)
|
||
("=" text-scale-increase :color pink)
|
||
("-" text-scale-decrease :color pink)
|
||
("^" evil-window-increase-height :color pink)
|
||
("V" evil-window-decrease-height :color pink)
|
||
("t" evil-window-increase-height :color pink)
|
||
("T" evil-window-decrease-height :color pink)
|
||
(">" evil-window-increase-width :color pink)
|
||
("<" evil-window-decrease-width :color pink)
|
||
("e" balance-windows)
|
||
|
||
("o" winum-select-window-by-number)
|
||
("1" winum-select-window-1)
|
||
("2" winum-select-window-2)
|
||
("3" winum-select-window-3)
|
||
("4" winum-select-window-4)
|
||
("5" winum-select-window-5)
|
||
("6" winum-select-window-6)
|
||
("7" winum-select-window-7)
|
||
("8" winum-select-window-8)
|
||
("9" winum-select-window-9)
|
||
("0" neotree-toggle)
|
||
|
||
;; Extra bindings:
|
||
("t" evil-window-increase-height :color pink)
|
||
("T" evil-window-decrease-height :color pink)
|
||
("." evil-window-increase-width :color pink)
|
||
("," evil-window-decrease-width :color pink)
|
||
("q" nil :color blue)))
|
||
|
||
(ha-leader "w" '("windows" . hydra-window-resize/body))
|
||
#+END_SRC
|
||
*** Search Operations
|
||
Ways to search for information goes under the ~s~ key. This primarily depends on the [[https://github.com/dajva/rg.el][rg]] package, which builds on the internal =grep= system, and creates a =*rg*= window with =compilation= mode, so ~C-j~ and ~C-k~ will move and show the results by loading those files.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package rg
|
||
:config
|
||
(ha-leader
|
||
"s" '(:ignore t :which-key "search")
|
||
"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-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
|
||
*** Text Operations
|
||
Stealing much of this from Spacemacs.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(ha-leader
|
||
"x" '(:ignore t :which-key "text")
|
||
"x a" '("align" . align-regexp)
|
||
"x q" '("fill paragraph" . fill-paragraph)
|
||
"x p" '("unfill paragraph" . unfill-paragraph))
|
||
#+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.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(ha-leader
|
||
"h" '(:ignore t :which-key "help")
|
||
"h e" '("errors" . view-echo-area-messages)
|
||
"h f" '("function" . describe-function)
|
||
"h v" '("variable" . describe-variable)
|
||
"h k" '("key binding" . describe-key)
|
||
"h B" '("embark" . embark-bindings)
|
||
"h i" '("info" . info))
|
||
#+END_SRC
|
||
|
||
*** Consult Enhancements
|
||
The [[https://github.com/minad/consult][consult]] package is a replacement for selecting buffers and other /speciality functions/, similar to the [[https://oremacs.com/2015/04/09/counsel-completion/][Ivy's counsel completion]] project. I think I may be adding it sparingly, as personally, I read files and buffers based on the selected /project/.
|
||
|
||
The pattern is to add the /consult/ functions to my standard general leader organization, but they will all end with ~TAB~ (unique, easy and consistent).
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package consult
|
||
:config
|
||
(ha-leader
|
||
"b TAB" '("consult buffer" . consult-buffer)
|
||
"b S-TAB" '("consult buffer in window" . consult-buffer-other-window)
|
||
"s TAB" '("consult search" . consult-ripgrep)
|
||
"f TAB" '("consult file" . consult-file)))
|
||
#+END_SRC
|
||
*** Embark
|
||
The [[https://github.com/oantolin/embark/][embark]] project offers /actions/ on /targets/, however, I'm primarily thinking of acting on selected items in the minibuffer, however, they actually act anywhere. Consequently, 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))
|
||
#+END_SRC
|
||
Consult users will also want the embark-consult package.
|
||
#+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
|
||
** Evil Snipe
|
||
|
||
Doom introduced me to [[https://github.com/hlissner/evil-snipe][evil-snipe]] which is similar to =f= and =t=, but does two characters, and can, when configured, search more than the current line:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package evil-snipe
|
||
:after evil
|
||
:init
|
||
(setq evil-snipe-scope 'visible)
|
||
:config
|
||
(evil-define-key '(normal motion operator visual)
|
||
"s" #'evil-snipe-s
|
||
"S" #'evil-snipe-S)
|
||
(evil-snipe-mode +1))
|
||
#+END_SRC
|
||
|
||
It highlights all potential matches, use ~;~ to skip to the next match, and ~,~ to jump back.
|
||
** Evil Surround
|
||
I like both [[https://github.com/emacs-evil/evil-surround][evil-surround]] and Henrik's [[https://github.com/hlissner/evil-snipe][evil-snipe]], however, they both start with ~s~, and conflict, and getting them to work together means I have to remember when does ~s~ call sniper and when calls surround. As an original Emacs person, I am not bound by that key history, but I do need them consistent:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package evil-surround
|
||
:after evil-snipe
|
||
:config
|
||
(dolist (state '(normal motion operator visual))
|
||
(evil-define-key state evil-surround-mode-map "z" 'evil-surround-edit)
|
||
(evil-define-key state evil-surround-mode-map "Z" 'evil-Surround-edit))
|
||
(global-evil-surround-mode 1))
|
||
#+END_SRC
|
||
Notes:
|
||
- ~cz'"~ :: to convert surrounding single quote string to double quotes.
|
||
- ~dz"~ :: to delete the surrounding double quotes.
|
||
- ~yze"~ :: puts single quotes around the next word.
|
||
- ~yZ$<p>~ :: surrouds the line with HTML =<p>= tag (with extra carriage returns).
|
||
- ~(~ :: puts spaces /inside/ the surrounding parens, but ~)~ doesn't. Same with ~[~ and ~]~.
|
||
** Jump, Jump, Jump!
|
||
|
||
While I grew up on =Control S=, I am liking the /mental model/ associated with the [[https://github.com/abo-abo/avy][avy project]] that allows a /jump/ among matches across all visible windows. I use the ~F18~ key on my keyboard that should be easy to use.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package avy
|
||
:init
|
||
(setq avy-all-windows t
|
||
avy-single-candidate-jump t
|
||
avy-orders-alist
|
||
'((avy-goto-char . avy-order-closest)
|
||
(avy-goto-word-0 . avy-order-closest)))
|
||
:config (ha-leader "j" '("jump" . avy-goto-char-timer))
|
||
:bind ("<f18>" . avy-goto-char-timer))
|
||
#+END_SRC
|
||
*Note:* The links should be shorter near the point as opposed to starting from the top of the window.
|
||
** Miscellaneous Keys
|
||
I really appreciated the [[https://github.com/benma/visual-regexp.el][visual-regexp package]]:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package visual-regexp
|
||
:bind (("C-c r" . vr/replace)
|
||
("C-c q" . vr/query-replace)))
|
||
#+END_SRC
|
||
|
||
* Working Layout
|
||
While editing any file on disk is easy enough, I like the mental context switch associated with a full-screen window frame showing all the buffers of a /project task/ (often a direct link to a repository project, but not always).
|
||
** Projects
|
||
While I really don't /need/ all the features that [[https://github.com/bbatsov/projectile][projectile]] provides, it has all the features I do need, and is easy enough to install. I am referring to the fact that I /could/ use the built-in =project.el= system (see [[https://cestlaz.github.io/post/using-emacs-79-project/][this essay]] for details on what I mean as an alternative).
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package projectile
|
||
:custom
|
||
(projectile-sort-order 'recentf)
|
||
:config
|
||
(ha-leader
|
||
"p" '(:ignore t :which-key "projects")
|
||
"p W" '("initialize workspace" . ha-workspace-initialize)
|
||
"p n" '("new project space" . ha-project-persp)
|
||
"p !" '("run cmd in project root" . projectile-run-shell-command-in-root)
|
||
"p &" '("async cmd in project root" . projectile-run-async-shell-command-in-root)
|
||
"p a" '("add new project" . projectile-add-known-project)
|
||
"p b" '("switch to project buffer" . projectile-switch-to-buffer)
|
||
"p c" '("compile in project" . projectile-compile-project)
|
||
"p C" '("repeat last command" . projectile-repeat-last-command)
|
||
"p d" '("remove known project" . projectile-remove-known-project)
|
||
"p e" '("edit project .dir-locals" . projectile-edit-dir-locals)
|
||
"p f" '("find file in project" . projectile-find-file)
|
||
"p g" '("configure project" . projectile-configure-project)
|
||
"p i" '("invalidate project cache" . projectile-invalidate-cache)
|
||
"p k" '("kill project buffers" . projectile-kill-buffers)
|
||
"p o" '("find other file" . projectile-find-other-file)
|
||
"p p" '("switch project" . projectile-switch-project)
|
||
"p r" '("find recent project files" . projectile-recentf)
|
||
"p R" '("run project" . projectile-run-project)
|
||
"p s" '("save project files" . projectile-save-project-buffers)
|
||
"p T" '("test project" . projectile-test-project)))
|
||
#+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/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:
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defun ha--persp-label (num names)
|
||
"Return string of numbered elements. NUM is the starting
|
||
number and NAMES is a list of strings."
|
||
(when names
|
||
(concat
|
||
(format " %d: %s%s" ; Shame that the following doesn't work:
|
||
num ; (propertize (number-to-string num) :foreground "#00a0")
|
||
(car names) ; Nor does surrounding the number with underbars.
|
||
(if (equal (car names) (projectile-project-name)) "*" ""))
|
||
(ha--persp-label (1+ num) (cdr names)))))
|
||
|
||
(defun ha-persp-labels ()
|
||
"Return a string of numbered elements from a list of names."
|
||
(ha--persp-label 1 (sort (hash-table-keys (perspectives-hash)) 's-less?)))
|
||
#+END_SRC
|
||
|
||
Build the hydra as well as configure the =perspective= project.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package perspective
|
||
:custom
|
||
(persp-modestring-short t)
|
||
(persp-sort 'name)
|
||
(persp-show-modestring t)
|
||
|
||
:config
|
||
(persp-mode +1)
|
||
(defhydra hydra-workspace-leader (:color blue :hint nil) "
|
||
Workspaces- %s(ha-persp-labels)
|
||
_n_: new project _r_: rename _a_: add buffer _l_: load worksp
|
||
_]_: next worksp _d_: delete _b_: goto buffer _s_: save worksp
|
||
_[_: previous _W_: init all _k_: remove buffer _`_: to last worksp "
|
||
("TAB" persp-switch)
|
||
("`" persp-switch-last)
|
||
("1" (persp-switch-by-number 1))
|
||
("2" (persp-switch-by-number 2))
|
||
("3" (persp-switch-by-number 3))
|
||
("4" (persp-switch-by-number 4))
|
||
("5" (persp-switch-by-number 5))
|
||
("6" (persp-switch-by-number 6))
|
||
("7" (persp-switch-by-number 7))
|
||
("8" (persp-switch-by-number 8))
|
||
("9" (persp-switch-by-number 9))
|
||
("0" (persp-switch-by-number 0))
|
||
("n" ha-project-persp)
|
||
("]" persp-next :color pink)
|
||
("[" persp-prev :color pink)
|
||
("r" persp-rename)
|
||
("d" persp-kill)
|
||
("W" ha-workspace-initialize)
|
||
("a" persp-add-buffer)
|
||
("b" persp-switch-to-buffer)
|
||
("k" persp-remove-buffer)
|
||
("K" persp-kill-buffer)
|
||
("s" persp-state-save)
|
||
("l" persp-state-load)
|
||
("q" nil)
|
||
("C-g" nil))
|
||
|
||
(ha-leader "TAB" '("workspaces" . hydra-workspace-leader/body)))
|
||
#+END_SRC
|
||
*** Predefined Workspaces
|
||
First step is to get rid of the /recent/ feature, as I don't really use that.
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(recentf-mode -1)
|
||
(remove-hook 'kill-emacs-hook 'recentf-cleanup)
|
||
(remove-hook 'kill-emacs-hook 'save-place-kill-emacs-hook)
|
||
(remove-hook 'kill-emacs-hook 'savehist-autosave)
|
||
#+END_SRC
|
||
|
||
Let's describe a list of startup project workspaces. This way, I don't need the clutter of the recent state, but also get back to a state of mental normality.
|
||
Granted, this list is essentially a list of projects that I'm currently developing, so I expect this to change often.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defvar ha-workspace-projects-personal
|
||
'(("projects" "~/projects" ("breathe.org" "tasks.org"))
|
||
("personal" "~/personal" ("general.org"))
|
||
("technical" "~/technical" ("ansible.org"))
|
||
("hamacs" "~/other/hamacs" ("README.org" "ha-config.org"))
|
||
("rpg" "~/Dropbox/org/rpg" ("workdavians-dragon-heist.org" "dragon-heist.org"))
|
||
("dm-work" "~/Dropbox/org/rpg-dm" ("README-mythic.org" "rpgdm.el")))
|
||
"List of default projects with a name.")
|
||
#+END_SRC
|
||
|
||
Given a list of information about project-workspaces, can we just create them all?
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defun ha-persp-exists? (name)
|
||
"Return non-nill is a perspective of NAME has been created."
|
||
(seq-contains (hash-table-keys (perspectives-hash)) name))
|
||
|
||
(defun ha-workspace-initialize (&optional projects)
|
||
"Precreate workspace projects from a PROJECTS list.
|
||
Each entry in the list is a list containing:
|
||
- name (as a string)
|
||
- project root directory
|
||
- a optional list of files to display"
|
||
(interactive)
|
||
(unless projects
|
||
(setq projects ha-workspace-projects-personal))
|
||
|
||
(dolist (project projects)
|
||
(-let (((name root files) project))
|
||
(unless (ha-persp-exists? name)
|
||
(message "Creating workspace: %s (from %s)" name root)
|
||
(ha-project-persp root name files)))))
|
||
#+END_SRC
|
||
Often, but not always, I want a perspective based on an actual Git repository, e.g. a project. Projectile keeps state of a "project" based on the current file loaded, so we /combine/ the two projects by first choosing from a list of /known projects/ and then creating a perspective based on the name. To pin the perspective to a project, we just need to load a file from it, e.g. Like a README or something.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defun ha-project-persp (project &optional name files)
|
||
"Create a new perspective, and then switch to the PROJECT using projectile.
|
||
If NAME is not given, then figure it out based on the name of the
|
||
PROJECT. If FILES aren't specified, then see if there is a
|
||
README. Otherwise, pull up a dired."
|
||
(interactive (list (projectile-completing-read "Project: " projectile-known-projects)))
|
||
(when (f-directory-p project)
|
||
(unless name
|
||
(setq name (f-filename project)))
|
||
(persp-switch name)
|
||
|
||
;; Unclear if the following is actually necessary.
|
||
(ignore-errors
|
||
(projectile-add-known-project root)
|
||
(let ((projectile-switch-project-action nil))
|
||
(projectile-switch-project-by-name root)))
|
||
|
||
;; To pin a project in projectile to the perspective, we need to load a file
|
||
;; from that project. The README will do, or at least, the dired of it.
|
||
(if files
|
||
(ha--project-show-files project files)
|
||
(if-let ((readme (f-join project "README*")))
|
||
(find-file readme t)
|
||
(dired project)))))
|
||
#+END_SRC
|
||
Displaying a few files? Well, when /starting/ I am only showing one or two files (maybe three), so we will split the window horizontally for each file.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defun ha--project-show-files (root files)
|
||
"Display a list of FILES in a project ROOT directory.
|
||
Each file gets its own window (so don't make the list of files
|
||
long)."
|
||
(message "Loading files from %s ... %s" root files)
|
||
(let* ((file (car files))
|
||
(more (cdr files))
|
||
(filename (format "%s/%s" root file)))
|
||
(find-file filename)
|
||
(when more
|
||
(split-window-horizontally)
|
||
(ha--project-show-files root more))))
|
||
#+END_SRC
|
||
* Applications
|
||
Can we really call these /applications/?
|
||
** Magit
|
||
Can not live without [[https://magit.vc/][Magit]], a Git porcelain for Emacs. I stole the bulk of this work from Doom Emacs.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package magit
|
||
:config
|
||
;; The following code re-instates my General Leader key in Magit.
|
||
(general-unbind magit-mode-map "SPC")
|
||
|
||
(ha-leader
|
||
"g" '(:ignore t :which-key "git")
|
||
"g /" '("Magit dispatch" . magit-dispatch)
|
||
"g ." '("Magit file dispatch" . magit-file-dispatch)
|
||
"g b" '("Magit switch branch" . magit-branch-checkout)
|
||
|
||
"g g" '("Magit status" . magit-status)
|
||
"g s" '("Magit status here" . magit-status-here)
|
||
"g D" '("Magit file delete" . magit-file-delete)
|
||
"g B" '("Magit blame" . magit-blame-addition)
|
||
"g C" '("Magit clone" . magit-clone)
|
||
"g F" '("Magit fetch" . magit-fetch)
|
||
"g L" '("Magit buffer log" . magit-log-buffer-file)
|
||
"g R" '("Revert file" . vc-revert)
|
||
"g S" '("Git stage file" . magit-stage-file)
|
||
"g U" '("Git unstage file" . magit-unstage-file)
|
||
|
||
"g f" '(:ignore t :which-key "find")
|
||
"g f f" '("Find file" . magit-find-file)
|
||
"g f g" '("Find gitconfig file" . magit-find-git-config-file)
|
||
"g f c" '("Find commit" . magit-show-commit)
|
||
|
||
"g l" '(:ignore t :which-key "list")
|
||
"g l r" '("List repositories" . magit-list-repositories)
|
||
"g l s" '("List submodules" . magit-list-submodules)
|
||
|
||
"g o" '(:ignore t :which-key "open")
|
||
|
||
"g c" '(:ignore t :which-key "create")
|
||
"g c R" '("Initialize repo" . magit-init)
|
||
"g c C" '("Clone repo" . magit-clone)
|
||
"g c c" '("Commit" . magit-commit-create)
|
||
"g c f" '("Fixup" . magit-commit-fixup)
|
||
"g c b" '("Branch" . magit-branch-and-checkout)))
|
||
#+END_SRC
|
||
|
||
Let's extend Magit with [[https://github.com/magit/forge][Magit Forge]] for working with Github and Gitlab:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package forge
|
||
:after magit
|
||
:config
|
||
(ha-leader
|
||
"g '" '("Forge dispatch" . forge-dispatch)
|
||
"g f i" '("Find issue" . forge-visit-issue)
|
||
"g f p" '("Find pull request" . forge-visit-pullreq)
|
||
|
||
"g l i" '("List issues" . forge-list-issues)
|
||
"g l p" '("List pull requests" . forge-list-pullreqs)
|
||
"g l n" '("List notifications" . forge-list-notifications)
|
||
|
||
"g o r" '("Browse remote" . forge-browse-remote)
|
||
"g o c" '("Browse commit" . forge-browse-commit)
|
||
"g o i" '("Browse an issue" . forge-browse-issue)
|
||
"g o p" '("Browse a pull request" . forge-browse-pullreq)
|
||
"g o i" '("Browse issues" . forge-browse-issues)
|
||
"g o P" '("Browse pull requests" . forge-browse-pullreqs)
|
||
|
||
"g c i" '("Issue" . forge-create-issue)
|
||
"g c p" '("Pull request" . forge-create-pullreq)))
|
||
#+END_SRC
|
||
|
||
The [[https://github.com/emacsmirror/git-timemachine][git-timemachine]] project is cool:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package git-timemachine
|
||
:config
|
||
(ha-leader "g t" '("git timemachine" . git-timemachine)))
|
||
#+END_SRC
|
||
|
||
Using the [[https://github.com/emacsmirror/gist][gist package]] to write code snippets on [[https://gist.github.com/][Github]] seems like it can be useful, but I'm not sure how often.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package gist
|
||
:config
|
||
(ha-leader
|
||
"g G" '(:ignore t :which-key "gists")
|
||
"g l g" '("gists" . gist-list)
|
||
"g G l" '("list" . gist-list) ; Lists your gists in a new buffer.
|
||
"g G r" '("region" . gist-region) ; Copies Gist URL into the kill ring.
|
||
"g G R" '("private region" . gist-region-private) ; Explicitly create a private gist.
|
||
"g G b" '("buffer" . gist-buffer) ; Copies Gist URL into the kill ring.
|
||
"g G B" '("private buffer" . gist-buffer-private) ; Explicitly create a private gist.
|
||
"g c g" '("gist" . gist-region-or-buffer) ; Post either the current region, or buffer
|
||
"g c G" '("private gist" . gist-region-or-buffer-private))) ; create private gist from region or buffer
|
||
#+END_SRC
|
||
** Web Browsing
|
||
*** EWW
|
||
Web pages look pretty good with EWW, but I'm having difficulty getting it to render a web search from DuckDuck.
|
||
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package eww
|
||
:init
|
||
(setq browse-url-browser-function 'eww-browse-url
|
||
browse-url-secondary-browser-function 'browse-url-default-browser
|
||
eww-browse-url-new-window-is-tab nil
|
||
shr-use-colors nil
|
||
shr-use-fonts t ; I go back and forth on this one
|
||
; shr-discard-aria-hidden t
|
||
shr-bullet "• "
|
||
shr-inhibit-images nil ; Gotta see the images
|
||
; shr-blocked-images '(svg)
|
||
; shr-folding-mode nil
|
||
url-privacy-level '(email))
|
||
|
||
:config
|
||
(define-key eww-mode-map (kbd "L") #'eww-list-bookmarks)
|
||
(define-key eww-buffers-mode-map (kbd "q") #'eww-bookmark-kill)
|
||
(define-key eww-bookmark-mode-map (kbd "q") #'eww-bookmark-kill))
|
||
#+END_SRC
|
||
And let's get [[https://github.com/abo-abo/ace-link][ace-link]] to work with EWW and Info pages:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package ace-link
|
||
:config
|
||
(ace-link-setup-default))
|
||
#+END_SRC
|
||
** VTerm
|
||
|
||
I'm not giving up on Eshell, but I am playing around with [[https://github.com/akermu/emacs-libvterm][vterm]], and it is pretty good, but I use it primarily as a more reliable approach to [[file:ha-remoting.org][a remote shell]].
|
||
|
||
VTerm has an issue (at least for me) with ~M-Backspace~ not deleting the previous word, and yeah, I want to make sure that both keystrokes do the same thing.
|
||
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(use-package vterm
|
||
:init
|
||
(setq vterm-shell "/usr/local/bin/fish")
|
||
;; Granted, I seldom pop out to the shell except during code demonstrations,
|
||
;; but I like how C-p/C-n jumps up to each prompt entry using this setting
|
||
;; that works with my prompt:
|
||
(setq vterm-use-vterm-prompt-detection-method nil
|
||
term-prompt-regexp "^.* $ ")
|
||
:config
|
||
(dolist (k '("<C-backspace>" "<M-backspace>"))
|
||
(define-key vterm-mode-map (kbd k)
|
||
(lambda () (interactive) (vterm-send-key (kbd "C-w")))))
|
||
|
||
(advice-add 'vterm-copy-mode :after 'evil-normal-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.
|
||
|
||
*Note:* To exit the copy-mode (and copy the selected text to the clipboard), hit ~Return~.
|
||
|
||
Hrm. Seems that I might want a function to copy the output of the last command to a register, or even an org-capture...
|
||
** Neotree
|
||
I primarily use [[https://github.com/jaypei/emacs-neotree][Neotree]] when I am screen-sharing my Emacs session with collegues as it shows a /project/ like an IDE.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package neotree
|
||
:config
|
||
(evil-define-key 'normal neotree-mode-map (kbd "TAB") 'neotree-enter)
|
||
(evil-define-key 'normal neotree-mode-map (kbd "SPC") 'neotree-quick-look)
|
||
(evil-define-key 'normal neotree-mode-map (kbd "RET") 'neotree-enter)
|
||
(evil-define-key 'normal neotree-mode-map (kbd "g") 'neotree-refresh)
|
||
(evil-define-key 'normal neotree-mode-map (kbd "H") 'neotree-hidden-file-toggle))
|
||
#+END_SRC
|
||
* Technical Artifacts :noexport:
|
||
Let's provide a name so that the file can be required:
|
||
|
||
#+BEGIN_SRC emacs-lisp :exports none
|
||
(provide 'ha-config)
|
||
;;; ha-config.el ends here
|
||
#+END_SRC
|
||
|
||
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
|
||
|
||
#+DESCRIPTION: A literate programming file for configuring Emacs.
|
||
|
||
#+PROPERTY: header-args:sh :tangle no
|
||
#+PROPERTY: header-args:emacs-lisp :tangle yes
|
||
#+PROPERTY: header-args :results none :eval no-export :comments no
|
||
|
||
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|