Skip to content

alhassy/emacs.d

Repository files navigation

A Life Configuring Emacs

Updated, pretty, version: https://alhassy.github.io/emacs.d/index.html

LifeMacs CI Status Badge


I enjoy reading others’ literate configuration files and incorporating what I learn into my own. The result is a sufficiently well-documented and accessible read that yields a stylish and functional system (•̀ᴗ•́)و

This README.org has been automatically generated from my configuration and its contents below are accessible in (outdated) blog format, with colour, or as colourful PDF, here. Enjoy

Abstract

Abstract

Herein I document the configurations I utilise with Emacs.

As a literate program file with Org-mode, I am ensured optimal navigation through my ever growing configuration files, ease of usability and reference for peers, and, most importantly, better maintainability for myself!

Dear reader, when encountering a foregin command X I encourage you to execute (describe-symbol 'X), or press C-h o with the cursor on X. An elementary Elisp Cheat Sheet can be found here and here is a 2-page 3-column Emacs Cheat Sheet of the bindings in “this” configuration.

  • C-h oWhat’s this thing?
  • C-h eWhat’d /Emacs/ do?
  • C-h lWhat’d /I/ do?
  • C-h ?What’re the help topics? —gives possible completions to “C-h ⋯”.
  • “I accidentally hit a key, which one and what did it do!?” ⇒ C-h e and C-h l, then use C-h o to get more details on the action. ;-)

Finally, C-h d asks nicely what ‘d’ocumentation you’re interested in. After providing a few keywords, the apropos tool yields possible functions and variables that may accomplish my goal.

Table of Contents

Booting Up

Let’s decide on where we want to setup our declarations for personalising Emacs to our needs. Then, let’s bootstrap Emacs’ primitive packaging mechanism with a slick interface —which not only installs Emacs packages but also programs at the operating system level, all from inside Emacs! Finally, let’s declare who we are and use that to setup Emacs email service.

~/.emacs vs. init.org

Emacs is extenible: When Emacs is started, it tried to load a user’s Lisp program known as a initialisation file which specfies how Emacs should look and behave for you. Emacs looks for the init file using the filenames ~/.emacs.el, ~/.emacs, or ~/.emacs.d/init.el —it looks for the first one that exists, in that order; at least it does so on my machine. Below we’ll avoid any confusion by ensuring that only one of them is in our system. Regardless, execute C-h o user-init-file to see the name of the init file loaded. Having no init file is tantamount to have an empty init file.

  • One can read about the various Emacs initialisation files online or within Emacs by the sequence C-h i m emacs RET i init file RET.
  • A friendly tutorial on ‘beginning a .emacs file’ can be read online or within Emacs by C-h i m emacs lisp intro RET i .emacs RET.
  • After inserting some lisp code and saving, such as (set-background-color "salmon"), one can load the changes with M-x eval-buffer.
  • In a terminal, use emacs -Q to open emacs without any initialisation files.

Besides writing Lisp in an init file, one may use Emacs’ customisation interface, M-x customize: Point and click to change Emacs to your needs. The resulting customisations are, by default, automatically thrown into your init file —=~/.emacs= is created for you if you have no init file. This interface is great for beginners, but one major drawback is that it’s a bit difficult to share settings since it’s not amicable to copy-pasting.

We shall use ~/.emacs.d/init.el as the initialisation file so that all of our Emacs related files live in the same directory: ~/.emacs.d/.

A raw code file is difficult to maintain, especially for a large system such as Emacs. Instead, we’re going with a ‘literate programming’ approach: The intialisation configuration is presented in an essay format, along with headings and subheadings, intended for consumption by humans such as myself, that, incidentally, can be ‘tangled’ into a raw code file that is comprehensible by a machine. We achieve this goal using org-mode —/Emacs’ killer app/— which is discussed in great detail later on.

Adventure time! “Honey, where’s my init?”

Let’s use the three possible locations for the initialisation files to explore how Emacs finds them. Make the following three files.

~/.emacs.el

;; Emacs looks for this first;
(set-background-color "chocolate3")
(message-box ".emacs.el says hello")

~/.emacs

;; else; looks for this one;
(set-background-color "plum4")
(message-box ".emacs says hello")

~/.emacs.d/init.el

;; Finally, if neither are found; it looks for this one.
(set-background-color "salmon")
(message-box ".emacs.d/init.el says hello")

Now restart your Emacs to see how there super tiny initilaisation files affect your editor. Delete some of these files in-order for others to take effect!

Adventure time! Using Emacs’ Easy Customisation Interface

We have chosen not to keep configurations in ~~/.emacs~ since Emacs may explicitly add, or alter, code in it.

Let’s see this in action!

Execute the following to see additions to the ~~/.emacs~ have been added by ‘custom’.

  1. M-x customize-variable RET line-number-mode RET
  2. Then press: toggle, state, then 1.
  3. Now take a look: C-x C-f ~/.emacs

Support for ‘Custom’

Let the Emacs customisation GUI insert configurations into its own file, not touching or altering my initialisation file. For example, I tend to have local variables to produce README.org’s and other matters, so Emacs’ Custom utility will remember to not prompt me each time for the safety of such local variables.

(setq custom-file "~/.emacs.d/custom.el")
(ignore-errors (load custom-file)) ;; It may not yet exist.

Speaking of local variables, let’s always ones we’ve already marked as safe —see the bottom of the source of this file for an example of local variables. ( At one point, all my files had locals! )

(setq enable-local-variables :safe)

use-package —The start of init.el

There are a few ways to install packages —run C-h C-e for a short overview. The easiest, for a beginner, is to use the command package-list-packages then find the desired package, press i to mark it for installation, then install all marked packages by pressing x.

  • Interactively: M-x list-packages to see all melpa packages that can install
    • Press Enter on a package to see its description.
  • Or more quickly, to install, say, the haskell mode: M-x package-install RET unicode-fonts RET.

“From rags to riches”: Recently I switched to Mac —first time trying the OS. I had to do a few package-install’s and it was annoying. I’m looking for the best way to package my Emacs installation —including my installed packages and configuration— so that I can quickly install it anywhere, say if I go to another machine. It seems use-package allows me to configure and auto install packages. On a new machine, when I clone my .emacs.d and start Emacs, on the first start it should automatically install and compile all of my packages through use-package when it detects they’re missing.

First we load package, the built-in package manager. It is by default only connected to the GNU ELPA (Emacs Lisp Package Archive) repository, so we extended it with other popular repositories; such as the much larger MELPA (Milkypostman’s ELPA) —it builds packages directly from the source-code reposistories of developers, rather than having all packages in one repository.

;; Make all commands of the “package” module present.
(require 'package)

;; Internet repositories for new packages.
(setq package-archives '(("org"       . "http://orgmode.org/elpa/")
                         ("gnu"       . "http://elpa.gnu.org/packages/")
                         ("melpa"     . "http://melpa.org/packages/")))

;; Actually get “package” to work.
(package-initialize)
(package-refresh-contents)
  • All installed packages are placed, by default, in ~/.emacs.d/elpa.
  • Neato: If one module requires others to run, they will be installed automatically.

