#+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:
  ;;            ~/src/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
    ;; See https://takeonrules.com/2024/03/01/quality-of-life-improvement-for-entering-and-exiting-magit/
    (setq magit-display-buffer-function
        #'magit-display-buffer-fullframe-status-v1)
    (setq magit-bury-buffer-function
        #'magit-restore-window-configuration)

    ;; 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)

      "g <escape>" '(keyboard-escape-quit :which-key t)
      "g C-g" '(keyboard-escape-quit :which-key t))

    (general-nmap "<escape>" #'transient-quit-one))
#+end_src
** VC Diff Highlight
The [[https://github.com/dgutov/diff-hl][diff-hl project]], while more active, has more features than the [[https://github.com/syohex/emacs-git-gutter-fringe][git-gutter-fringe]] project.

#+BEGIN_SRC emacs-lisp
  (use-package diff-hl
    :custom
    (diff-hl-side 'right)
    (fringe-mode '(8 . 4))
    (diff-hl-draw-borders nil)

    :hook ((dired-mode   . diff-hl-dired-mode)
           (diff-hl-mode . diff-hl-flydiff-mode)))
#+END_SRC

Turning on the mode, as well as binding some new /leader/ keys:

#+BEGIN_SRC emacs-lisp
  (use-package diff-hl
    :config
    (global-diff-hl-mode)

    (ha-leader
      "g j" '("jump hunk"     . diff-hl-diff-goto-hunk)
      "g ]" '("next hunk"     . diff-hl-next-hunk)
      "g [" '("previous hunk" . diff-hl-previous-hunk)
      "g e" '("end of hunk"   . diff-hl-end-of-hunk)
      "g r" '("revert hunk"   . diff-hl-revert-hunk)
      "g s" '("show hunk"     . diff-hl-show-hunk)
      "g S" '("stage hunk"    . diff-hl-stage-dwim)

      ;; Using Gerrit means I might want to view changes not from my
      ;; last review, but from the original changes:
      "g a" '("diff amend"    . diff-hl-amend-mode)))
#+END_SRC

This project (and others) can use repeat mode, but

#+BEGIN_SRC emacs-lisp
  (repeat-mode)
#+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

This requires [[https://dandavison.github.io/delta/installation.html][installing an executable]]. For instance, on my Mac:
#+begin_src sh
  brew install git-delta
#+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

How much has been already integrated? Need to re-evaluate this.
** 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
** Github Search?
Wanna see an example of how other’s use a particular function?
#+begin_src emacs-lisp
  (defun my-github-search(&optional search)
    (interactive (list (read-string "Search: " (thing-at-point 'symbol))))
    (let* ((language (cond ((eq major-mode 'python-mode) "Python")
                           ((eq major-mode 'emacs-lisp-mode) "Emacs Lisp")
                           ((eq major-mode 'yaml-mode) "Ansible")
                           (t "Text")))
           (url (format "https://github.com/search/?q=\"%s\"+language:\"%s\"&type=Code" (url-hexify-string search)
                        language)))
      (browse-url url)))
#+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
          browse-url-chrome-program "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

          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))

    (defun ha-eww-save-off-window (name)
      (interactive (list (read-string "Name: " (plist-get eww-data :title))))
      (rename-buffer (format "*eww: %s*" name) t))

    (defun ha-eww-better-scroll (prefix)
      (interactive "^p")
      (forward-paragraph prefix)
      ;; (recenter) ... if you want the cursor in the center,
      ;; otherwise, this puts the paragraph at the top of window:
      (recenter-top-bottom 0))

    (major-mode-hydra-define eww-mode nil
      ("Browser"
       (("G" eww-browse "Browse")
        ("B" eww-list-bookmarks "Bookmarks")
        ("q" bury-buffer "Quit"))
       "History"
       (("l" eww-back-url     "Back" :color pink)
        ("r" eww-forward-url  "Forward" :color pink)
        ("H" eww-list-histories "History"))
       "Current Page"
       (("b" eww-add-bookmark "Bookmark")
        ("g" link-hint-open-link "Jump Link")
        ("d" eww-download "Download"))
       "Render Page"
       (("e" eww-browse-with-external-browser "Open in Firefox")
        ("R" eww-readable "Reader Mode")
        ("y" eww-copy-page-url "Copy URL"))
       "Navigation"
       (("u" eww-top-url "Site Top")
        ("n" eww-next-url "Next Page" :color pink)
        ("p" eww-previous-url "Previous" :color pink))
       "Toggles"
       (("c" eww-toggle-colors "Colors")
        ("i" eww-toggle-images "Images")
        ("f" eww-toggle-fonts "Fonts"))
       "Misc"
       (("s" ha-eww-save-off-window "Rename")
        ("S" eww-switch-to-buffer "Switch to")
        ("-" eww-write-bookmarks "Save Bookmarks")
        ("M" eww-read-bookmarks "Load Bookmarks"))))

    :general
    (:states 'normal :keymaps 'eww-mode-map
             "q" 'bury-buffer
             "J" 'ha-eww-better-scroll)
    (: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 :tangle no
  (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
** SHRFace
Make my EWW browsers /look/ like an Org file with the [[https://github.com/chenyanming/shrface][shrface project]].

#+begin_src emacs-lisp
  (use-package shrface
    :straight (:host github :repo "chenyanming/shrface")
    :config
    (shrface-basic)
    ;; (shrface-trial)
    ;; (shrface-default-keybindings) ; setup default keybindings
    (setq shrface-href-versatile t)

    (major-mode-hydra-define+ eww-mode nil
      ("Headlines"
       (("j" shrface-next-headline "Next Heading" :color pink)
        ("k" shrface-previous-headline "Previous" :color pink)
        ("J" shrface-headline-consult "Goto Heading")))))
#+end_src

The following connection to EWW throws errors now. Hrm.

#+begin_src emacs-lisp :tangle no
  (use-package eww
    :after shrface
    :hook (eww-after-render #'shrface-mode))
#+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 :tangle no
  (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
* Dired
Allow me a confession. When renaming a file or flipping an executable bit, I don’t pull up =dired= as a first thought. But I feel like I should, as can do a lot of things quicker than pulling up a shell. Especially when working [[https://www.masteringemacs.org/article/working-multiple-files-dired][with multiple files]].
Most commands are /somewhat/ 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 it has /two actions/ … mark one or more files to do something, or /flag/ one or more files to delete them. Why two? Dunno. Especially since they act the same. For instance:

  1. Mark a few files with ~m~, and then type ~D~ to delete them, or …
  2. Flag a few files with ~d~, and then type ~x~ to delete them.

Seems the same to me. Especially since you can type ~u~ to unmark or unflag.

Few other commands to note:

  + ~m~ :: marks a single file
  + ~%~ :: will /mark/ a bunch of files based on a regular expression
  + ~u~ :: un-mark a file, or type ~U~ 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
  + ~D~ :: delete 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=

Couple useful settings:

#+begin_src emacs-lisp
  (setq delete-by-moving-to-trash t
        dired-auto-revert-buffer t
        dired-vc-rename-file t)  ; Why not mention to git when renaming?
#+end_src

My =ls= is an often alias and GNU’s =ls=, labeled  =gls= on my Mac, isn’t consistent between Mac and Linux, so I *don’t* do:
#+begin_src emacs-lisp :tangle no
  (setq insert-directory-program "gls")
#+end_src

Instead I use Emacs' built-in directory lister (which accepts the standard, =dired-listing-switches= to customize the output):

#+begin_src emacs-lisp
  (use-package ls-lisp
    :straight (:type built-in)
    :config
    (setq ls-lisp-use-insert-directory-program nil
          dired-listing-switches
          "-l --almost-all --human-readable --group-directories-first --no-group"))
#+end_src

And [[https://www.masteringemacs.org/article/dired-shell-commands-find-xargs-replacement][this article by Mickey Petersen]] convinced me to turn on the built-in =dired-x= (just have to tell [[file:bootstrap.org::*Introduction][straight]] that knowledge):

#+begin_src emacs-lisp
  (use-package dired-x
    :straight (:type built-in))
#+end_src

The advantage of =dired-x= is the ability to have [[https://www.emacswiki.org/emacs/DiredExtra#Dired_X][shell command guessing]] when selecting one or more files, and running a shell command on them with ~!~ or ~&~.

** Dirvish
The [[https://github.com/alexluigit/dirvish][dirvish]] project aims to make a prettier =dired=. And since the =major-mode= is still =dired-mode=, the decades of finger memory isn’t lost. 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 :tangle no
  (use-package dirvish
    :straight (:host github :repo "alexluigit/dirvish")
    :init (dirvish-override-dired-mode)

    :custom
    (dirvish-quick-access-entries
     '(("h" "~/"           "Home")
       ("e" "~/.emacs.d/" "Emacs user directory")
       ("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))

    (set-face-attribute 'dirvish-hl-line nil :background "darkmagenta"))
#+end_src

While in =dirvish-mode=, we can rebind some keys:
#+begin_src emacs-lisp :tangle no
  (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'
     (","   . dirvish-dispatch)
     ("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
** My Dired Interface
Because I can’t remember all the cool things =dired= can do, I put together a helper/cheatsheet. Typing ~,~ brings up a menu of possibilities (for others, I recommend [[https://github.com/kickingvegas/casual-dired][Casual Dired]]):

#+begin_src emacs-lisp
  (use-package major-mode-hydra
    :config
    (major-mode-hydra-define dired-mode (:quit-key "q")
      ("File"
       (("C" dired-do-copy "Copy")
        ("D" dired-do-delete "Delete")
        ("S" dired-do-symlink "Symlink")
        ("w" dired-copy-filename-as-kill "Copy name")
        ("!" dired-do-shell-command "Shell")
        ("&" dired-do-async-shell-command "Shell &"))   ; Really?
       "Change"
       (("R" dired-do-rename "Rename")
        ("M" dired-do-chmod "Mode")
        ("O" dired-do-chown "Owner")
        ("G" dired-do-chgrp "Group")
        ("T" dired-do-touch "Mod time"))
       "Directory"
       (("+" dired-create-directory "New")
        ("i" dired-insert-subdir "Insert subdir" :color pink)
        ("I" dired-hide-subdir "Hide subdir" :color pink)
        ("g" revert-buffer  "Refresh" :color pink)
        ("E" wdired-change-to-wdired-mode "Edit (wdired)"))
       "Mark"
       (("m" dired-mark "Mark" :color pink)
        ("u" dired-unmark "Unmark" :color pink)
        ("U" dired-unmark-all-marks "Unmark all" :color pink)
        ("t" dired-toggle-marks "Toggle marks" :color pink)
        ("~" dired-flag-backup-files "Mark backups" :color pink)
        ("r" hydra-dired-regexp-mark/body "Regexp »"))
       "Navigation"
       (("^" dired-up-directory "Up Directory")
        ("j" dired-next-line "Next File" :color pink)
        ("k" dired-previous-line "Previous File" :color pink)
        ("J" dired-next-subdir "Next subdir" :color pink)
        ("K" dired-previous-subdir "Previous subdir" :color pink))
       "Misc"
       (("x" hydra-dired-utils/body "Utils »")
        ("o" hydra-dired-toggles/body "Toggles »")
        ("a" dirvish-quick-access "Quick Access"))))

    ;; And some more hydras for the sub-menus:
    (pretty-hydra-define hydra-dired-regexp-mark (:color blue :hint nil)
      ("Mark files with regexp..."
       (("m" dired-mark-files-regexp "matching filenames")
        ("g" dired-mark-files-containing-regexp "containing text")
        ("d" dired-flag-files-regexp "to delete")
        ("c" dired-do-copy-regexp "to copy")
        ("r" dired-do-rename-regexp "to rename"))))

    (pretty-hydra-define hydra-dired-toggles (:color blue)
      ("Dired Toggles"
       (("d" dired-hide-details-mode "File details")
        ("h" dired-do-kill-lines "Hide marked")
        ("o" dired-omit-mode "Hide (omit) some?")
        ("T" image-dired "Image thumbnails"))))

    (pretty-hydra-define hydra-dired-utils (:color blue :hint nil)
      ("Files"
       (("f" dired-do-find-marked-files "open marked")
        ("z" dired-do-compress "(un)compress marked"))
       "Rename"
       (("u" dired-upcase "upcase")
        ("d" dired-downcase "downcase"))
       "Search"
       (("g" dired-do-find-regexp "grep marked")
        ("s" dired-do-isearch "isearch marked")) ; Maybe C-s ... even on top?
       "Replace"
       (("r" dired-do-find-regexp-and-replace "find/replace marked")
        ("R" dired-do-query-replace-regexp "query find/replace")))))
#+end_src

Notice ~E~ to turn on =wdired=, which brings =dired= to a whole new level.

I do want to change a couple of bindings, as ~j~ to pull up a =completing-read= interface for files, and then move the cursor to the on selected (why not just search) and ~k~ for /hiding/ marked files, aren’t very useful, compared to the finger memory I now have for using those two keys to move up and down lines.

#+begin_src emacs-lisp
  (define-key dired-mode-map (kbd "j") 'evil-next-line)
  (define-key dired-mode-map (kbd "k") 'evil-previous-line)
  (define-key dired-mode-map (kbd "/") 'isearch-forward)
  (define-key dired-mode-map (kbd "n") 'evil-search-next)
  (define-key dired-mode-map (kbd ",") 'major-mode-hydras/dired-mode/body)
#+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

* 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)

I’d 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
    (major-mode-hydra-define org-noter-doc-mode-map nil
      ("Notes"
       (("i" org-noter-insert-note "insert note")
        ("s" org-noter-sync-current-note "sync note")
        ("n" org-noter-sync-next-note "next note" :color pink)
        ("p" org-noter-sync-prev-note "previous note" :color pink)))))
#+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:t 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:
