hamacs/ha-applications.org
2024-01-19 17:43:24 -08:00

697 lines
32 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+title: Applications
#+author: Howard X. Abrams
#+date: 2023-12-21
#+tags: emacs
A literate programming file configuring critical applications.
#+begin_src emacs-lisp :exports none
;;; ha-applications.el --- configuring critical applications. -*- lexical-binding: t; -*-
;;
;; © 2023 Howard X. Abrams
;; 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 <howard.abrams@gmail.com>
;; Created: December 21, 2023
;;
;; While obvious, GNU Emacs does not include this file
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; /Users/howard/other/hamacs/ha-applications.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
Can we call the following /applications/? I guess.
* Git and 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
;; See https://github.com/magit/magit/wiki/Emacsclient for why we need to set:
:custom (with-editor-emacsclient-executable "emacsclient")
: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 <escape>" '(keyboard-escape-quit :which-key t)
"g /" '("Magit dispatch" . magit-dispatch)
"g ." '("Magit file dispatch" . magit-file-dispatch)
"g b" '("Magit switch branch" . magit-branch-checkout)
"g u" '("Git Update" . vc-update)
"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" . magit-file-checkout)
"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))
(general-nmap "<escape>" #'transient-quit-one))
#+end_src
** Git Gutter
The [[https://github.com/syohex/emacs-git-gutter-fringe][git-gutter-fringe]] project displays markings in the fringe (extreme left margin) to show modified and uncommitted lines. This project builds on [[https://github.com/emacsorphanage/git-gutter][git-gutter]] project to provide movement between /hunks/:
#+begin_src emacs-lisp
(use-package git-gutter-fringe
:custom
;; To have both flymake and git-gutter work, we put
;; git-gutter on the right side:
(git-gutter-fr:side 'right-fringe)
(left-fringe-width 15)
(right-fringe-width 10)
:config
(set-face-foreground 'git-gutter-fr:modified "yellow")
(set-face-foreground 'git-gutter-fr:added "green")
(set-face-foreground 'git-gutter-fr:deleted "red")
(global-git-gutter-mode)
(ha-leader
"g n" '("next hunk" . git-gutter:next-hunk)
"g p" '("previous hunk" . git-gutter:previous-hunk)
"g e" '("end of hunk" . git-gutter:end-of-hunk)
"g r" '("revert hunk" . git-gutter:revert-hunk)
"g s" '("stage hunk" . git-gutter:stage-hunk)))
#+end_src
** Git Delta
The [[https://scripter.co/using-git-delta-with-magit][magit-delta]] project uses [[https://github.com/dandavison/delta][git-delta]] for colorized diffs.
#+begin_src emacs-lisp
(use-package magit-delta
:ensure t
:hook (magit-mode . magit-delta-mode))
#+end_src
I also need to append the following to my [[file:~/.gitconfig][~/.gitconfig]] file:
#+begin_src conf
[delta]
minus-style = normal "#8f0001"
minus-non-emph-style = normal "#8f0001"
minus-emph-style = normal bold "#d01011"
minus-empty-line-marker-style = normal "#8f0001"
zero-style = syntax
plus-style = syntax "#006800"
plus-non-emph-style = syntax "#006800"
plus-emph-style = syntax "#009000"
plus-empty-line-marker-style = normal "#006800"
#+end_src
** Git with Difftastic
Im stealing the code for this section from [[https://tsdh.org/posts/2022-08-01-difftastic-diffing-with-magit.html][this essay]] by Tassilo Horn, and in fact, Im going to lift a lot of his explanation too, as I may need to remind myself how this works. The idea is based on using Wilfreds excellent [[https://github.com/Wilfred/difftastic][difftastic]] tool to do a structural/syntax comparison of code changes in git. To begin, install the binary:
#+begin_src sh
brew install difftastic # and the equivalent on Linux
#+end_src
Next, we can do this, to use this as a diff tool for everything.
#+begin_src emacs-lisp
(setenv "GIT_EXTERNAL_DIFF" "difft")
#+end_src
But perhaps integrating it into Magit and selectively calling it (as it is slow). Tassilo suggests making the call to =difft= optional by first creating a helper function to set the =GIT_EXTERNAL_DIFF= to =difft=:
#+begin_src emacs-lisp
(defun th/magit--with-difftastic (buffer command)
"Run COMMAND with GIT_EXTERNAL_DIFF=difft then show result in BUFFER."
(let ((process-environment
(cons (concat "GIT_EXTERNAL_DIFF=difft --width="
(number-to-string (frame-width)))
process-environment)))
;; Clear the result buffer (we might regenerate a diff, e.g., for
;; the current changes in our working directory).
(with-current-buffer buffer
(setq buffer-read-only nil)
(erase-buffer))
;; Now spawn a process calling the git COMMAND.
(make-process
:name (buffer-name buffer)
:buffer buffer
:command command
;; Don't query for running processes when emacs is quit.
:noquery t
;; Show the result buffer once the process has finished.
:sentinel (lambda (proc event)
(when (eq (process-status proc) 'exit)
(with-current-buffer (process-buffer proc)
(goto-char (point-min))
(ansi-color-apply-on-region (point-min) (point-max))
(setq buffer-read-only t)
(view-mode)
(end-of-line)
;; difftastic diffs are usually 2-column side-by-side,
;; so ensure our window is wide enough.
(let ((width (current-column)))
(while (zerop (forward-line 1))
(end-of-line)
(setq width (max (current-column) width)))
;; Add column size of fringes
(setq width (+ width
(fringe-columns 'left)
(fringe-columns 'right)))
(goto-char (point-min))
(pop-to-buffer
(current-buffer)
`(;; If the buffer is that wide that splitting the frame in
;; two side-by-side windows would result in less than
;; 80 columns left, ensure it's shown at the bottom.
,(when (> 80 (- (frame-width) width))
#'display-buffer-at-bottom)
(window-width . ,(min width (frame-width))))))))))))
#+end_src
The crucial parts of this helper function are that we "wash" the result using =ansi-color-apply-on-region= so that the function can transform the difftastic highlighting using shell escape codes to Emacs faces. Also, note the need to possibly change the width, as difftastic makes a side-by-side comparison.
The functions below depend on [[help:magit-thing-at-point][magit-thing-at-point]], and this depends on the [[https://sr.ht/~pkal/compat/][compat]] library, so lets grab that stuff:
#+begin_src emacs-lisp :tangle no
(use-package compat
:straight (:host github :repo "emacs-straight/compat"))
(use-package magit-section
:commands magit-thing-at-point)
#+end_src
Next, let's define our first command basically doing a =git show= for some revision which defaults to the commit or branch at point or queries the user if there's none.
#+begin_src emacs-lisp
(defun th/magit-show-with-difftastic (rev)
"Show the result of \"git show REV\" with GIT_EXTERNAL_DIFF=difft."
(interactive
(list (or
;; Use if given the REV variable:
(when (boundp 'rev) rev)
;; If not invoked with prefix arg, try to guess the REV from
;; point's position.
(and (not current-prefix-arg)
(or (magit-thing-at-point 'git-revision t)
(magit-branch-or-commit-at-point)))
;; Otherwise, query the user.
(magit-read-branch-or-commit "Revision"))))
(if (not rev)
(error "No revision specified")
(th/magit--with-difftastic
(get-buffer-create (concat "*git show difftastic " rev "*"))
(list "git" "--no-pager" "show" "--ext-diff" rev))))
#+end_src
And here the second command which basically does a =git diff=. It tries to guess what one wants to diff, e.g., when point is on the Staged changes section in a magit buffer, it will run =git diff --cached= to show a diff of all staged changes. If it can not guess the context, it'll query the user for a range or commit for diffing.
#+begin_src emacs-lisp
(defun th/magit-diff-with-difftastic (arg)
"Show the result of \"git diff ARG\" with GIT_EXTERNAL_DIFF=difft."
(interactive
(list (or
;; Use If RANGE is given, just use it.
(when (boundp 'range) range)
;; If prefix arg is given, query the user.
(and current-prefix-arg
(magit-diff-read-range-or-commit "Range"))
;; Otherwise, auto-guess based on position of point, e.g., based on
;; if we are in the Staged or Unstaged section.
(pcase (magit-diff--dwim)
('unmerged (error "unmerged is not yet implemented"))
('unstaged nil)
('staged "--cached")
(`(stash . ,value) (error "stash is not yet implemented"))
(`(commit . ,value) (format "%s^..%s" value value))
((and range (pred stringp)) range)
(_ (magit-diff-read-range-or-commit "Range/Commit"))))))
(let ((name (concat "*git diff difftastic"
(if arg (concat " " arg) "")
"*")))
(th/magit--with-difftastic
(get-buffer-create name)
`("git" "--no-pager" "diff" "--ext-diff" ,@(when arg (list arg))))))
#+end_src
What's left is integrating the new show and diff commands in Magit. For that purpose, Tasillo created a new transient prefix for all personal commands. Intriguing, but I have a hack that I can use on a leader:
#+begin_src emacs-lisp
(defun ha-difftastic-here ()
(interactive)
(call-interactively
(if (eq major-mode 'magit-log-mode)
'th/magit-show-with-difftastic
'th/magit-diff-with-difftastic)))
(ha-leader "g d" '("difftastic" . ha-difftastic-here))
#+end_src
** Time Machine
The [[https://github.com/emacsmirror/git-timemachine][git-timemachine]] project visually shows how a code file changes with each iteration:
#+begin_src emacs-lisp
(use-package git-timemachine
:config
(ha-leader "g t" '("git timemachine" . git-timemachine)))
#+end_src
** Gist
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 :tangle no
(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
The gist project depends on the [[https://github.com/sigma/gh.el][gh library]]. There seems to be a problem with it.
#+begin_src emacs-lisp :tangle no
(use-package gh
:straight (:host github :repo "sigma/gh.el"))
#+end_src
** Forge
Let's extend Magit with [[https://github.com/magit/forge][Magit Forge]] for working with Github and Gitlab:
#+begin_src emacs-lisp :tangle no
(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
Every /so often/, pop over to the following URLs and generate a new token where the *Note* is =forge=, and then copy that into the [[file:~/.authinfo.gpg][~/.authinfo.gpg]]:
- [[https://gitlab.com/-/user_settings/personal_access_tokens][Gitlab]]
- [[https://github.com/settings/tokens][Github]]
and make sure this works:
#+begin_src emacs-lisp :tangle no :results replace
(ghub-request "GET" "/user" nil
:forge 'github
:host "api.github.com"
:username "howardabrams"
:auth 'forge)
#+end_src
** Pushing is Bad
Pushing directly to the upstream branch is /bad form/, as one should create a pull request, etc. To prevent an accidental push, we /double-check/ first:
#+begin_src emacs-lisp
(define-advice magit-push-current-to-upstream (:before (args) query-yes-or-no)
"Prompt for confirmation before permitting a push to upstream."
(when-let ((branch (magit-get-current-branch)))
(unless (yes-or-no-p (format "Push %s branch upstream to %s? "
branch
(or (magit-get-upstream-branch branch)
(magit-get "branch" branch "remote"))))
(user-error "Push to upstream aborted by user"))))
#+end_src
* ediff
Love me ediff, but with monitors that are wider than they are tall, lets put the diffs side-by-side:
#+begin_src emacs-lisp
(setq ediff-split-window-function 'split-window-horizontally)
#+end_src
Frames, er, windows, are actually annoying for me, as Emacs is always in full-screen mode.
#+begin_src emacs-lisp
(setq ediff-window-setup-function 'ediff-setup-windows-plain)
#+end_src
When =ediff= is finished, it leaves the windows /borked/. This is annoying, but according to [[http://yummymelon.com/devnull/surprise-and-emacs-defaults.html][this essay]], we can fix it:
#+begin_src emacs-lisp
(defvar my-ediff-last-windows nil
"Session for storing window configuration before calling `ediff'.")
(defun my-store-pre-ediff-winconfig ()
"Store `current-window-configuration' in variable `my-ediff-last-windows'."
(setq my-ediff-last-windows (current-window-configuration)))
(defun my-restore-pre-ediff-winconfig ()
"Restore window configuration to stored value in `my-ediff-last-windows'."
(set-window-configuration my-ediff-last-windows))
(add-hook 'ediff-before-setup-hook #'my-store-pre-ediff-winconfig)
(add-hook 'ediff-quit-hook #'my-restore-pre-ediff-winconfig)
#+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
(ha-leader "a b" '("eww browser" . eww))
:general
(:states 'normal :keymaps 'eww-mode-map
"B" 'eww-list-bookmarks
"Y" 'eww-copy-page-url
"H" 'eww-back-url
"L" 'eww-forward-url
"u" 'eww-top-url
"p" 'eww-previous-url
"n" 'eww-next-url
"q" 'bury-buffer)
(:states 'normal :keymaps 'eww-buffers-mode-map
"q" 'bury-buffer))
#+end_src
This function allows Imenu to offer HTML headings in EWW buffers, helpful for navigating long, technical documents.
#+begin_src emacs-lisp
(use-package eww
:config
(defun unpackaged/imenu-eww-headings ()
"Return alist of HTML headings in current EWW buffer for Imenu.
Suitable for `imenu-create-index-function'."
(let ((faces '(shr-h1 shr-h2 shr-h3 shr-h4 shr-h5 shr-h6 shr-heading)))
(save-excursion
(save-restriction
(widen)
(goto-char (point-min))
(cl-loop for next-pos = (next-single-property-change (point) 'face)
while next-pos
do (goto-char next-pos)
for face = (get-text-property (point) 'face)
when (cl-typecase face
(list (cl-intersection face faces))
(symbol (member face faces)))
collect (cons (buffer-substring (point-at-bol) (point-at-eol)) (point))
and do (forward-line 1))))))
:hook (eww-mode .
(lambda ()
(setq-local imenu-create-index-function #'unpackaged/imenu-eww-headings))))
#+end_src
** Get Pocket
The [[https://github.com/alphapapa/pocket-reader.el][pocket-reader]] project connects to the [[https://getpocket.com/en/][Get Pocket]] service.
#+begin_src emacs-lisp
(use-package pocket-reader
:init
(setq org-web-tools-pandoc-sleep-time 1)
:config
(ha-leader "o p" '("get pocket" . pocket-reader))
;; Instead of jumping into Emacs mode to get the `pocket-mode-map',
;; we add the keybindings to the normal mode that makes sense.
:general
(:states 'normal :keymaps 'pocket-reader-mode-map
"RET" 'pocket-reader-open-url
"TAB" 'pocket-reader-pop-to-url
"*" 'pocket-reader-toggle-favorite
"B" 'pocket-reader-open-in-external-browser
"D" 'pocket-reader-delete
"E" 'pocket-reader-excerpt-all
"F" 'pocket-reader-show-unread-favorites
"M" 'pocket-reader-mark-all
"R" 'pocket-reader-random-item
"S" 'tabulated-list-sort
"a" 'pocket-reader-toggle-archived
"c" 'pocket-reader-copy-url
"d" 'pocket-reader
"e" 'pocket-reader-excerpt
"f" 'pocket-reader-toggle-favorite
"l" 'pocket-reader-limit
"m" 'pocket-reader-toggle-mark
"o" 'pocket-reader-more
"q" 'quit-window
"s" 'pocket-reader-search
"u" 'pocket-reader-unmark-all
"t a" 'pocket-reader-add-tags
"t r" 'pocket-reader-remove-tags
"t s" 'pocket-reader-tag-search
"t t" 'pocket-reader-set-tags
"g s" 'pocket-reader-resort
"g r" 'pocket-reader-refresh))
#+end_src
Use these special keywords when searching:
- =:*=, =:favorite= Return favorited items.
- =:archive= Return archived items.
- =:unread= Return unread items (default).
- =:all= Return all items.
- =:COUNT= Return at most /COUNT/ (a number) items. This limit persists until you start a new search.
- =:t:TAG=, =t:TAG= Return items with /TAG/ (you can search for one tag at a time, a limitation of the Pocket API).
** External Browsing
Browsing on a work laptop is a bit different. According to [[http://ergoemacs.org/emacs/emacs_set_default_browser.html][this page]], I can set a /default browser/ for different URLs, which is great, as I can launch my browser for personal browsing, or another browser for work access, or even EWW. To make this clear, I'm using the abstraction associated with [[https://github.com/rolandwalker/osx-browse][osx-browse]]:
#+begin_src emacs-lisp
(use-package osx-browse
:init
(setq browse-url-handlers
'(("docs\\.google\\.com" . osx-browse-url-personal)
("grafana.com" . osx-browse-url-personal)
("dndbeyond.com" . osx-browse-url-personal)
("tabletopaudio.com" . osx-browse-url-personal)
("youtu.be" . osx-browse-url-personal)
("youtube.com" . osx-browse-url-personal)
("." . eww-browse-url)))
:config
(defun osx-browse-url-personal (url &optional new-window browser focus)
"Open URL in Firefox for my personal surfing.
The parameters, URL, NEW-WINDOW, and FOCUS are as documented in
the function, `osx-browse-url'."
(interactive (osx-browse-interactive-form))
(cl-callf or browser "org.mozilla.Firefox")
(osx-browse-url url new-window browser focus)))
#+end_src
* Dirvish
The [[https://github.com/alexluigit/dirvish][dirvish]] project aims to be a better =dired=. And since the =major-mode= is still =dired-mode=, the decades of finger memory isnt lost. For people starting to use =dired=, most commands are pretty straight-forward (and Prot did a pretty good [[https://www.youtube.com/watch?v=5dlydii7tAU][introduction]] to it), but to remind myself, keep in mind:
- ~%~ :: will /mark/ a bunch of files based on a regular expression
- ~m~ :: marks a single file
- ~d~ :: marks a file to delete, type ~x~ to follow-through on all files marked for deletion.
- ~u~ :: un-mark a file, or type ~!~ to un-mark all
- ~t~ :: to toggle the marked files. Keep files with =xyz= extension? Mark those with ~%~, and then ~t~ toggle.
- ~C~ :: copy the current file or all marked files
- ~R~ :: rename/move the current file or all marked files
- ~M~ :: change the mode (=chmod=) of current or marked files, accepts symbols, like =a+x=
Note that =dired= has /two marks/ … one is a general mark, and the other is specifically a mark of files to delete.
Dirvish does require the following supporting programs, but Ive already got those puppies installed:
#+begin_src sh
brew install coreutils fd poppler ffmpegthumbnailer mediainfo imagemagick
#+end_src
Im beginning with dirvish to use the [[https://github.com/alexluigit/dirvish/blob/main/docs/CUSTOMIZING.org][sample configuration]] and change it:
#+begin_src emacs-lisp
(use-package dirvish
:straight (:host github :repo "alexluigit/dirvish")
:init
(dirvish-override-dired-mode)
:custom
(dirvish-quick-access-entries
'(("h" "~/" "Home")
("p" "~/personal" "Personal")
("p" "~/projects" "Projects")
("t" "~/technical" "Technical")
("w" "~/website" "Website")
("d" "~/Downloads/" "Downloads")))
:config
;; This setting is like `treemacs-follow-mode' where the buffer
;; changes based on the current file. Not sure if I want this:
;; (dirvish-side-follow-mode)
(setq dirvish-mode-line-format
'(:left (sort symlink) :right (omit yank index)))
(setq dirvish-attributes
'(all-the-icons file-time file-size collapse subtree-state vc-state git-msg))
(setq delete-by-moving-to-trash t
dired-auto-revert-buffer t)
;; With `ls' as an alias, and `gls' available on _some_ of my systems, I dont:
;; (setq insert-directory-program "gls")
;; And instead use Emacs' built-in directory lister:
(setq insert-directory-program nil)
(setq ls-lisp-use-insert-directory-program nil)
(require 'ls-lisp)
(setq dired-listing-switches
"-l --almost-all --human-readable --group-directories-first --no-group")
(set-face-attribute 'dirvish-hl-line nil :background "darkmagenta"))
#+end_src
While in =dirvish-mode=, we can rebind some keys:
#+begin_src emacs-lisp
(use-package dirvish
:bind
(:map dirvish-mode-map ; Dirvish inherits `dired-mode-map'
("a" . dirvish-quick-access)
("f" . dirvish-file-info-menu)
("y" . dirvish-yank-menu)
("N" . dirvish-narrow)
("^" . dirvish-history-last)
("h" . dirvish-history-jump) ; remapped `describe-mode'
("q" . dirvish-quit)
("s" . dirvish-quicksort) ; remapped `dired-sort-toggle-or-edit'
("v" . dirvish-vc-menu) ; remapped `dired-view-file'
("TAB" . dirvish-subtree-toggle)
("M-f" . dirvish-history-go-forward)
("M-b" . dirvish-history-go-backward)
("M-l" . dirvish-ls-switches-menu)
("M-m" . dirvish-mark-menu)
("M-t" . dirvish-layout-toggle)
("M-s" . dirvish-setup-menu)
("M-e" . dirvish-emerge-menu)
("M-j" . dirvish-fd-jump)))
#+end_src
* Annotations
Let's try [[https://github.com/bastibe/annotate.el][annotate-mode]], which allows you to drop "notes" and then move to them (yes, serious overlap with bookmarks, which we will return to).
#+begin_src emacs-lisp
(use-package annotate
:config
(ha-leader
"t A" '("annotations" . annotate-mode)
"n" '(:ignore t :which-key "notes")
"n a" '("toggle mode" . annotate-mode)
"n n" '("annotate" . annotate-annotate)
"n d" '("delete" . annotate-delete)
"n s" '("summary" . annotate-show-annotation-summary)
"n j" '("next" . annotate-goto-next-annotation)
"n k" '("prev" . annotate-goto-previous-annotation)
;; If a shift binding isn't set, it defaults to non-shift version
;; Use SPC N N to jump to the next error:
"n N" '("next error" . flycheck-next-error)))
#+end_src
Keep the annotations simple, almost /tag-like/, and then the summary allows you to display them.
* Keepass
Use the [[https://github.com/ifosch/keepass-mode][keepass-mode]] to view a /read-only/ version of my Keepass file in Emacs:
#+begin_src emacs-lisp
(use-package keepass-mode)
#+end_src
When having your point on a key entry, you can copy fields to kill-ring using:
- ~u~ :: URL
- ~b~ :: user name
- ~c~ :: password
* Demo It
Making demonstrations /within/ Emacs with my [[https://github.com/howardabrams/demo-it][demo-it]] project. While on MELPA, I want to use my own cloned version to make sure I can keep debugging it.
#+begin_src emacs-lisp
(use-package demo-it
:straight (:local-repo "~/other/demo-it")
;; :straight (:host github :repo "howardabrams/demo-it")
:commands (demo-it-create demo-it-start))
#+end_src
* PDF Viewing
Why not [[https://github.com/politza/pdf-tools][view PDF files]] better? If you have standard build tools installed on your system, run [[help:pdf-tools-install][pdf-tools-install]], as this command will an =epdfinfo= program to PDF displays.
#+begin_src emacs-lisp
(use-package pdf-tools
:mode ("\\.pdf\\'" . pdf-view-mode)
:init
(setq pdf-info-epdfinfo-program
(if (file-exists-p "/opt/homebrew")
"/opt/homebrew/bin/epdfinfo"
"/usr/local/bin/epdfinfo")
;; Match my theme:
pdf-view-midnight-colors '("#c5c8c6" . "#1d1f21"))
:general
(:states 'normal :keymaps 'pdf-view-mode-map
;; Since the keys don't make sense when reading:
"J" 'pdf-view-scroll-up-or-next-page
"K" 'pdf-view-scroll-down-or-previous-page
"gp" 'pdf-view-goto-page
">" 'doc-view-fit-window-to-page))
#+end_src
Make sure the [[help:pdf-info-check-epdfinfo][pdf-info-check-epdfinfo]] function works.
The [[Evil Collection][evil-collection]] package adds the following keybindings:
- ~z d~ :: Dark mode … indispensable, see also ~z m~
- ~C-j~ / ~C-k~ :: next and previous pages
- ~j~ / ~k~ :: up and down the page
- ~h~ / ~l~ :: scroll the page left and right
- ~=~ / ~-~ :: enlarge and shrink the page
- ~o~ :: Table of contents (if available)
Id like write notes in org files that link to the PDFs (and maybe visa versa), using the [[https://github.com/weirdNox/org-noter][org-noter]] package:
#+begin_src emacs-lisp
(use-package org-noter
:config
(ha-leader "o N" '("pdf notes" . org-noter))
(ha-local-leader
:keymaps 'org-noter-doc-mode-map
"n" '("pdf notes" . org-noter)
;; This means that I can stay in normal mode:
:keymaps 'org-noter-notes-mode-map
"i" '("insert note" . org-noter-insert-note)
"s" '("sync note" . org-noter-sync-current-note)
"n" '("next note" . org-noter-sync-next-note)
"p" '("previous note" . org-noter-sync-prev-note)))
#+end_src
To use, open a header in an org doc, and run =M-x org-noter= (~SPC o N~) and select the PDF. The =org-noter= function can be called in the PDF doc as well. In Emacs state, type ~i~ to insert a note /as a header/, or in Normal state, type ~, i~.
* Technical Artifacts :noexport:
Let's provide a name so that the file can be required:
#+begin_src emacs-lisp :exports none
(provide 'ha-applications)
;;; ha-applications.el ends here
#+end_src
#+DESCRIPTION: A literate programming file configuring critical applications.
#+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
# Local Variables:
# eval: (add-hook 'after-save-hook #'org-babel-tangle t t)
# End: