659 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Org Mode
		
	
	
	
	
	
			
		
		
	
	
			659 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Org Mode
		
	
	
	
	
	
| #+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 /" '("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)))
 | ||
| #+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
 | ||
| I’m stealing the code for this section from [[https://tsdh.org/posts/2022-08-01-difftastic-diffing-with-magit.html][this essay]] by Tassilo Horn, and in fact, I’m going to lift a lot of his explanation too, as I may need to remind myself how this works. The idea is based on using Wilfred’s excellent [[https://github.com/Wilfred/difftastic][difftastic]] tool to do a structural/syntax comparison of code changes in git. To begin, install the binary:
 | ||
| #+begin_src sh
 | ||
|   brew install difftastic # and the equivalent on Linux
 | ||
| #+end_src
 | ||
| Next, we can do this, to use this as a diff tool for everything.
 | ||
| #+begin_src emacs-lisp
 | ||
|   (setenv "GIT_EXTERNAL_DIFF" "difft")
 | ||
| #+end_src
 | ||
| But perhaps integrating it into Magit and selectively calling it (as it is slow). Tassilo suggests making the call to =difft= optional by first creating a helper function to set the =GIT_EXTERNAL_DIFF= to =difft=:
 | ||
| #+begin_src emacs-lisp
 | ||
|   (defun th/magit--with-difftastic (buffer command)
 | ||
|     "Run COMMAND with GIT_EXTERNAL_DIFF=difft then show result in BUFFER."
 | ||
|     (let ((process-environment
 | ||
|            (cons (concat "GIT_EXTERNAL_DIFF=difft --width="
 | ||
|                          (number-to-string (frame-width)))
 | ||
|                  process-environment)))
 | ||
|       ;; Clear the result buffer (we might regenerate a diff, e.g., for
 | ||
|       ;; the current changes in our working directory).
 | ||
|       (with-current-buffer buffer
 | ||
|         (setq buffer-read-only nil)
 | ||
|         (erase-buffer))
 | ||
|       ;; Now spawn a process calling the git COMMAND.
 | ||
|       (make-process
 | ||
|        :name (buffer-name buffer)
 | ||
|        :buffer buffer
 | ||
|        :command command
 | ||
|        ;; Don't query for running processes when emacs is quit.
 | ||
|        :noquery t
 | ||
|        ;; Show the result buffer once the process has finished.
 | ||
|        :sentinel (lambda (proc event)
 | ||
|                    (when (eq (process-status proc) 'exit)
 | ||
|                      (with-current-buffer (process-buffer proc)
 | ||
|                        (goto-char (point-min))
 | ||
|                        (ansi-color-apply-on-region (point-min) (point-max))
 | ||
|                        (setq buffer-read-only t)
 | ||
|                        (view-mode)
 | ||
|                        (end-of-line)
 | ||
|                        ;; difftastic diffs are usually 2-column side-by-side,
 | ||
|                        ;; so ensure our window is wide enough.
 | ||
|                        (let ((width (current-column)))
 | ||
|                          (while (zerop (forward-line 1))
 | ||
|                            (end-of-line)
 | ||
|                            (setq width (max (current-column) width)))
 | ||
|                          ;; Add column size of fringes
 | ||
|                          (setq width (+ width
 | ||
|                                         (fringe-columns 'left)
 | ||
|                                         (fringe-columns 'right)))
 | ||
|                          (goto-char (point-min))
 | ||
|                          (pop-to-buffer
 | ||
|                           (current-buffer)
 | ||
|                           `(;; If the buffer is that wide that splitting the frame in
 | ||
|                             ;; two side-by-side windows would result in less than
 | ||
|                             ;; 80 columns left, ensure it's shown at the bottom.
 | ||
|                             ,(when (> 80 (- (frame-width) width))
 | ||
|                                #'display-buffer-at-bottom)
 | ||
|                             (window-width . ,(min width (frame-width))))))))))))
 | ||
| #+end_src
 | ||
| The crucial parts of this helper function are that we "wash" the result using =ansi-color-apply-on-region= so that the function can transform the difftastic highlighting using shell escape codes to Emacs faces. Also, note the need to possibly change the width, as difftastic makes a side-by-side comparison.
 | ||
| 
 | ||
| The functions below depend on [[help:magit-thing-at-point][magit-thing-at-point]], and this depends on the [[https://sr.ht/~pkal/compat/][compat]] library, so let’s grab that stuff:
 | ||
| #+begin_src emacs-lisp :tangle no
 | ||
|   (use-package compat
 | ||
|     :straight (:host github :repo "emacs-straight/compat"))
 | ||
| 
 | ||
|   (use-package magit-section
 | ||
|     :commands magit-thing-at-point)
 | ||
| #+end_src
 | ||
| Next, let's define our first command basically doing a =git show= for some revision which defaults to the commit or branch at point or queries the user if there's none.
 | ||
| #+begin_src emacs-lisp
 | ||
|   (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, let’s 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 isn’t 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 I’ve already got those puppies installed:
 | ||
| #+begin_src sh
 | ||
|   brew install coreutils fd poppler ffmpegthumbnailer mediainfo imagemagick
 | ||
| #+end_src
 | ||
| 
 | ||
| I’m 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
 | ||
|     (if (ha-running-on-macos?)
 | ||
|         (setq pdf-info-epdfinfo-program "/opt/homebrew/bin/epdfinfo")
 | ||
|       (setq pdf-info-epdfinfo-program "/usr/local/bin/epdfinfo"))
 | ||
|     :general
 | ||
|     (:states 'normal :keymaps 'pdf-view-mode-map
 | ||
|              "gp" 'pdf-view-goto-page
 | ||
|              ">"  'doc-view-fit-window-to-page))
 | ||
| #+end_src
 | ||
| 
 | ||
| Make sure the [[help:pdf-info-check-epdfinfo][pdf-info-check-epdfinfo]] function works.
 | ||
| 
 | ||
| 
 | ||
| * 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:
 |