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
The [[https://scripter.co/using-git-delta-with-magit][magit-delta]] project uses [[https://github.com/dandavison/delta][git-delta]] for colorized diffs.
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.
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:
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 "*"))
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))
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:
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]]:
#+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:
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'."
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]]:
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:
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).
;; 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.
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.
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:
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~.