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 \"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 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 "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.
- 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 alhassy@gmail.com password xxxxxxxxxxxxxxxx port imaps machine smtp.gmail.com login alhassy@gmail.com 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.