#+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 ;; 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 " '(keyboard-escape-quit :which-key t) "g /" '("Magit dispatch" . magit-dispatch) "g ." '("Magit file dispatch" . magit-file-dispatch) "g b" '("Magit switch branch" . magit-branch-checkout) "g u" '("Git Update" . vc-update) "g g" '("Magit status" . magit-status) "g s" '("Magit status here" . magit-status-here) "g D" '("Magit file delete" . magit-file-delete) "g B" '("Magit blame" . magit-blame-addition) "g C" '("Magit clone" . magit-clone) "g F" '("Magit fetch" . magit-fetch) "g L" '("Magit buffer log" . magit-log-buffer-file) "g R" '("Revert file" . magit-file-checkout) "g S" '("Git stage file" . magit-stage-file) "g U" '("Git unstage file" . magit-unstage-file) "g f" '(:ignore t :which-key "find") "g f f" '("Find file" . magit-find-file) "g f g" '("Find gitconfig file" . magit-find-git-config-file) "g f c" '("Find commit" . magit-show-commit) "g l" '(:ignore t :which-key "list") "g l r" '("List repositories" . magit-list-repositories) "g l s" '("List submodules" . magit-list-submodules) "g o" '(:ignore t :which-key "open") "g c" '(:ignore t :which-key "create") "g c R" '("Initialize repo" . magit-init) "g c C" '("Clone repo" . magit-clone) "g c c" '("Commit" . magit-commit-create) "g c f" '("Fixup" . magit-commit-fixup) "g c b" '("Branch" . magit-branch-and-checkout)) (general-nmap "" #'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 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 ** 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 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)) (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) (: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 ** 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 And connect it to EWW: #+begin_src emacs-lisp (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 (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 make a prettier =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") ("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)) (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 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' ("," . 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 * 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) 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: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: