hamacs/ha-applications.org
Howard Abrams 71168110e0 Fixing my Markdown configuration a bit
Just need to get the fonts working better.
2024-09-02 15:57:51 -07:00

898 lines
40 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
;; 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
** 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
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
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
** Github Search?
Wanna see an example of how others 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, 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))
(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 dont 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 GNUs =ls=, labeled =gls= on my Mac, isnt consistent between Mac and Linux, so I *dont* 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 isnt lost. 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")
("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 cant 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, arent 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
* 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
(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: