diff --git a/README.org b/README.org index 1dfd4dd..9ca18fd 100644 --- a/README.org +++ b/README.org @@ -14,7 +14,7 @@ I’ve separated my configuration into /chapters/ around particular subjects, ap Hit me up with questions on Mastodon: [[https://emacs.ch/@howard][@howard@emacs.ch]]. -If you want to try the entire process, after installing Emacs, clone this repo with: +If you want to try the entire process, after installing Emacs (see my instructions for [[file:README-MacOS.org][both MacOS]] and [[file:README-Linux.org][Linux]]), clone this repo with: #+begin_src sh git clone https://github.com/howardabrams/hamacs #+end_src diff --git a/bootstrap.org b/bootstrap.org index ee077a9..c677239 100644 --- a/bootstrap.org +++ b/bootstrap.org @@ -196,6 +196,7 @@ The following loads the rest of my org-mode literate files. I add new filesas t (defvar ha-hamacs-files (flatten-list `("ha-private.org" "ha-config.org" + "ha-applications.org" ,(when (display-graphic-p) "ha-display.org") "ha-org.org" diff --git a/ha-applications.org b/ha-applications.org new file mode 100644 index 0000000..0993550 --- /dev/null +++ b/ha-applications.org @@ -0,0 +1,658 @@ +#+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 + ;; Maintainer: Howard X. Abrams + ;; 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: diff --git a/ha-config.org b/ha-config.org index 0aadd2e..569f8fc 100644 --- a/ha-config.org +++ b/ha-config.org @@ -667,11 +667,15 @@ Build the hydra as well as configure the =perspective= project. ("C-g" nil))) #+end_src -I have no idea why this binding doesn’t work /within/ the =use-package= declaration, but oh well… +Let’s give it a binding: #+begin_src emacs-lisp (ha-leader "TAB" '("workspaces" . hydra-workspace-leader/body)) #+end_src +When called, it /can/ look like: + +[[file:screenshots/projects-hydra.png]] + The /special/ perspective is a nice shortcut to the one I use the most: #+begin_src emacs-lisp (defun ha-switch-to-special () @@ -784,613 +788,6 @@ The =persp-switch= allows me to select or create a new project, but what if we i ((s-starts-with? "twit" name) (twit)))) #+end_src Once we create the new perspective workspace, if it matches a particular name, I pretty much know what function I would like to call. -* Applications -Can we call these /applications/? -** Magit -Can not live without [[https://magit.vc/][Magit]], a Git porcelain for Emacs. I stole the bulk of this work from Doom Emacs. -#+begin_src emacs-lisp - (use-package magit - ;; 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 -** 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 -** 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 -** 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 we can =require= this file: #+begin_src emacs-lisp :exports none diff --git a/screenshots/projects-hydra.png b/screenshots/projects-hydra.png new file mode 100644 index 0000000..caba040 Binary files /dev/null and b/screenshots/projects-hydra.png differ diff --git a/support/hamacs-logo.png b/support/hamacs-logo.png new file mode 100644 index 0000000..765f186 Binary files /dev/null and b/support/hamacs-logo.png differ