The declarative configuration tool use-package is a macro/interface that manages other packages and the way they interact.

  • It allows us to tersely organise a package’s configuration.
    • By default, (use-package foo) only loads a package, if it’s on our system.
      • Use the standalone keyword :disabled to turn off loading a module that, say, you’re not using anymore.
  • It is not a package manger, but we can make it one by having it automatically install modules, via Emacs packing mechanism, when they’re not in our system.

    We achieve this by using the keyword option :ensure t.

  • Here are common keywords we will use, in super simplified terms.
    • :init f₁ … fₙ Always executes code forms fᵢ before loading a package.
    • :diminish str Uses optional string str in the modeline to indicate this module is active. Things we use often needn’t take real-estate down there and so no we provide no str.
    • :config f₁ … fₙ Only executes code forms fᵢ after loading a package.

      The remaining keywords only take affect after a module loads.

    • :bind ((k₁ . f₁) … (kₙ . fₙ) Lets us bind keys kᵢ, such as "M-s o", to functions, such as occur.
      • When n = 1, the extra outer parenthesis are not necessary.
    • :hook ((m₁ … mₙ) . f) Enables functionality f whenever we’re in one of the modes mᵢ, such as org-mode. The . f, along with the outermost parenthesis, is optional and defaults to the name of the package —Warning: Erroneous behaviour happens if the package’s name is not a function provided by the package; a common case is when package’s name does not end in -mode, leading to the invocation ((m₁ … mₙ) . <whatever-the-name-is>-mode) instead.

      Additionally, when n = 1, the extra outer parenthesis are not necessary.

      Outside of use-package, one normally uses a add-hook clause. Likewise, an ‘advice’ can be given to a function to make it behave differently —this is known as ‘decoration’ or an ‘attribute’ in other languages.

    • :custom (k₁ v₁ d₁) … (kₙ vₙ dₙ) Sets a package’s custom variables kᵢ to have values vᵢ, along with optional user documentation dᵢ to explain to yourself, in the future, why you’ve made this decision.

      This is essentially setq within :config.

We now bootstrap use-package.

(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(require 'use-package)

We can now invoke (use-package XYZ :ensure t) which should check for the XYZ package and make sure it is accessible. If not, the :ensure t part tells use-package to download it —using the built-in package manager— and place it somewhere accessible, in ~/.emacs.d/elpa/ by default. By default we would like to download packages, since I do not plan on installing them manually by downloading Lisp files and placing them in the correct places on my system.

(setq use-package-always-ensure t)

The use of :ensure t only installs absent modules, but it does no updating. Let’s set up an auto-update mechanism.

(use-package auto-package-update
  :defer 10
  :config
  ;; Delete residual old versions
  (setq auto-package-update-delete-old-versions t)
  ;; Do not bother me when updates have taken place.
  (setq auto-package-update-hide-results t)
  ;; Update installed packages at startup if there is an update pending.
  (auto-package-update-maybe))

Here’s another example use of use-package. Later on, I have a “show recent files pop-up” command set to C-x C-r; but what if I forget? This mode shows me all key completions when I type C-x, for example. Moreover, I will be shown other commands I did not know about! Neato :-)

;; Making it easier to discover Emacs key presses.
(use-package which-key
  :diminish
  :defer 5
  :config (which-key-mode)
          (which-key-setup-side-window-bottom)
          (setq which-key-idle-delay 0.05))

⟨ Honestly, I seldom even acknowledge this pop-up; but it’s always nice to show to people when I’m promoting Emacs. ⟩

Above, the :diminish keyword indicates that we do not want the mode’s name to be shown to us in the modeline —the area near the bottom of Emacs. It does so by using the diminish package, so let’s install that.

(use-package diminish
  :defer 5
  :config ;; Let's hide some markers.
    (diminish  'org-indent-mode))

Here are other packages that I want to be installed onto my machine.

;; Efficient version control.
;;
;; Bottom of Emacs will show what branch you're on
;; and whether the local file is modified or not.
(use-package magit
  :config (global-set-key (kbd "C-x g") 'magit-status))

(use-package htmlize :defer t)
;; Main use: Org produced htmls are coloured.
;; Can be used to export a file into a coloured html.

;; Get org-headers to look pretty! E.g., * → ⊙, ** ↦ ◯, *** ↦ ★
;; https://github.com/emacsorphanage/org-bullets
(use-package org-bullets
  :hook (org-mode . org-bullets-mode))

;; Haskell's cool
(use-package haskell-mode :defer t)

;; Lisp libraries with Haskell-like naming.
(use-package dash)    ;; “A modern list library for Emacs”
(use-package s   )    ;; “The long lost Emacs string manipulation library”.

;; Library for working with system files;
;; e.g., f-delete, f-mkdir, f-move, f-exists?, f-hidden?
(use-package f)

Note:

  • dash: “A modern list library for Emacs”
    • E.g., (--filter (> it 10) (list 8 9 10 11 12))
  • s: “The long lost Emacs string manipulation library”.
    • E.g., s-trim, s-replace, s-join.

Remember that snippet for undo-tree in the introductory section? Let’s activate it now, after use-package has been setup.

<<undo-tree-setup>>

Finally, let’s try our best to have a useful & consistent commit log:

(defun my/git-commit-reminder ()
  (insert "\n\n# The commit subject line ought to finish the phrase:
# “If applied, this commit will ⟪your subject line here⟫.” ")
  (beginning-of-buffer))

(add-hook 'git-commit-setup-hook 'my/git-commit-reminder)

Super neat stuff!

README —From init.org to init.el

Rather than manually extracting the Lisp code from this literate document each time we alter it, let’s instead add a ‘hook’ —a method that is invoked on a particular event, in this case when we save the file. More precisely, in this case, C-x C-s is a normal save whereas C-u C-x C-s is a save after forming init.elc and README.md.

The my/make-init-el-and-README function

We ‘hook on’ the following function to the usual save method that is associated with this file only.

  (defun my/make-init-el-and-README ()
    "Tangle an el and a github README from my init.org."
    (interactive "P") ;; Places value of universal argument into: current-prefix-arg
    (when current-prefix-arg
      (let* ((time      (current-time))
             (_date     (format-time-string "_%Y-%m-%d"))
             (.emacs    "~/.emacs")
             (.emacs.el "~/.emacs.el"))
        ;; Make README.org
        (save-excursion
          (org-babel-goto-named-src-block "make-readme") ;; See next subsubsection.
          (org-babel-execute-src-block))

        ;; remove any other initialisation file candidates
        (ignore-errors
          (f-move .emacs    (concat .emacs _date))
          (f-move .emacs.el (concat .emacs.el _date)))

        ;; Make init.el
        (org-babel-tangle)
        ;; (byte-compile-file "~/.emacs.d/init.el")
        (load-file "~/.emacs.d/init.el")

        ;; Acknowledgement
        (message "Tangled, compiled, and loaded init.el; and made README.md … %.06f seconds"
                 (float-time (time-since time))))))

(add-hook 'after-save-hook 'my/make-init-el-and-README nil 'local-to-this-file-please)

The Org-block named make-readme

Where the following block has #+NAME: make-readme before it. This source block generates the README for the associated Github repository.

(save-buffer)
(with-temp-buffer
    (insert
    "#+EXPORT_FILE_NAME: README.org

     # Logos and birthday present painting
     #+HTML:" (s-collapse-whitespace (concat
    " <p align=\"center\">
       <img src=\"images/emacs-logo.png\" width=150 height=150/>
     </p>

     <p align=\"center\">
        <a href=\"https://www.gnu.org/software/emacs/\">
             <img src=\"https://img.shields.io/badge/GNU%20Emacs-" emacs-version "-b48ead.svg?style=plastic\"/></a>
        <a href=\"https://orgmode.org/\"><img src=\"https://img.shields.io/badge/org--mode-" org-version "-489a9f.svg?style=plastic\"/></a>
     </p>

     <p align=\"center\">
       <img src=\"images/emacs-birthday-present.png\" width=250 height=250/>
     </p>
    "))

   ;; My Literate Setup; need the empty new lines for the export
   "

     I enjoy reading others' /literate/ configuration files and
     incorporating what I learn into my own. The result is a
     sufficiently well-documented and accessible read that yields
     a stylish and functional system (•̀ᴗ•́)و

     This ~README.org~ has been automatically generated from my
     configuration and its contents below are accessible
     in (outdated) blog format, with /colour/, or as colourful
     PDF, [[https://alhassy.github.io/init/][here]]. Enjoy
     :smile:

     #+INCLUDE: init.org
    ")

    ;; No code execution on export
    ;; ⟪ For a particular block, we use “:eval never-export”. ⟫
    (let ((org-export-use-babel nil))
      (org-mode)
      (org-org-export-to-org)))

Alternatively, evaluate the above source block with C-c C-c to produce a README file.

For the ‘badges’, see https://shields.io/. The syntax above is structured:

https://img.shields.io/badge/<LABEL>-<MESSAGE>-<COLOR>.svg

‘Table of Contents’ for Org vs. Github

The above mentioned package toc-org, which creates an up-to-date table of contents in an org file, at any heading tagged :TOC:. It’s useful primarily for README files on Github. There is also org-make-toc, which is more flexible: The former provides only a top-level TOC; whereas this package allows TOCs at the sibling level, say, to produce a TOC of only the subsections of a particular heading, and other TOC features. Unlike toc-org, org-make-toc uses property drawers to designate TOC matter.

(use-package toc-org
  ;; Automatically update toc when saving an Org file.
  :hook (org-mode . toc-org-mode)
  ;; Use both “:ignore_N:” and ":export_N:” to exlude headings from the TOC.
  :custom (toc-org-noexport-regexp
           "\\(^*+\\)\s+.*:\\(ignore\\|noexport\\)\\([@_][0-9]\\)?:\\($\\|[^ ]*?:$\\)"))

However, toc-org produces broken links for numbered sections. That is, if we use #+OPTIONS: num:t then a section, say ** =~/.emacs= vs. =init.org= as the first subheading of the third heading, then it renders with the text preceeded by 3.1. On the left-most part of the heading, Github provides a a link option; clicking provides a link to this exact location in the README, changing the current URL to something like https://github.com/alhassy/emacs.d#31-emacs-vs-initorg. Now, toc-org produces Github-style anchors from Org headings, but does not account for numbers, and so gives us https://github.com/alhassy/emacs.d#emacs-vs-initorg, which is so close but missing the translated number, 31.

I’ve experimented with using toc-org links using org-style, instead of the default Github style, but it seems that the org-style completely breaks rendering the resulting readme. Likewise, it seems that headings that are links break the TOC link; whence my section on the Reveal slide-deck system has a broken link to it. Perhaps org-make-toc solves these issues —something to look into.

I’m not sure how I feel about actually having the Github-serving TOC in my source file. It’s nice to have around, from an essay-perspecive, but it breaks HTML export since its links are not well-behaved; e.g., :ignore:-ed headlines appear in the toc, but do not link to any visible heading in the HTML; likewise, headings with URLS in their names break. As such, below I’ve developed a way to erase it altogether —alternatively, one could mark the toc as :noexport:, but this would then, in my current approach, not result in a toc in the resulting README.

(cl-defun my/org-replace-tree-contents (heading &key (with "") (offset 0))
  "Replace the contents of org tree HEADING with WITH, starting at OFFSET.

Clear a subtree leaving first 3 lines untouched  ⇐  :offset 3
Deleting a tree & its contents                   ⇐  :offset -1, or any negative number.
Do nothing to a tree of 123456789 lines          ⇐  :offset 123456789

Precondition: offset < most-positive-fixnum; else we wrap to a negative number."
  (interactive)
  (save-excursion
    (beginning-of-buffer)
    (re-search-forward (format "^\\*+ %s" (regexp-quote heading)))
    ;; To avoid ‘forward-line’ from spilling onto other trees.
    (org-narrow-to-subtree)
    (org-mark-subtree)
    ;; The 1+ is to avoid the heading.
    (dotimes (_ (1+ offset)) (forward-line))
    (delete-region (region-beginning) (region-end))
    (insert with)
    (widen)))

;; Erase :TOC: body ---provided we're using toc-org.
;; (my/org-replace-tree-contents "Table of Contents")

Alternate approaches to generating a README

Github supports several markup languages, one of which is Org-mode.

  • It seems that Github uses org-ruby to convert org-mode to html.
  • Here is a repo demonstrating how Github interprets Org-mode files.
  • org-ruby supports inline #+HTML but not html blocks.

It seems coloured HTML does not render well:

(org-html-export-to-html)
(shell-command "mv README.html README.md")

JavaScript supported display of web pages with:

#+INFOJS_OPT: view:info toc:t buttons:t

This looks nice for standalone pages, but doesn’t incorporate nicely with github README.org.

Usually, Github readme files are in markdown, which we may obtain from an Org file with M-x org-md-export-to-markdown.

  • [ ] By default, this approach results in grey-coloured source blocks —eek!
  • [X] It allows strategic placement of a table of contents.

    Declare #+options: toc:nil at the top of the Org file, then have #+TOC: headlines 2 in a strategic position for a table of contents, say after a brief explanation of what the readme is for.

  • [X] It allows us to preview the readme locally before comitting, using grip.
;; grip looks for README.md
(system-packages-ensure "grip")
;; Next: (async-shell-command "cd ~/.emacs.d/; grip")

We can approximate this behaviour for the other approaches:

  1. Export to markdown.
  2. COMMENT-out any :TOC:-tagged sections —their links are not valid markdown links, since they don’t refer to any markdown labels.
  3. Rename the exported file to README.md.
  4. Run grip.

Installing Emacs packages directly from source

Quelpa allows us to build Emacs packages directly from source repositories. It derives its name from the German word Quelle, for souce [code], adjoined to ELPA. Its use-package interface allows us to use use-package like normal but when we want to install a file from souce we use the keyword :quelpa.

(use-package quelpa
  :defer 5
  :custom (quelpa-upgrade-p t "Always try to update packages")
  :config
  ;; Get ‘quelpa-use-package’ via ‘quelpa’
  (quelpa
   '(quelpa-use-package
     :fetcher git
     :url "https://github.com/quelpa/quelpa-use-package.git"))
  (require 'quelpa-use-package))

Let’s use this to obtain an improved info-mode from the EmacsWiki. [Disabled for now]

(use-package info+
  :disabled
  :quelpa (info+ :fetcher wiki :url "https://www.emacswiki.org/emacs/info%2b.el"))

magit —Emacs’ porcelain interface to gitq

Let’s setup an Emacs ‘porcelain’ interface to git —it makes working with version control tremendously convenient. Moreover, I add a little pop-up so that I don’t forget to commit often!

Why use magit as the interface to the git version control system? In magit buffer nearly everything can be acted upon: Press return, or space, to see details and tab to see children items, usually.

First, let’s setup our git credentials.

;; See here for a short & useful tutorial:
;; https://alvinalexander.com/git/git-show-change-username-email-address
(when (equal ""
(shell-command-to-string "git config user.name"))
  (shell-command "git config --global user.name \"Musa Al-hassy\"")
  (shell-command "git config --global user.email \"alhassy@gmail.com\""))

Below is my personal quick guide to working with magit —for a full tutorial see jr0cket’s blog.

dired
See the contents of a particular directory.
magit-init
Put a project under version control. The mini-buffer will prompt you for the top level folder version. A .git folder will be created there.
magit-status , C-x g
See status in another buffer. Press ? to see options, including:
g
Refresh the status buffer.
TAB
See collapsed items, such as what text has been changed.
q
Quit magit, or go to previous magit screen.
s
Stage, i.e., add, a file to version control. Add all untracked files by selecting the Untracked files title.

The staging area is akin to a pet store; commiting is taking the pet home.

k
Kill, i.e., delete a file locally.
K
This’ (magit-file-untrack) which does git rm --cached.
i
Add a file to the project .gitignore file. Nice stuff =)
u
Unstage a specfif staged change highlighed by cursor. C-u s stages everything –tracked or not.
c
Commit a change.
  • A new buffer for the commit message appears, you write it then commit with C-c C-c or otherwise cancel with C-c C-k. These commands are mentioned to you in the minibuffer when you go to commit.
  • You can provide a commit to each altered chunk of text! This is super neat, you make a series of local such commits rather than one nebulous global commit for the file. The magit interface makes this far more accessible than a standard terminal approach!
  • You can look at the unstaged changes, select a region, using C-SPC as usual, and commit only that if you want!
  • When looking over a commit, M-p/n to efficiently go to previous or next altered sections.
  • Amend a commit by pressing a on HEAD.
d
Show differences, another d or another option.
  • This is magit! Each hunk can be acted upon; e.g., s or c or k ;-)
v
Revert a commit.
x
Undo last commit. Tantamount to git reset HEAD~ when cursor is on most recent commit; otherwise resets to whatever commit is under the cursor.
l
Show the log, another l for current branch; other options will be displayed.
  • Here space shows details in another buffer while cursour remains in current buffer and, moreover, continuing to press space scrolls through the other buffer! Neato.
P
Push.
F
Pull.
:
Execute a raw git command; e.g., enter whatchanged.

Notice that every time you press one of these commands, a ‘pop-up’ of realted git options appears! Thus not only is there no need to memorise many of them, but this approach makes discovering other commands easier.

Below are the git repos I’d like to clone —along with a function to do so quickly.

(use-package magit
  :defer t
  :custom ;; Do not ask about this variable when cloning.
          (magit-clone-set-remote.pushDefault t))

(cl-defun maybe-clone (remote &optional (local (concat "~/" (file-name-base remote))))
  "Clone a REMOTE repository if the LOCAL directory does not exist.

Yields ‘repo-already-exists’ when no cloning transpires,
otherwise yields ‘cloned-repo’.

LOCAL is optional and defaults to the base name; e.g.,
if REMOTE is https://github.com/X/Y then LOCAL becomes ~/Y."
  (if (file-directory-p local)
      'repo-already-exists
    (async-shell-command (concat "git clone " remote " " local))
    (add-to-list 'magit-repository-directories `(,local   . 0))
    'cloned-repo))

(maybe-clone "https://github.com/alhassy/emacs.d" "~/.emacs.d")
(maybe-clone "https://github.com/alhassy/alhassy.github.io")
(maybe-clone "https://github.com/alhassy/CheatSheet")
(maybe-clone "https://github.com/alhassy/ElispCheatSheet")
(maybe-clone "https://github.com/alhassy/CatsCheatSheet")
(maybe-clone "https://github.com/alhassy/islam")

;; For brevity, many more ‘maybe-clone’ clauses are hidden in the source file.

Let’s always notify ourselves of a file that has uncommited changes —we might have had to step away from the computer and forgotten to commit.

(require 'magit-git)

(defun my/magit-check-file-and-popup ()
  "If the file is version controlled with git
  and has uncommitted changes, open the magit status popup."
  (let ((file (buffer-file-name)))
    (when (and file (magit-anything-modified-p t file))
      (message "This file has uncommited changes!")
      (when nil ;; Became annyoying after some time.
      (split-window-below)
      (other-window 1)
      (magit-status)))))

;; I usually have local variables, so I want the message to show
;; after the locals have been loaded.
(add-hook 'find-file-hook
  '(lambda ()
      (add-hook 'hack-local-variables-hook 'my/magit-check-file-and-popup)))

Finally, one of the main points for using version control is to have access to historic versions of a file. The following utility allows us to M-x git-timemachine on a file and use p/n/g/q to look at previous, next, goto arbitrary historic versions, or quit.

(use-package git-timemachine :defer t)

If we want to roll back to a previous version, we just write-file or C-x C-s as usual! The power of text!

Syncing to the System’s $PATH

For one reason or another, on OS X it seems that an Emacs instance begun from the terminal may not inherit the terminal’s environment variables, thus making it difficult to use utilities like pdflatex when Org-mode attempts to produce a PDF.

(use-package exec-path-from-shell
  :init
  (when (memq window-system '(mac ns x))
    (exec-path-from-shell-initialize)))

See the exec-path-from-shell documentation for setting other environment variables.

Installing OS packages, and automatically keeping my system up to data, from within Emacs

Sometimes Emacs packages depend on existing system binaries, use-package let’s us ensure these exist using the :ensure-system-package keyword extension.

  • This is like :ensure t but operates at the OS level and uses your default OS package manager.

Let’s obtain the extension.

;; Auto installing OS system packages
(use-package use-package-ensure-system-package
  :defer 5
  :config (system-packages-update))

;; Ensure our operating system is always up to date.
;; This is run whenever we open Emacs & so wont take long if we're up to date.
;; It happens in the background ^_^
;;
;; After 5 seconds of being idle, after starting up.

After an update to Mac OS, one may need to restore file system access privileges to Emacs.

Here’s an example use for Emacs packages that require OS packages:

(shell-command-to-string "type rg") ;; ⇒ rg not found
(use-package rg
  :ensure-system-package rg) ;; ⇒ There's a buffer *system-packages*
                             ;;   installing this tool at the OS level!

If you look at the *Messages* buffer, via C-h e, on my machine it says brew install rg: finished —it uses brew which is my OS package manager!

The extension makes use of system-packages; see its documentation to learn more about managing installed OS packages from within Emacs. This is itself a powerful tool, however it’s interface M-x system-packages-install leaves much to be desired —namely, tab-compleition listing all available packages, seeing their descriptions, and visiting their webpages. This is remedied by M-x helm-system-packages then RET to see a system package’s description, or TAB for the other features! This is so cool!

;; An Emacs-based interface to the package manager of your operating system.
(use-package helm-system-packages :defer t)

The Helm counterpart is great for discovarability, whereas the plain system-packages is great for programmability.

It is tedious to arrange my program windows manually, and as such I love tiling window managers, which automatically arrange them. I had been using xmonad until recently when I obtained a Mac machine and now use Amethyst —“Tiling window manager for macOS along the lines of xmonad.”

;; Unlike the Helm variant, we need to specify our OS pacman.
(setq system-packages-package-manager 'brew)
;; Use “brew cask install” instead of “brew install” for installing programs.
(setf (nth 2 (assoc 'brew system-packages-supported-package-managers))
      '(install . "brew cask install"))

;; If the given system package doesn't exist; install it.
(system-packages-ensure "amethyst")

Neato! Now I can live in Emacs even more ^_^

“Being at the Helm” —Completion & Narrowing Framework

Whenever we have a choice to make from a list, Helm provides possible completions and narrows the list of choices as we type. This is extremely helpful for when switching between buffers, C-x b, and discovering & learning about other commands! E.g., press M-x to see recently executed commands and other possible commands! Press M-x and just start typing, methods mentioning what you’ve typed are suddenly listed!

Remembrance comes with time, until then ask Emacs!

Try and be grateful!

(use-package helm
 :diminish
 :init (helm-mode t)
 :bind (("M-x"     . helm-M-x)
        ("C-x C-f" . helm-find-files)
        ("C-x b"   . helm-mini)     ;; See buffers & recent files; more useful.
        ("C-x r b" . helm-filtered-bookmarks)
        ("C-x C-r" . helm-recentf)  ;; Search for recently edited files
        ("C-c i"   . helm-imenu)
        ("C-h a"   . helm-apropos)
        ;; Look at what was cut recently & paste it in.
        ("M-y" . helm-show-kill-ring)

        :map helm-map
        ;; We can list ‘actions’ on the currently selected item by C-z.
        ("C-z" . helm-select-action)
        ;; Let's keep tab-completetion anyhow.
        ("TAB"   . helm-execute-persistent-action)
        ("<tab>" . helm-execute-persistent-action)))

Helm provides generic functions for completions to replace tab-completion in Emacs with no loss of functionality.

  • The execute-extended-command, the default “M-x”, is replaced with helm-M-x which shows possible command completions.

    Likewise with apropos, which is helpful for looking up commands. It shows all meaningful Lisp symbols whose names match a given pattern.

  • The ‘Helm-mini’, C-x b, shows all buffers, recently opened files, bookmarks, and allows us to create new bookmarks and buffers!
  • The ‘Helm-imenu’, C-c i, yields a a menu of all “top-level items” in a file; e.g., functions and constants in source code or headers in an org-mode file.

    ⟳ Nifty way to familarise yourself with a new code base, or one from a while ago.

  • When Helm is active, C-x lists possible course of actions on the currently selected item.

When helm-mode is enabled, even help commands make use of it. E.g., C-h o runs describe-symbol for the symbol at point, and C-h w runs where-is to find the key binding of the symbol at point. Both show a pop-up of other possible commands.

Here’s a nifty tutorial: A package in a league of its own: Helm

Let’s ensure C-x b shows us: Current buffers, recent files, and bookmarks —as well as the ability to create bookmarks, which is via C-x r b manually. For example, I press C-x b then type any string and will have the option of making that a bookmark referring to the current location I’m working in, or jump to it if it’s an existing bookmark, or make a buffer with that name, or find a file with that name.

(setq helm-mini-default-sources '(helm-source-buffers-list
                                    helm-source-recentf
                                    helm-source-bookmarks
                                    helm-source-bookmark-set
                                    helm-source-buffer-not-found))

Incidentally, Helm even provides an interface for the top program via helm-top. It also serves as an interface to popular search engines and over 100 websites such as google, stackoverflow, ctan, and arxiv.

(system-packages-ensure "surfraw")
; ⇒  “M-x helm-surfraw” or “C-x c s”

If we want to perform a google search, with interactive suggestions, then invoke helm-google-suggest —which can be acted for other serves, such as Wikipedia or Youtube by C-z. For more google specific options, there is the google-this package.

Let’s switch to a powerful searching mechanism – helm-swoop. It allows us to not only search the current buffer but also the other buffers and to make live edits by pressing C-c C-e when a search buffer exists. Incidentally, executing C-s on a word, region, will search for that particular word, region; then make changes with C-c C-e and apply them by C-x C-s.

(use-package helm-swoop
  :bind  (("C-s"     . 'helm-swoop)           ;; search current buffer
          ("C-M-s"   . 'helm-multi-swoop-all) ;; Search all buffer
          ;; Go back to last position where ‘helm-swoop’ was called
          ("C-S-s" . 'helm-swoop-back-to-last-point)
          ;; swoop doesn't work with PDFs, use Emacs' default isearch instead.
          :map pdf-view-mode-map
          ("C-s" . isearch-forward))
  :custom (helm-swoop-speed-or-color nil "Give up colour for speed.")
          (helm-swoop-split-with-multiple-windows nil "Do not split window inside the current window."))
  • C-u 𝓃 C-s does a search but showing 𝓃 contextual lines!
  • helm-multi-swoop-all, C-M-s, lets us grep files anywhere!

Finally, note that there is now a M-x helm-info command to show documentation, possibly with examples, of the packages installed. For example, M-x helm-info RET dash RET -parition RET to see how the parition function from the dash library works via examples ;-)

Having a workspace manager in Emacs

I’ve loved using XMonad as a window tiling manager. I’ve enjoyed the ability to segregate my tasks according to what ‘project’ I’m working on; such as research, marking, Emacs play, etc. With perspective, I can do the same thing :-)

That is, I can have a million buffers, but only those that belong to a workspace will be visible when I’m switching between buffers, for example. ( The awesome-tab and centaur-tab, mentioned elsewhere here, can be used to achieve the same thing by ‘grouping buffers together’. )

(use-package perspective
  :defer t
  :config ;; Activate it.
          (persp-mode)
          ;; In the modeline, tell me which workspace I'm in.
          (persp-turn-on-modestring))

All commands are prefixed by C-x x; main commands:

s, n/→, p/←
‘S’elect a workspace to go to or create it, or go to ‘n’ext one, or go to ‘p’revious one.
c
Query a perspective to kill.
r
Rename a perspective.
A
Add buffer to current perspective & remove it from all others.

As always, since we’ve installed which-key, it suffices to press C-x x then look at the resulting menu 😃

Excellent PDF Viewer

Let’s install the pdf-tools library for viewing PDFs in Emacs.

(use-package pdf-tools
  :defer t
  ; :init   (system-packages-ensure "pdf-tools")
  :custom (pdf-tools-handle-upgrades nil)
          (pdf-info-epdfinfo-program "/usr/local/bin/epdfinfo")
  :config (pdf-tools-install))

;; Now PDFs opened in Emacs are in pdfview-mode.

Besides the expected PDF viewing utilities, such as search, annotation, and continuous scrolling; with a simple mouse right-click, we can even select a ‘midnight’ rendering mode which may be easier on the eyes. For more, see the brief pdf-tools-tourdeforce demo.

Who am I? —Using Gnus for Gmail

Let’s set the following personal Emacs-wide variables —to be used in other locations besides email.

(setq user-full-name    "Musa Al-hassy"
      user-mail-address "alhassy@gmail.com")

For some fun, run this cute method.

(animate-birthday-present user-full-name)

By default, in Emacs, we may send mail: Write it in Emacs with C-x m, then press C-c C-c to have it sent via your OS’s default mailing system —mine appears to be Gmail via the browser. Or cancel sending mail with C-c C-k —the same commands for org-capturing, discussed below (•̀ᴗ•́)و

To send and read email in Emacs we use GNUS, which, like GNU itself, is a recursive acronym: GNUS Network User Service.

  1. Execute, rather place in your init:
    (setq message-send-mail-function 'smtpmail-send-it)
        

    Revert to the default OS mailing method by setting this variable to mailclient-send-it.

  2. Follow only the quickstart here; namely, make a file named ~~/.gnus~ containing:
         ;; user-full-name and user-mail-address should be defined
    
    (setq gnus-select-method
          '(nnimap "gmail"
                   (nnimap-address "imap.gmail.com")
                   (nnimap-server-port "imaps")
                   (nnimap-stream ssl)))
    
    (setq smtpmail-smtp-server "smtp.gmail.com"
          smtpmail-smtp-service 587
          gnus-ignored-newsgroups "^to\\.\\|^[0-9. ]+\\( \\|$\\)\\|^[\"]\"[#'()]")
        
  3. Enable “2 step authentication” for Gmail following these instructions.
  4. You will then obtain a secret password, the x marks below, which you insert in a file named ~~/.authinfo~ as follows —using your email address.
    machine imap.gmail.com login alhassy@gmail.com password xxxxxxxxxxxxxxxx port imaps
    machine smtp.gmail.com login alhassy@gmail.com password xxxxxxxxxxxxxxxx port 587
        
  5. In Emacs, M-x gnus to see what’s there.

    Or compose mail with C-x m then send it with C-c C-c.

    • Press C-h m to learn more about message mode for mail composition; or read the Message Manual.
;; After startup, if Emacs is idle for 10 seconds, then start Gnus.
;; Gnus is slow upon startup since it fetches all mails upon startup.
;(run-with-idle-timer 10 nil #'gnus)

Learn more by reading The Gnus Newsreader Manual; also available within Emacs by C-h i m gnus (•̀ᴗ•́)و

EmacsWiki has a less technical and more user friendly tutorial.

Prettifications

Let’s add the icon  near my mail groups ^_^

;; Fancy icons for Emacs
;; Only do this once:
(use-package all-the-icons :defer t)
  ; :config (all-the-icons-install-fonts 'install-without-asking)

;; Make mail look pretty
(use-package all-the-icons-gnus
  :defer t
  :config (all-the-icons-gnus-setup))

;; While we're at it: Make dired, ‘dir’ectory ‘ed’itor, look pretty
(use-package all-the-icons-dired
  :hook (dired-mode . all-the-icons-dired-mode))

Next, let’s paste in some eye-candy for Gnus:

(setq gnus-sum-thread-tree-vertical        ""
      gnus-sum-thread-tree-leaf-with-other "├─► "
      gnus-sum-thread-tree-single-leaf     "╰─► "
      gnus-summary-line-format
      (concat
       "%0{%U%R%z%}"
       "%3{│%}" "%1{%d%}" "%3{│%}"
       "  "
       "%4{%-20,20f%}"
       "  "
       "%3{│%}"
       " "
       "%1{%B%}"
       "%s\n"))

Super Terse Tutorial

⟨ See the GNUS Reference Card! ⟩

In gnus, by default items you’ve looked at disappear —i.e., are archived. They can still be viewed in, say, your online browser if you like. In the Group view, R resets gnus, possibly retriving mail or alterations from other mail clients. q exits gnus in Group mode, q exits the particular view to go back to summary mode. Only after pressing q from within a group do changes take effect on articles —such as moves, reads, deletes, etc.

  • Expected keys: RET enter/open an item, q quit and return to previous view, g refresh view —i.e., ‘g’et new articles.
  • RET: Enter a group by pressing, well, the enter key.
    • Use SPC to open a group and automatically one first article there.
    • Use C-u RET to see all mail in a folder instead of just unread mail.
  • Only groups/folders with unread mail will be shown, use L/l to toggle between listing all groups.
  • SPC, DEL to scroll forward and backward; or C-v, M-v as always.
  • G G: Search mail at server side in the group buffer.
    • Limit search to particular folders/groups by marking them with #, or unmarking them with M-#.
  • / /,a: Filter mail according to subject or author; there are many other options, see §3.8 Limiting.
  • d: Mark an article as done, i.e., read it and it can be archived.
  • !: Mark an article as read, but to be kept around —e.g., you have not replied to it, or it requires more reading at a later time.

    This lets us read mail offline; cached mail is found at ~/News/cache/.

    (setq gnus-use-cache 'use-as-much-cache-as-possible)
        
  • B m: Move an article, in its current state, to another group —i.e., ‘label’ using Gmail parlance.
    • Something to consider doing when finished with an article.

    To delete an article, simply move it to ‘trash’ —of course this will delete it in other mail clients as well. There is no return from trash.

    Emails can always be archieved —never delete, maybe?

    Anyhow, B m Trash is too verbose, let’s just use t for “trash”:

    (with-eval-after-load 'gnus
      (bind-key "t"
              (lambda (N) (interactive "P") (gnus-summary-move-article N "[Gmail]/Trash"))
              gnus-summary-mode-map))
    
    ;; Orginally: t ⇒ gnus-summary-toggle-header
        
    • Select and deselect many articles before moving them by pressing # and M-#, respectively, anywhere on the entry.
    • As usual, you can mark a region, C-SPC, then move all entries therein.
  • R, r: Reply with sender’s quoted text in place, or without but still visible in an adjacent buffer.
    • Likewise S W or S w to reply all, ‘wide reply’, with or without quoted text.
    • C-c C-z Delete everything from current position till the end.
    • C-c C-e Replace selected region with ‘[…]’; when omitting parts of quoted text.
  • Press m to compose mail; or C-x m from anywhere in Emacs to do so.
    • C-c C-c to send the mail.
    • S D e to resend an article as new mail: Alter body, subject, etc, before
    • C-c C-f to forward mail. sending.
  • C-c C-a to attach a file; it’ll be embedded in the mail body as plaintext.
    • Press o on an attachment to save it locally.

Capturing Mail as Todo/Notes

Sometime mail contains useful reference material or may be a self-contained task. Rather than using our inbox as a todo-list, we can copy the content of the mail and store it away in our todos/notes files. Capturing, below, is a way to, well, capture ideas and notes without interrupting the current workflow. Below, in the section on capturing, we define my/org-capture-buffer which quickly captures the contents of the current buffer as notes to store away. We use that method in the article view of mail so that c captures mail content with the option to provide additional remarks, and C to silently do so without additional remarks.

(with-eval-after-load 'gnus

(bind-key "c" #'my/org-capture-buffer gnus-article-mode-map)
;; Orginally: c ⇒ gnus-summary-catchup-and-exit

(bind-key "C"
          (lambda (&optional keys)
            (interactive "P") (my/org-capture-buffer keys 'no-additional-remarks))
          gnus-article-mode-map))
;; Orginally: C ⇒ gnus-summary-cancel-article

Gnus’ default c only enables a bad habit: Subscribing to stuff that you don’t read, since you can mark all entries as read with one key. We now replace it with a ‘c’apturing mechanism that captures the current message as a todo or note for further processing. Likewise, the default C is to cancel posting an article; we replace it to be a silent capture: Squirrel away informative mail content without adding additional remarks.

Auto-completing mail addresses

In order to get going quickly, using gmail2bbdb, let’s convert our Gmail contacts into a BBDB file —the Insidious Big Brother Database is an address-book application that we’ll use for E-mail; if you want to use it as a address-book application to keep track of contacts, notes, their organisation, etc, then consider additionally installing helm-bbdb which gives a nice menu interface.

  • From the Gmail Contacts page, obtain a contacts.vcf file by clicking “More -> Export -> vCard format -> Export”.
  • Run command M-x gmail2bbdb-import-file and select contacts.vcf; a bbdb file will be created in my Dropbox folder.
  • Press C-x m then begin typing a contact’s name and you’ll be queried about setting up BBDB, say yes.
(use-package gmail2bbdb
  :defer t
  :custom (gmail2bbdb-bbdb-file "~/Dropbox/bbdb"))

(use-package bbdb
 :after company ;; The “com”plete “any”thig mode is set below in §Prose
 :hook   (message-mode . bbdb-insinuate-gnus)
         (gnus-startup-hook . bbdb-insinuate-gnus)
 :custom (bbdb-file gmail2bbdb-bbdb-file)
         (bbdb-use-pop-up t)                        ;; allow popups for addresses
 :config (add-to-list 'company-backends 'company-bbdb))

Here is an emacs-fu article on managing e-mail addressed with bbdb.

Feeds to Blogs

One can easily subscribe to an RSS feed in Gnus: Just press G R in the group buffer view, then follow the prompts. However, doing so programmatically is much harder. Below is my heartfelt attempt at doing so —if you want a feed reader in Emacs that “just works”, then elfeed is the way to go. When all is said and done, the code below had me reading Gnus implementations and led me to conclude that Gnus has a great key-based interface but a /poor programming interface —or maybe I need to actually read the manual instead of frantically consulting source code.

My homemade hack to getting tagged feeds programmatically into Gnus.

;; Always show Gnus items organised by topic.
(add-hook 'gnus-group-mode-hook 'gnus-topic-mode)

;; From Group view, press ^, then SPC on Gwene, then look for the site you want to follow.
;; If it's not there, add it via the web interface http://gwene.org/
(add-to-list 'gnus-secondary-select-methods '(nntp "news.gwene.org"))
;;
;; E.g., http://nullprogram.com/feed/ uses an Atom feed which Gnus does not
;; support natively.  But it can be found on Gwene.

(setq my/gnus-feeds
      ;; topic  title  url
      '(Emacs "C‘est La 𝒵" https://cestlaz.github.io/rss.xml
        Emacs "Marcin Borkowski's Blog" http://mbork.pl?action=rss
        Emacs "Howardism" http://www.howardism.org/rss.xml
        Islam "Shia Islam Blogspot" http://welcometoshiaislam.blogspot.com/feeds/posts/default?alt=rss
        Cats "Hedonistic Learning" http://www.hedonisticlearning.com/rss.xml
        Cats "Functorial Blog"  https://blog.functorial.com/feed.rss
        Programming "Joel on Software" http://www.joelonsoftware.com/rss.xml
        Haskell "Lysxia's Blog"  https://blog.poisson.chat/rss.xml))

;; If fubared, then:
;; (ignore-errors (f-delete "~/News/" 'force) (f-delete "~/.newsrc.eld"))

;; Execute this after a Gnus buffer has been opened.
(progn
(use-package with-simulated-input)
(cl-loop for (topic title url)
      in (-partition 3 my/gnus-feeds)
      ;; url & topic are symbols, make them strings.
      for url′   = (symbol-name url)
      for topic′ = (symbol-name topic)
      ;; Avoid spacing issues by using a Unicode ghost space “ ”.
      for title′ = (gnus-newsgroup-savable-name (s-replace " " " " title))
      for input  = (format "C-SPC C-a %s RET RET" title′)
      do
      ; cl-letf* (((symbol-function 'insert) (lambda (x) nil))) ;; see the (undo) below.
      ;; Add the group
      (with-simulated-input input
        (gnus-group-make-rss-group url′))
      ;; Ensure it lives in the right topic category.
      (if (equal 'no-such-topic (alist-get topic gnus-topic-alist 'no-such-topic nil #'string=))
        (push (list topic′ title′) gnus-topic-alist) ;; make topic if it doesnt exist
      (setf (alist-get topic′ gnus-topic-alist 'no-such-topic nil #'string=)
            (cons title′ (alist-get topic gnus-topic-alist 'no-such-topic nil #'string=)))))
      ;; Acknowledgement
      (message "Now switch into the GNUS group buffer, and refresh the topics; i.e., t t."))

      ;; The previous command performs an insert, since it's intended to be interactively
      ;; used; let's undo the insert.
      ; (undo-only)

;; (setq gnus-permanently-visible-groups ".*")
;;
;; Show topic alphabetically? The topics list is rendered in reverse order.
;; (reverse (cl-sort gnus-topic-alist 'string-lessp :key 'car))

Ironically, I’ve decide that “no, I do not want to see my blogs in Emacs” for the same reasons I do not activelly use M-x eww to browse the web in Emacs: I like seeing the colours, fonts, and math symbols that the authours have labored over to producing quality content. Apparently, I’m shallow and I’m okay with it —but not that shallow, since I’m constantly pushing Emacs which looks ugly by default but it’s unreasonably powerful.

Jumping to extreme semantic units

Sometimes it’s unreasonable for M-< to take us to the actual start of a buffer; instead it’d be preferable to go to the first “semantic unit” in the buffer. For example, when directory editing with dired we should jump to the first file, with version control with magit we should jump to the first section, when composing mail we should jump to the first body line, and in the agenda we should jump to the first entry.

;; M-< and M-> jump to first and final semantic units.
;; If pressed twice, they go to physical first and last positions.
(use-package beginend
  :diminish 'beginend-global-mode
  :config (beginend-global-mode)
    (cl-loop for (_ . m) in beginend-modes do (diminish m)))

Quickly pop-up a terminal, run a command, close it —and zsh

Pop up a terminal, do some work, then close it using the same command.

Shell-pop uses only one key action to work: If the buffer exists, and we’re in it, then hide it; else jump to it; otherwise create it if it doesn’t exit. Use universal arguments, e.g., C-u 5 C-t, to have multiple shells and the same universal arguments to pop those shells up, but C-t to pop them away.

(use-package shell-pop
  :defer t
  :custom
    ;; This binding toggles popping up a shell, or moving cursour to the shell pop-up.
    (shell-pop-universal-key "C-t")

    ;; Percentage for shell-buffer window size.
    (shell-pop-window-size 30)

    ;; Position of the popped buffer: top, bottom, left, right, full.
    (shell-pop-window-position "bottom")

    ;; Please use an awesome shell.
    (shell-pop-term-shell "/bin/zsh"))

Now that we have access to quick pop-up for a shell, let’s get a pretty and practical shell: zsh along with the Oh My Zsh community configurations give us:

  1. brew install zsh
  2. sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

    This installs everything ^_^

;; Be default, Emacs please use zsh
;; E.g., M-x shell
(setq shell-file-name "/bin/zsh")

Out of the box, zsh comes with

  • git support; the left side indicates which branch we’re on and whether the repo is dirty, ✗.
  • Recursive path expansion; e.g., /u/lo/b TAB expands to /usr/local/bin/
  • Over 250+ Plugins and 125+ Themes that are enabled by simply mentioning their name in the .zshrc file.

The defaults have been good enough for me, for now —as all else is achieved via Emacs ;-)

Also, there’s tldr tool which aims to be like terse manuals for commandline-tools in the style of practical example uses cases: tldr 𝒳 yields a number of ways you’d actually use 𝒳.

(system-packages-ensure "tldr")

Restarting Emacs —Keeping buffers open across sessions?

Sometimes I wish to close then reopen Emacs; unsurprisingly someone’s thought of implementing that.

;; Provides only the command “restart-emacs”.
(use-package restart-emacs
  ;; If I ever close Emacs, it's likely because I want to restart it.
  :bind ("C-x C-c" . restart-emacs)
  ;; Let's define an alias so there's no need to remember the order.
  :config (defalias 'emacs-restart #'restart-emacs))

The following is disabled. I found it a nuisance to have my files open across sessions —If I’m closing Emacs, it’s for a good reason.

;; Keep open files open across sessions.
(desktop-save-mode 1)
(setq desktop-restore-eager 10)

Instead, let’s try the following: When you visit a file, point goes to the last place where it was when you previously visited the same file.

(setq-default save-place  t)
(setq save-place-file "~/.emacs.d/etc/saveplace")

Automatic Backups

By default, Emacs saves backup files —those ending in ~— in the current directory, thereby cluttering it up. Let’s place them in ~~/.emacs.d/backups~, in case we need to look for a backup; moreover, let’s keep old versions since there’s disk space to go around —what am I going to do with 500gigs when nearly all my ‘software’ is textfiles interpreted within Emacs 😼

;; New location for backups.
(setq backup-directory-alist '(("." . "~/.emacs.d/backups")))

;; Silently delete execess backup versions
(setq delete-old-versions t)

;; Only keep the last 1000 backups of a file.
(setq kept-old-versions 1000)

;; Even version controlled files get to be backed up.
(setq vc-make-backup-files t)

;; Use version numbers for backup files.
(setq version-control t)

Why backups? Sometimes I may forget to submit a file, or edit, to my version control system, and it’d be nice to be able to see a local automatic backup. Whenever ‘I need space,’ then I simply empty the backup directory, if ever. That the backups are numbered is so sweet ^_^

Like package installations, my backups are not kept in any version control system, like git; only locally.

Let’s use an elementary diff system for backups.

(use-package backup-walker
  :commands backup-walker-start)

In a buffer that corresponds to a file, invoke backup-walker-start to see a visual diff of changes between versions. By default, you see the changes ‘backwards’: Red means delete these things to get to the older version; i.e., the red ‘-’ are newer items.

Emacs only makes a backup the very first time a buffer is saved; I’d prefer Emacs makes backups everytime I save! —If I saved, that means I’m at an important checkpoint, so please check what I have so far as a backup!

;; Make Emacs backup everytime I save

(defun my/force-backup-of-buffer ()
  "Lie to Emacs, telling it the curent buffer has yet to be backed up."
  (setq buffer-backed-up nil))

(add-hook 'before-save-hook  'my/force-backup-of-buffer)

It is intestesting to note that the above snippet could be modified to make our own backup system, were Emacs lacked one, by having our function simply save copies of our file —on each save— where the filename is augmented with a timestamp.

  • diff-backup compares a file with its backup or vice versa.

Screencapturing the Current Emacs Frame

Sometimes an image can be tremendously convincing, or at least sufficiently inviting. The following incantation is written for MacOS and uses it’s native screencapture utility, as well as magick.

(defun my/capture-emacs-frame (&optional prefix output)
"Insert a link to a screenshot of the current Emacs frame.

Unless the name of the OUTPUT file is provided, read it from the
user. If PREFIX is provided, let the user select a portion of the screen."
(interactive "p")
(defvar my/emacs-window-id
   (s-collapse-whitespace (shell-command-to-string "osascript -e 'tell app \"Emacs\" to id of window 1'"))
   "The window ID of the current Emacs frame.

    Takes a second to compute, whence a defvar.")

(let* ((screen  (if prefix "-i" (concat "-l" my/emacs-window-id)))
       (temp    (format "emacs_temp_%s.png" (random)))
       (default (format-time-string "emacs-%m-%d-%Y-%H:%M:%S.png")))
;; Get output file name
  (unless output
    (setq output (read-string (format "Emacs screenshot filename (%s): " default)))
    (when (s-blank-p output) (setq output default)))
;; Clear minibuffer before capturing screen or prompt user
(message (if prefix "Please select region for capture …" "♥‿♥"))
;; Capture current screen and resize
(thread-first
    (format "screencapture -T 2 %s %s" screen temp)
    (concat "; magick convert -resize 60% " temp " " output)
    (shell-command))
(f-delete temp)
;; Insert a link to the image and reload inline images.
(insert (concat "[[file:" output "]]")))
(org-display-inline-images nil t))

(bind-key* "C-c M-s" #'my/capture-emacs-frame)

Why this way? On MacOS, ImageMagick’s import doesn’t seem to work —not at all for me! Also, I dislike how large the resulting image is. As such, I’m using MacOS’s screencapture utility, which in-turn requires me to somehow obtain frame IDs. Hence, the amount of work needed to make this happen on my system was most simple if I just wrote it out myself rather than tweaking an existing system.

  • C-c C-x C-v ⇒ Toggle inline images!

Editor Documentation with Contextual Information

Emacs is an extensible self-documenting editor!

Let’s use a helpful Emacs documentation system that cleanly shows a lot of contextual information —then let’s extend that to work as we want it to: C-h o to describe the symbol at point.

(use-package helpful :defer t)

(defun my/describe-symbol (symbol)
  "A “C-h o” replacement using “helpful”:
   If there's a thing at point, offer that as default search item.

   If a prefix is provided, i.e., “C-u C-h o” then the built-in
   “describe-symbol” command is used.

   ⇨ Pretty docstrings, with links and highlighting.
   ⇨ Source code of symbol.
   ⇨ Callers of function symbol.
   ⇨ Key bindings for function symbol.
   ⇨ Aliases.
   ⇨ Options to enable tracing, dissable, and forget/unbind the symbol!
  "
  (interactive "p")
  (let* ((thing (symbol-at-point))
         (val (completing-read
               (format "Describe symbol (default %s): " thing)
               (vconcat (list thing) obarray)
               (lambda (vv)
                 (cl-some (lambda (x) (funcall (nth 1 x) vv))
                          describe-symbol-backends))
               t nil nil))
         (it (intern val)))
    (cond
     (current-prefix-arg (funcall #'describe-symbol it))
     ((or (functionp it) (macrop it) (commandp it)) (helpful-callable it))
     (t (helpful-symbol it)))))

;; Keybindings.
(global-set-key (kbd "C-h o") #'my/describe-symbol)
(global-set-key (kbd "C-h k") #'helpful-key)

I like helpful and wanted it to have the same behaviour as C-h o, which helpful-at-point does not achieve. The incantation above makes C-h o use helpful in that if the cursor is on a symbol, then it is offered to the user as a default search item for help, otherwise a plain search box for help appears. Using a universal argument lets us drop to the built-in help command.