Updated, pretty, version: https://alhassy.github.io/emacs.d/index.html
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
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 o
⇒ What’s this thing?C-h e
⇒ What’d /Emacs/ do?C-h l
⇒ What’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
andC-h l
, then useC-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.
- Abstract
- Booting Up
- =~/.emacs= vs. =init.org=
- =use-package= —The start of =init.el=
- =README= —From
init.org
to =init.el= - Installing Emacs packages directly from source
- =magit= —Emacs’ porcelain interface to gitq
- Syncing to the System’s =$PATH=
- Installing OS packages, and automatically keeping my system up to data, from within Emacs
- “Being at the Helm” —Completion & Narrowing Framework
- Having a workspace manager in Emacs
- Excellent PDF Viewer
- Who am I? —Using Gnus for Gmail
- Jumping to extreme semantic units
- Quickly pop-up a terminal, run a command, close it —and zsh
- Restarting Emacs —Keeping buffers open across sessions?
- Automatic Backups
- Screencapturing the Current Emacs Frame
- Editor Documentation with Contextual Information
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 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 byC-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 withM-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.
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!
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’.
M-x customize-variable RET line-number-mode RET
- Then press:
toggle
,state
, then1
. - Now take a look:
C-x C-f ~/.emacs
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)
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.
- Press
- 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.
- Use the standalone keyword
- By default,
- 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 formsfᵢ
before loading a package.:diminish str
Uses optional stringstr
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 nostr
.:config f₁ … fₙ
Only executes code formsfᵢ
after loading a package.The remaining keywords only take affect after a module loads.
:bind ((k₁ . f₁) … (kₙ . fₙ)
Lets us bind keyskᵢ
, such as"M-s o"
, to functions, such asoccur
.- When n = 1, the extra outer parenthesis are not necessary.
:hook ((m₁ … mₙ) . f)
Enables functionalityf
whenever we’re in one of the modesmᵢ
, such asorg-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 aadd-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 variableskᵢ
to have valuesvᵢ
, along with optional user documentationdᵢ
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))
- E.g.,
- s: “The long lost Emacs string manipulation library”.
- E.g.,
s-trim, s-replace, s-join
.
- E.g.,
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!
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
.
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)
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
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")
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:
- Export to markdown.
COMMENT
-out any:TOC:
-tagged sections —their links are not valid markdown links, since they don’t refer to any markdown labels.- Rename the exported file to
README.md
. - Run
grip
.
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"))
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 \"[email protected]\""))
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 doesgit 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 withC-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
onHEAD
.
- A new buffer for the commit message appears, you write it then
commit with
d
- Show differences, another
d
or another option.- This is magit! Each hunk can be acted upon; e.g.,
s
orc
ork
;-)
- This is magit! Each hunk can be acted upon; e.g.,
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 pressspace
scrolls through the other buffer! Neato.
- Here
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!
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.
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 use-package-ensure-system-package documentation for a flurry of use cases.
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 ^_^
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 withhelm-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 ;-)
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 😃
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.
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 "[email protected]")
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.
- 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
. - 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. ]+\\( \\|$\\)\\|^[\"]\"[#'()]")
- Enable “2 step authentication” for Gmail following these instructions.
- 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 [email protected] password xxxxxxxxxxxxxxxx port imaps machine smtp.gmail.com login [email protected] password xxxxxxxxxxxxxxxx port 587
- In Emacs,
M-x gnus
to see what’s there.Or compose mail with
C-x m
then send it withC-c C-c
.- Press
C-h m
to learn more about message mode for mail composition; or read the Message Manual.
- Press
;; 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
(•̀ᴗ•́)و
- Or look at the Gnus Reference Card.
- Or, less comprehensively, this outline.
EmacsWiki has a less technical and more user friendly tutorial.
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"))
⟨ 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.
- Use
- 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; orC-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 withM-#
.
- Limit search to particular folders/groups by marking them with
/ /,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 uset
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
#
andM-#
, 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
orS 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.
- Likewise
- Press
m
to compose mail; orC-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, beforeC-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.
- Press
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.
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 selectcontacts.vcf
; abbdb
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.
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.
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)))
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:
brew install zsh
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")
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")
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.
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!
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.