This is my .emacs
file, written using org-mode, so that I can
organize it and take notes on all the complexity.
This file is really a script that requires version 24 of Emacs. While I often use Emacs for Mac, lately, I’ve been building from Homebrew with the following dependency:
brew cask install xquartz
And then built from source especially for the Mac:
brew install emacs --HEAD --use-git-head --with-cocoa \
--with-gnutls --srgb --with-librsvg --with-ns
brew linkapps emacs
Not only does this install the latest version of Emacs in
/usr/local/bin/emacs
, but it also links a GUI version in
/Application/Emacs.app
.
All Homebrew options for Emacs can be seen with the command:
brew options emacs
To “load” the contents of this file, add the following to $HOME/.emacs
:
;; Load our Literate Programming version of our Dot Emacs
;; file, from file: ~/Work/dot-files/emacs.org
(unless (boundp 'aquamacs-version)
(load-file "~/.emacs.d/elisp/init-main.el")
(when (window-system)
(require 'init-client))
(server-start))
Note: I only load this from a “normal” Emacs distribution, which allows me to play around with Aquamacs and Starter Kits for recommendations to people new to Emacs.
Normally, the user-emacs-directory
stores everything in a
.emacs.d
directory in the home directory, however, Aquamacs
overrides that, and since I now feel the need to use these settings
for both editors (sure feels like XEmacs all over again).
Any way, I have a new global variable for that:
(defconst ha/emacs-directory (concat (getenv "HOME") "/.emacs.d/"))
(defun ha/emacs-subdirectory (d) (expand-file-name d ha/emacs-directory))
In case this is the first time running this on a computer, we need to make sure the following directories have been created.
(let* ((subdirs '("elisp" "backups" "snippets" "ac-dict"))
(fulldirs (mapcar (lambda (d) (ha/emacs-subdirectory d)) subdirs)))
(dolist (dir fulldirs)
(when (not (file-exists-p dir))
(message "Make directory: %s" dir)
(make-directory dir))))
While I would rather program my configurations, sometimes the Emacs menu system is “good enough”, but I want it in its own file:
(setq custom-file (expand-file-name "custom.el" ha/emacs-directory))
(when (file-exists-p custom-file)
(load custom-file))
Extra packages not available via the package manager go in my
personal stash at: $HOME/.emacs.d/elisp
(add-to-list 'load-path (ha/emacs-subdirectory "elisp"))
Load up my special collection of enhancements to Emacs Lisp:
(require 'init-support)
(require 'cl)
Emacs has become like every other operating system, and now has a package manager with its own collection repository, but since it is so conservative, we need to add more repositories to get all the sweet goodness, I demand.
(require 'package)
(setq package-archives '(("org" . "http://orgmode.org/elpa/")
("gnu" . "http://elpa.gnu.org/packages/")
("melpa" . "http://melpa.milkbox.net/packages/")
("marmalade" . "http://marmalade-repo.org/packages/")))
While we can now do a package-list-packages
, you can install and
everything is good, however, we can’t require
any of these
packages (in order to customize them in this file) until we do
this:
(package-initialize)
(package-refresh-contents)
Not sure why the package management system doesn’t come with a programmatic way to specify what packages should be installed. Oh yeah, this is pretty new. Looks like everyone just rolls there own, so this is mine.
(defun packages-install (packages)
"Given a list of packages, this will install them from the standard locations."
(let ((to-install (inverse-filter 'package-installed-p packages)))
(when to-install
(package-refresh-contents)
(dolist (it to-install)
(package-install it)
(delete-other-windows)))))
This means that at any point in my configuration file, I can specify a list of packages to make sure they are installed.
(packages-install
'(ace-jump-mode
ag
avy
company
company-quickhelp
company-math
dired-details
color-identifiers-mode ;; Color variables differently
ctags-update
epl
esh-buf-stack
expand-region
flx
flx-ido
flycheck
flycheck-color-mode-line
git-blame
git-gutter-fringe
gitconfig-mode
gitignore-mode
graphviz-dot-mode
hungry-delete
htmlize
hydra
ido-vertical-mode
iy-go-to-char
linum-relative
magit
markdown-mode
multiple-cursors
paredit
redo+ ;; If not installed, edit mac-key-mode
smex
thesaurus
undo-tree
visual-regexp
yasnippet))
Just beginning to use use-package. I’m sure it will be a long time to finally convert everything.
(require 'use-package)
General settings about me that other packages can use. The biggest problem is guessing my email address based on what computer I am using:
(if (equal "howard.abrams" user-login-name)
(setq user-mail-address "howard.abrams@workday.com")
(setq user-mail-address "howard.abrams@gmail.com"))
I have learned to distrust tabs in my source code, so let’s make sure that we only have spaces. See this discussion for details.
(setq-default indent-tabs-mode nil)
(setq tab-width 2)
Make tab key do indent first then completion.
(setq-default tab-always-indent 'complete)
Automatically indent without use of the tab found in this article, and seems to be quite helpful for many types of programming languages.
To begin, we create a function that can indent a function by
calling indent-region
on the beginning and ending points of a
function.
(defun indent-defun ()
"Indent current defun.
Do nothing if mark is active (to avoid deactivaing it), or if
buffer is not modified (to avoid creating accidental
modifications)."
(interactive)
(unless (or (region-active-p)
buffer-read-only
(null (buffer-modified-p)))
(let ((l (save-excursion (beginning-of-defun 1) (point)))
(r (save-excursion (end-of-defun 1) (point))))
(cl-letf (((symbol-function 'message) #'ignore))
(indent-region l r)))))
Next, create a hook that will call the indent-defun
with every
command call:
(defun activate-aggressive-indent ()
"Locally add `ha/indent-defun' to `post-command-hook'."
(add-hook 'post-command-hook
'indent-defun nil 'local))
The trick is to add the following to each programming hook:
(add-hook 'emacs-lisp-mode-hook 'activate-aggressive-indent)
Synchronize notes formatted in org-mode across multiple computers with cloud storage services, like Dropbox? Those files are cached in various other storage facilities… so, I use symmetric key encryption with PGP.
To get started on the Mac, install the goodies:
brew install gpg
Now, any file loaded with a gpg
extension, e.g. some.org.gpg
,
will prompt for a password (and then use org-mode
). Since these
files are for my eyes only, I don’t need the key-ring prompt:
(setq epa-file-select-keys 2)
If you trust your Emacs session on your computer, you can have Emacs cache the password. Not sure I do…
(setq epa-file-cache-passphrase-for-symmetric-encryption t)
I’ve been using Emacs for many years, and appreciate a certain minimalist approach to its display. While you can turn these off with the menu items now, it is just as easy to set them here.
(setq initial-scratch-message "") ;; Uh, I know what Scratch is for
(setq visible-bell t) ;; Get rid of the beeps
(when (window-system)
(tool-bar-mode 0) ;; Toolbars were only cool with XEmacs
(when (fboundp 'horizontal-scroll-bar-mode)
(horizontal-scroll-bar-mode -1))
(scroll-bar-mode -1)) ;; Scrollbars are waste screen estate
Most of the display settings actually come from the Mac initialization file.
My mode-line code is now more complex in order to make it more simpler.
(require 'init-mode-line)
Clearly, the most important keybindings are the function keys, right? Here is my list of needs:
- F1 - Help? Isn’t Control-H good enough?
- F2 - Standard alternate meta key with lots of bindings
- F3 - Define a keyboard macro
- F4 - Replay a keyboard macro
- F5 - Use org-mode’s Mark Ring feature globally
- F6 - Open to temporary, changeable commands…
- F7 - Switch to another window … Shift goes the other way.
- F8 - Switch to buffer
- F9 - My other meta key for changing colors and other odd bindings that I actually don’t use that often
(global-set-key (kbd "<f5>") 'org-mark-ring-push)
(global-set-key (kbd "C-<f5>") 'org-mark-ring-goto)
;; (global-set-key (kbd "<f6>") ')
(global-set-key (kbd "<f7>") 'other-window)
(global-set-key (kbd "<f8>") 'ido-switch-buffer)
Set up ace-window mode:
(when (require 'ace-window nil t)
(global-set-key (kbd "<f7>") 'ace-window)
(global-set-key (kbd "C-<f7>") (lambda () (interactive) (ace-window 4)))
(global-set-key (kbd "M-<f7>") (lambda () (interactive) (ace-window 8))))
At some point, I will want to choose window labels based on the right hand (since F7) is on the left side of my keyboards.
(setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
I like kpm-list a bit better than ibuffer
, but I really don’t use
either more than ido-switch-buffer
. Still:
(if (require 'kpm-list nil t)
(global-set-key (kbd "S-<f8>") 'kpm-list)
(global-set-key (kbd "S-<f8>") 'ibuffer))
The F9 prefix is scattered about my config files.
(define-prefix-command 'personal-global-map)
(global-set-key (kbd "<f9>") 'personal-global-map)
(define-key personal-global-map (kbd "b") 'bury-buffer)
Unlike the F9 bindings, all the F2 key-bindings happen in a single library file:
(require 'init-f2)
Big, structured file, like source code, hide all of the functions, and selectively reveal them, using hide-show-mode. I don’t like the complicated key-bindings, so I through them on the <f9>:
(defun ha-hs-hide-all ()
"Wrapper around 'hs-hide-all' that turns on the minor mode."
(interactive)
(hs-minor-mode t)
(hs-hide-all))
(define-key personal-global-map (kbd "M-H") 'ha-hs-hide-all)
(define-key personal-global-map (kbd "H") 'hs-hide-block)
(define-key personal-global-map (kbd "M-V") 'hs-show-all)
(define-key personal-global-map (kbd "V") 'hs-show-block)
While ‘S’ does make sense for showing, I’m already using that for ‘Stop’. Besides, ‘viewing’ isn’t a bad mnemonic.
I like the ability to highlight random text.
(define-key personal-global-map (kbd "h") 'highlight-regexp)
(define-key personal-global-map (kbd "u") 'unhighlight-regexp)
May get specific highlights automatically for certain files. We begin by highlighting lines in *.log files.
(defun highlite-it ()
"Highlight certain lines in specific files. Currently, only log files are supported."
(interactive)
(when (equal "log" (file-name-extension (buffer-file-name)))
(hi-lock-mode 1)
(highlight-lines-matching-regexp "ERROR:" 'hi-red-b)
(highlight-lines-matching-regexp "NOTE:" 'hi-blue-b)))
(add-hook 'find-file-hook 'highlite-it)
Turn on specific word groupings for specific occasions. We begin with highlighting keywords I use during note-taking sessions at the end of a sprint.
(defun ha/sprint-retrospective-highlighting ()
"Highlights the good, the bad and the improvements to make when taking notes."
(interactive)
(hi-lock-mode t)
(highlight-lines-matching-regexp "^ [-*] " 'hi-black-b)
(highlight-phrase "TODO:?" 'hi-black-b)
(highlight-regexp "(?Good)?:?" 'hi-green-b)
(highlight-regexp "(?Bad)?:?" 'hi-red-b)
(highlight-regexp "Imp\\(rove\\)?:" 'hi-blue-b))
Often, while on my laptop, I want the current window to be ‘large
enough for work’, and this is bound to <f9> .
(period).
(define-key personal-global-map (kbd ",") 'ha/window-standard-size)
If I’ve enlarged the window, I can restore that window to its original size, so this requires a buffer local variable:
(make-variable-buffer-local 'window-width-original)
Now a function that either changes the width to 80, or back to the original size if already at 80.
(defun ha/window-standard-size (arg)
"Sets the size of the current window to 80 characters, unless
it already is 80 characters, in which case, set it back to its
previous size. A prefix ARG can be given to set the window to a
particular width."
(interactive "p")
;; If not already set, let's store the current window width in our
;; buffer-local variable.
(if (not (local-variable-p 'window-width-original))
(setq window-width-original (window-width)))
;; The 'goal' is 80 unless we get a better argument, C-u 60 ...
(let* ((goal-width (if (> arg 8) arg 80))
(new-width (- goal-width (window-width))))
(if (= new-width 0) ; Already enlarged? Restore:
(enlarge-window-horizontally (- window-width-original goal-width))
(enlarge-window-horizontally new-width))))
After picking up an Advantage Kinesis, I decided that I wanted to redo some of the bindings to make it easier on me…mainly because the Shift key is now really, really hard to reach.
(global-set-key (kbd "M-<left>") 'beginning-of-line)
(global-set-key (kbd "M-<right>") 'end-of-line)
(global-set-key (kbd "C-M-<left>") 'beginning-of-buffer)
(global-set-key (kbd "C-M-<right>") 'end-of-buffer)
Why isn’t imenu
bound?
(global-set-key (kbd "M-s f") 'imenu)
(global-set-key (kbd "M-s M-f") 'imenu)
Better jumping around the buffer with ace-jump-mode and friends.
(when (require 'ace-jump-mode nil t)
(global-set-key (kbd "C-c SPC") 'ace-jump-mode)
(global-set-key (kbd "C-c C-SPC") 'ace-jump-mode-pop-mark)
(global-set-key (kbd "M-s s") 'ace-jump-mode) ;; Needed for org-mode files
(global-set-key (kbd "M-s a") 'avi-goto-char)
(global-set-key (kbd "M-s w") 'avi-goto-word-1)
(global-set-key (kbd "M-s l") 'avi-goto-line))
C-c SPC
…ace-jump-word-mode
enter first character of a word, select the highlighted key to move to it.C-u C-c SPC
…ace-jump-char-mode
enter a character for query, select the highlighted key to move to it.C-u C-u C-c SPC
…ace-jump-line-mode
each non-empty line will be marked, select the highlighted key to move to it.M-s l
…avi-goto-line
… works well with org-mode files.M-s .
…isearch-forward-symbol-at-point
search forward to whatever is at point, but you need toC-s
to jump to next match.M-s _
…isearch-forward-symbol
Using avy as an extension to the normal isearch
, so that if I
type a search phrase, I can then type C-'
to kick off
avy-isearch
, and jump to the one that I want. Little change to the
mental flow.
(when (require 'avy nil t)
(define-key isearch-mode-map (kbd "C-'") 'avy-isearch))
Unfilling a paragraph joins all the lines in a paragraph into a single line. Taken from here.
(defun unfill-paragraph ()
"Takes a multi-line paragraph and makes it into a single line of text."
(interactive)
(let ((fill-column (point-max)))
(fill-paragraph nil)))
;; Handy key definition
(define-key global-map "\M-Q" 'unfill-paragraph)
The subtle changes I’ve been making to Emacs behavior has grown until I felt I should move it into its own source file.
(require 'init-fixes)
While I’m not sure how often I will use multiple-cursors project, I’m going to try to remember it is there. It doesn’t have any default keybindings, so I set up the suggested:
(when (require 'multiple-cursors nil t)
(global-set-key (kbd "C->") 'mc/mark-next-like-this)
(global-set-key (kbd "C-<") 'mc/mark-previous-like-this)
(global-set-key (kbd "C-c C-<") 'mc/mark-all-like-this))
Wherever you are in a file, and whatever the type of file, you can slowly increase a region selection by logical segments.
(require 'expand-region)
(global-set-key (kbd "C-=") 'er/expand-region)
This works really well with other commands, including fancy-narrow, where I can visually high-light a section of a buffer. Great for code-reviews and other presentations.
(when (require 'fancy-narrow nil t)
(defun ha/highlight-block ()
"Highlights a 'block' in a buffer defined by the first blank
line before and after the current cursor position. Uses the
'fancy-narrow' mode to high-light the block."
(interactive)
(let (cur beg end)
(setq cur (point))
(setq end (or (re-search-forward "^\s*$" nil t) (point-max)))
(goto-char cur)
(setq beg (or (re-search-backward "^\s*$" nil t) (point-min)))
(fancy-narrow-to-region beg end)
(goto-char cur)))
(defun ha/highlight-section (num)
"If some of the buffer is highlighted with the 'fancy-narrow'
mode, then un-highlight it by calling 'fancy-widen'.
If region is active, call 'fancy-narrow-to-region'.
If given a prefix value, C-u, highlight the current
block (delimited by blank lines). Otherwise, called
'fancy-narrow-to-defun, to highlight current function."
(interactive "p")
(cond
((fancy-narrow-active-p) (fancy-widen))
((> num 1) (ha-highlight-block))
((region-active-p) (fancy-narrow-to-region (region-beginning) (region-end)))
;; Want to do something special in org-mode? Probably...
;; ((derived-mode-p 'org-mode) ...)
(t (fancy-narrow-to-defun))))
(global-set-key (kbd "C-M-+") 'ha/highlight-section))
The M-(
binding to insert-pair
is great, but we often need to
wrap other commands. Thankfully, insert-pair
is up to the task
by simply having new bindings.
(global-set-key (kbd "M-[") 'insert-pair)
(global-set-key (kbd "M-{") 'insert-pair)
(global-set-key (kbd "M-<") 'insert-pair)
(global-set-key (kbd "M-'") 'insert-pair)
(global-set-key (kbd "M-`") 'insert-pair)
(global-set-key (kbd "M-\"") 'insert-pair)
But in order to wrap text in a more general way (with just about
any textual string), we need something more. Especially with the
expand-region
command, wrapping a logical block of text with a
beginning and ending string really makes sense.
(defun surround (start end txt)
"Wraps the specified region (or the current 'symbol / word'
with some textual markers that this function requests from the
user. Opening-type text, like parens and angle-brackets will
insert the matching closing symbol.
This function also supports some org-mode wrappers:
- `#s` wraps the region in a source code block
- `#e` wraps it in an example block
- `#q` wraps it in an quote block"
(interactive "r\nsEnter text to surround: " start end txt)
;; If the region is not active, we use the 'thing-at-point' function
;; to get a "symbol" (often a variable or a single word in text),
;; and use that as our region.
(if (not (region-active-p))
(let ((new-region (bounds-of-thing-at-point 'symbol)))
(setq start (car new-region))
(setq end (cdr new-region))))
;; We create a table of "odd balls" where the front and the end are
;; not the same string.
(let* ((s-table '(("#e" . ("#+BEGIN_EXAMPLE\n" "\n#+END_EXAMPLE") )
("#s" . ("#+BEGIN_SRC \n" "\n#+END_SRC") )
("#q" . ("#+BEGIN_QUOTE\n" "\n#+END_QUOTE"))
("<" . ("<" ">"))
("(" . ("(" ")"))
("{" . ("{" "}"))
("[" . ("[" "]")))) ; Why yes, we'll add more
(s-pair (assoc-default txt s-table)))
;; If txt doesn't match a table entry, then the pair will just be
;; the text for both the front and the back...
(unless s-pair
(setq s-pair (list txt txt)))
(save-excursion
(narrow-to-region start end)
(goto-char (point-min))
(insert (car s-pair))
(goto-char (point-max))
(insert (cadr s-pair))
(widen))))
(global-set-key (kbd "C-+") 'surround)
To make it easier to call from other functions, let’s wrap that wrapper:
(defun surround-text (txt)
(if (region-active-p)
(surround (region-beginning) (region-end) txt)
(surround nil nil txt)))
While C-k
kills text to the end of the line, what about killing
text before the point?
(defun ha/kill-line-before ()
"Kills text from the current cursor position to the beginning
of the current line."
(interactive)
(kill-region (point-at-bol) (point)))
(global-set-key (kbd "C-S-K") 'ha/kill-line-before)
According to this article, killing the rest of the line is fine,
but C-3 C-k
kills only 2½ lines. Not so useful.
This creates a macro that moves to the beginning of the line and then calls a function given to it. Quite an interesting approach:
(defmacro bol-with-prefix (function)
"Define a new function which calls FUNCTION.
Except it moves to beginning of line before calling FUNCTION when
called with a prefix argument. The FUNCTION still receives the
prefix argument."
(let ((name (intern (format "ha/%s-BOL" function))))
`(progn
(defun ,name (p)
,(format
"Call `%s', but move to the beginning of the line when called with a prefix argument."
function)
(interactive "P")
(when p
(forward-line 0))
(call-interactively ',function))
',name)))
And we re-bind them to functions that use them.
(global-set-key [remap paredit-kill] (bol-with-prefix paredit-kill))
(global-set-key [remap org-kill-line] (bol-with-prefix org-kill-line))
(global-set-key [remap kill-line] (bol-with-prefix kill-line))
(global-set-key (kbd "C-k") (bol-with-prefix kill-line))
I’m starting to appreciate the Hydra project.
(require 'hydra)
(defhydra hydra-zoom (global-map "<f9>")
"zoom"
("+" text-scale-increase "in")
("=" text-scale-increase "in")
("-" text-scale-decrease "out"))
Change window configuration and then return to the old
configuration with winner-mode. Use Control-C Arrow
keys to
cycle through window/frame configurations.
(winner-mode 1)
(defhydra hydra-winner (global-map "<f9> c")
"zoom"
("<left>" winner-undo "undo-window")
("<right>" winner-redo "redo-window"))
Easily manipulate the size of the windows using the arrow keys in a particular buffer window.
(require 'windmove)
(defun hydra-move-splitter-left (arg)
"Move window splitter left."
(interactive "p")
(if (let ((windmove-wrap-around))
(windmove-find-other-window 'right))
(shrink-window-horizontally arg)
(enlarge-window-horizontally arg)))
(defun hydra-move-splitter-right (arg)
"Move window splitter right."
(interactive "p")
(if (let ((windmove-wrap-around))
(windmove-find-other-window 'right))
(enlarge-window-horizontally arg)
(shrink-window-horizontally arg)))
(defun hydra-move-splitter-up (arg)
"Move window splitter up."
(interactive "p")
(if (let ((windmove-wrap-around))
(windmove-find-other-window 'up))
(enlarge-window arg)
(shrink-window arg)))
(defun hydra-move-splitter-down (arg)
"Move window splitter down."
(interactive "p")
(if (let ((windmove-wrap-around))
(windmove-find-other-window 'up))
(shrink-window arg)
(enlarge-window arg)))
(defhydra hydra-splitter (global-map "<f9>")
"splitter"
("<left>" hydra-move-splitter-left)
("<down>" hydra-move-splitter-down)
("<up>" hydra-move-splitter-up)
("<right>" hydra-move-splitter-right))
The Projectile project is a nifty way to run commands and search for files in a particular “project”. Its necessity is less now that IDO with flexible matching seems to always just find what I need.
Still…
(when (require 'projectile nil t)
(require 'projectile)
(projectile-global-mode))
The associated group name isn’t too useful when viewing the dired output.
(setq ls-lisp-use-insert-directory-program nil)
This enhancement to dired hides the ugly details until you hit ‘(’ and shows the details with ‘)’. I also change the […] to a simple asterisk.
(when (require 'dired-details nil t)
(dired-details-install)
(setq dired-details-hidden-string "* "))
The ability to create a dired buffer based on searching for files
in a directory tree with find-name-dired
is fantastic. The
following magic optimizes this approach:
(require 'find-dired)
(setq find-ls-option '("-print0 | xargs -0 ls -od" . "-od"))
The dired-x project seems useful:
(add-hook 'dired-load-hook
(lambda ()
(load "dired-x")))
The ability to edit files on remote systems is a wonderful win, since it means I don’t need to have my Emacs environment running on remote machines (still a possibility, just not a requirement).
According to the manual, I can access a file over SSH, via:
/ssh:10.52.224.67:blah
If I set the default method to SSH, I can do this:
/10.52.224.67:blah
So, let’s do it…
(setq tramp-default-method "ssh")
Come back someday, and see if the vagrant-tramp project starts working, as that would be nice to access files like:
/vagrant:collectd-server:/var/chef/cache/chef-stacktrace.out
According to Emacs Fu, we can use the wonderful Tramp to edit Root-owned files, as in:
(defun ha/find-file-as-root ()
"Like `ido-find-file, but automatically edit the file with
root-privileges (using tramp/sudo), if the file is not writable by
user."
(interactive)
(let ((file (ido-read-file-name "Edit as root: ")))
(unless (file-writable-p file)
(setq file (concat "/sudo:root@localhost:" file)))
(find-file file)))
The trick, as always, is finding the correct keybinding… but I
have the C-c f
as prefix for loading all sorts of files…
(global-set-key (kbd "C-c f r") 'ha/find-file-as-root)
According to Mickey, IDO is the greatest thing.
(setq ido-enable-flex-matching t)
(setq ido-everywhere t)
(flx-ido-mode 1)
According to Ryan Kneufeld, we could make IDO work vertically, which is much easier to read. For this, I use ido-vertically:
(require 'ido-vertical-mode)
(ido-mode 1)
(ido-vertical-mode 1)
; I like up and down arrow keys:
(setq ido-vertical-define-keys 'C-n-C-p-up-and-down)
Built using IDO.
(require 'smex)
(smex-initialize) ; Can be omitted. This might cause a (minimal) delay
(global-set-key (kbd "M-x") 'smex)
(global-set-key (kbd "M-z") 'smex) ;; Zap to char isn't so helpful
(global-set-key (kbd "M-X") 'smex-major-mode-commands)
;; This is our old M-x.
(global-set-key (kbd "C-c C-c M-x") 'execute-extended-command)
Not crazy about zap-to-char
being so close to the very useful
M-x
sequence, so…
(global-set-key (kbd "M-z") 'smex-major-mode-commands)
I have a voluminous amount of org-mode text files I routinely need search and filter.
I use the standard grep package in Emacs, but need a later version of Gnu Grep. On Mac OS X, run these two commands:
brew tap homebrew/dupes
brew install homebrew/dupes/grep
With Wilfred Hughes fancy ag package, I’ve switch from ack to the Silver Searcher:
brew install ag
Best part about the ag package, is not needing any configuration (as all functions are load-on demand).
ag-project-at-point
- sets the query with the word at point, use:
C-c p s s
ag-regexp
- searches for regular expressions in a chosen
directory (Note: the
ag
command prompts withregexp
, but it adds a--literal
option to the command) C-u
- Adding a prefix adds command line options, like
-s
or-i
to specify case-sensitivity.
Create collection of ignorable files so it doesn’t look in backup files:
#.*
Using the latest version of ag
? Highlight the keywords:
(use-package ag
:init (setq ag-highlight-search t)
:config (add-to-list 'ag-arguments "--word-regexp"))
Personally, I’m almost always looking for full words:
However, I also need a global indexing approach to searching through my notes, and since I’m usually on a Mac, I might as well use the Spotlight service that is already running:
(setq locate-command "mdfind") ;; Use Mac OS X's Spotlight
(global-set-key (kbd "C-c f l") 'locate)
The following function wraps locate-with-filter
to only grab
org-mode
files:
(defun locate-org-files (search-string)
(interactive "sSearch string: ")
(locate-with-filter search-string ".org$"))
(global-set-key (kbd "C-c f o") 'locate-org-files)
We could limit the location that Spotlight request searches:
(defun locate-my-org-files (search-string)
(let ((tech (concat (getenv "HOME") "/technical"))
(pers (concat (getenv "HOME") "/personal"))
(note (concat (getenv "HOME") "/notes"))
(jrnl (concat (getenv "HOME") "/journal")))
(-flatten (list "mdfind"
(if (file-exists-p tech) (list "-onlyin" tech))
(if (file-exists-p pers) (list "-onlyin" pers))
(if (file-exists-p note) (list "-onlyin" note))
(if (file-exists-p jrnl) (list "-onlyin" jrnl))
"-interpret" search-string))))
(setq locate-make-command-line 'locate-my-org-files)
However, the problem with locate, is it doesn’t show me any
context. My find-notes script uses both mdfind
and grep
to both
better search and display some useful context.
Just need to wrap that in a function:
(defun find-notes (words)
"Uses my 'find-notes' shell script as a better grep
utility. Not only does it show the results in a clickable list,
it also highlights the result, allowing us to put more context in
the output."
(interactive "sSearch for words:")
(let ((program (concat (getenv "HOME") "/bin/find-notes"))
(buffer-name (concat "*find-notes: " words "*")))
(call-process program nil buffer-name t words)
(switch-to-buffer buffer-name)
(read-only-mode 1)
(grep-mode)
(toggle-truncate-lines)
(beginning-of-buffer)
(dolist (word (split-string words))
(highlight-regexp word))))
(global-set-key (kbd "C-x C-n") 'find-notes)
According to this article, Emacs already has the recent file listing available, just not turned on.
(require 'recentf)
(recentf-mode 1)
(setq recentf-max-menu-items 25)
(global-set-key (kbd "C-c f f") 'recentf-open-files)
This setting moves all backup files to a central location. Got it from this page.
(setq backup-directory-alist
`(("." . ,(expand-file-name
(ha/emacs-subdirectory "backups")))))
Make backups of files, even when they’re in version control
(setq vc-make-backup-files t)
And let’s make sure our files are saved if we wander off:
(defun save-all ()
"Saves all dirty buffers without asking for confirmation."
(interactive)
(save-some-buffers t))
(add-hook 'focus-out-hook 'save-all)
Just beginning to get a collection of templates to automatically insert if a blank file is loaded.
(add-hook 'find-file-hook 'auto-insert)
(auto-insert-mode 1)
(setq auto-insert-directory (ha/emacs-subdirectory "templates/"))
(setq auto-insert-query nil) ;;; If you don't want to be prompted before insertion
(define-auto-insert "\.el" "default-lisp.el")
Some auto insertion requires entering data for particular fields, and for that Yasnippet is better, so in this case, we combine them:
(defun ha/autoinsert-yas-expand()
"Replace text in yasnippet template."
(yas-expand-snippet (buffer-string) (point-min) (point-max)))
(define-auto-insert "\\.sh$" ["default-sh.sh" ha/autoinsert-yas-expand])
(define-auto-insert "/bin/" ["default-sh.sh" ha/autoinsert-yas-expand])
Using company-mode for all my auto completion needs.
(add-hook 'after-init-hook 'global-company-mode)
If the company-quickhelp is installed, let’s take advantage of it:
(company-quickhelp-mode 1)
This also requires pos-tip.
And I really like this idea of being able to easily insert math symbols based on LaTeX keywords:
(add-to-list 'company-backends 'company-math-symbols-unicode)
The yasnippet project allows me to create snippets of code that can be brought into a file, based on the language.
(require 'yasnippet)
(yas-global-mode 1)
Helper function so that we can automatically expand a snippet programmatically, which makes it easier to do this with auto-insert:
(defun yas--expand-by-uuid (mode uuid)
"Exapnd snippet template in MODE by its UUID"
(yas-expand-snippet
(yas--template-content
(yas--get-template-by-uuid mode uuid))))
Inside the snippets
directory should be directories for each
mode, e.g. clojure-mode
and org-mode
. This connects the mode
with the snippets.
(add-to-list 'yas-snippet-dirs (ha/emacs-subdirectory "snippets"))
js2-mode is good, but its name means that Yas’ won’t automatically
link it to its js-mode
. This little bit of magic does the linking:
(add-hook 'js2-mode-hook '(lambda ()
(make-local-variable 'yas-extra-modes)
(add-to-list 'yas-extra-modes 'js-mode)
(yas-minor-mode 1)))
Using the built-in Abbreviation Mode, and setting it up globally.
(setq-default abbrev-mode t)
Stop asking whether to save newly added abbrev when quitting Emacs.
(setq save-abbrevs nil)
While you can make abbreviations in situ, I figured I should pre-load a bunch that I use, but make a distinction between abbreviations that would be available globally, and in particular modes (especially the text modes, like org-mode):
(define-abbrev-table 'global-abbrev-table
'(("8ha" "Howard Abrams")
("8fun" "function")
("8l" "lambda")))
This allows me to write 8ha
as Howard Abrams
.
(define-abbrev-table 'text-mode-abbrev-table
'(("8js" "JavaScript")
("8cs" "CoffeeScript")
("8os" "OpenStack")
("8ng" "AngularJS")
("8wd" "Workday")
("btw" "by the way")
("note:" "*Note:*")))
Note: Capitalizing the first letter, i.e. Btw
, expands the
abbreviation with an initial capital, i.e. By the way
… Sweet.
According to this discussion, we can correct a misspelled word
with C-x C-i
and it will use the abbreviation mode to
automatically correct that word…as long as you misspell it the
same way each time.
(define-key ctl-x-map "\C-i" 'endless/ispell-word-then-abbrev)
(defun endless/ispell-word-then-abbrev (p)
"Call `ispell-word'. Then create an abbrev for the correction made.
With prefix P, create local abbrev. Otherwise it will be global."
(interactive "P")
(let ((bef (downcase (or (thing-at-point 'word) ""))) aft)
(call-interactively 'ispell-word)
(setq aft (downcase (or (thing-at-point 'word) "")))
(unless (string= aft bef)
(message "\"%s\" now expands to \"%s\" %sally"
bef aft (if p "loc" "glob"))
(define-abbrev
(if p global-abbrev-table local-abbrev-table)
bef aft))))
(setq save-abbrevs t)
(setq-default abbrev-mode t)
I like spell checking with FlySpell, which uses the built-in spell-check settings of ispell.
Seems like I would want this automatically turned on for all text modes (but not for log files).
(dolist (hook '(text-mode-hook org-mode-hook))
(add-hook hook (lambda () (flyspell-mode 1))))
(dolist (hook '(change-log-mode-hook log-edit-mode-hook org-agenda-mode-hook))
(add-hook hook (lambda () (flyspell-mode -1))))
The downside of using single quotes, like ’ is that the ispell dictionary doesn’t recognize it as an apostrophe, so don’t is often looked at as incorrect.
(eval-after-load "ispell"
'(add-to-list 'ispell-local-dictionary-alist '(nil
"[[:alpha:]]"
"[^[:alpha:]]"
"['‘’]"
t
("-d" "en_US")
nil
utf-8)))
Just not sure which of the three major spell checking systems to use. Currently, liking ASpell at this point.
Setting this to the American dictionary seems to make it work better with Homebrew.
(setq ispell-dictionary "american")
ASpell automatically configures a personal dictionary at: ~/.aspell.en.pws, so no need to configure that.
Seems that the ASpell is better supported than ISpell.
brew install aspell
And then configure it with the following:
(setq ispell-program-name "/usr/local/bin/aspell")
(setq ispell-extra-args '("--sug-mode=ultra" "--lang=en_US"))
To get Flyspell to work with Aspell, I need to do this:
(setq ispell-list-command "--list")
Using thesaurus.el to access the Big Huge Labs’ Online Thesaurus while editing my expressive literary style in my text files.
(when (require 'thesaurus nil t)
(thesaurus-set-bhl-api-key-from-file "~/.emacs.d/bighugelabs.apikey.txt")
(define-key personal-global-map (kbd "t") 'thesaurus-choose-synonym-and-replace))
Turn linum-mode
on/off with Command-K
(see the Macintosh
section above). However, I turn this on automatically for
programming modes.
(add-hook 'prog-mode-hook 'linum-mode)
If we make the line numbers a fixed size, then increasing or decreasing the font size doesn’t truncate the numbers:
(defun fix-linum-size ()
(interactive)
(set-face-attribute 'linum nil :height 110))
(add-hook 'linum-mode-hook 'fix-linum-size)
If we alternate between line numbers and no-line numbers, I also have to turn on/off the fringe. Actually, this is really only useful when giving presentations.
(defun linum-off-mode ()
"Toggles the line numbers as well as the fringe. This allows me
to maximize the screen estate."
(interactive)
(if linum-mode
(progn
(fringe-mode '(0 . 0))
(linum-mode -1))
(fringe-mode '(8 . 0))
(linum-mode 1)))
(global-set-key (kbd "A-C-K") 'linum-off-mode)
(global-set-key (kbd "s-C-K") 'linum-off-mode) ;; For Linux
I’m intrigued with the linum-relative mode (especially since I can
toggle between them). The idea is that I can see the line that I
want to jump to (like one 9 lines away), and then C-9 C-n
to
quickly pop to it.
(if (not (require 'linum-relative nil t))
;; If this isn't installed, we'll just toggle between showing and
;; not showing the line numbers.
(progn
(global-set-key (kbd "A-k") 'linum-mode)
(global-set-key (kbd "s-k") 'linum-mode)) ;; For Linux
;; Otherwise, let's take advantage of the relative line numbering:
(defun linum-new-mode ()
"If line numbers aren't displayed, then display them.
Otherwise, toggle between absolute and relative numbers."
(interactive)
(if linum-mode
(linum-relative-toggle)
(linum-mode 1)))
(global-set-key (kbd "A-k") 'linum-new-mode)
(global-set-key (kbd "s-k") 'linum-new-mode)) ;; For Linux
Use the M-n
to search the buffer for the word the cursor is
currently pointing. M-p
to go backwards.
(load-library "smart-scan")
When I save, I want to always, and I do mean always strip all trailing whitespace from the file.
(add-hook 'before-save-hook 'delete-trailing-whitespace)
Only after you’ve started an isearch-forward
do you wish you had
regular expressions available, so why not just switch those defaults?
(global-set-key (kbd "C-s") 'isearch-forward-regexp)
(global-set-key (kbd "C-r") 'isearch-backward-regexp)
(global-set-key (kbd "C-M-s") 'isearch-forward)
(global-set-key (kbd "C-M-r") 'isearch-backward)
The Visual Regular Expressions project highlights the matches while you try to remember the differences between Perl’s regular expressions and Emacs’…
Begin with C-c r
then type the regexp. To see the highlighted
matches, type C-c a
before you hit ‘Return’ to accept it.
(require 'visual-regexp)
(define-key global-map (kbd "C-c r") 'vr/replace)
(define-key global-map (kbd "C-c q") 'vr/query-replace)
;; if you use multiple-cursors, this is for you:
(define-key global-map (kbd "C-c m") 'vr/mc-mark)
Flycheck seems to be quite superior to good ol’ Flymake.
(when (require 'flycheck nil t)
(add-hook 'after-init-hook #'global-flycheck-mode))
The most interesting aspect is that it doesn’t support Clojure.
With this free feature, deleting any space, deletes ALL spaces. Not sure if I like it, or not.
(require 'hungry-delete)
(global-hungry-delete-mode)
All programming languages require some sort of tagging. but after thirty years, we are still using good ol’ ctags…well, Exuberant Ctags. Install with Homebrew:
brew install --HEAD ctags
On Ubuntu Linux, do:
sudo apt-get install -y exuberant-ctags
Note: for every project, run the following command:
ctags -e -R .
However, with the fancy new ctags-update package, this happens whenever we save a file:
(autoload 'turn-on-ctags-auto-update-mode
"ctags-update" "turn on `ctags-auto-update-mode'." t)
(add-hook 'c-mode-common-hook 'turn-on-ctags-auto-update-mode)
Now, use the following keys:
- M-.
- To find the tag at point to jump to the function’s definition when the point is over a function call. It is a dwim-type function.
- M-,
- jump back to where you were.
- M-?
- find a tag, that is, use the Tags file to look up a definition. If there are multiple tags in the project with the same name, use `C-u M-.’ to go to the next match.
M-x tags-search
- regexp-search through the source files
indexed by a tags file (a bit like
grep
) M-x tags-query-replace
- query-replace through the source files indexed by a tags file
M-x tags-apropos
- list all tags in a tags file that match a regexp
M-x list-tags
- list all tags defined in a source file
Other helpful movement commands I need to remember:
- C-M-a - Jump to the start of a function
- C-M-e - Jump to the end of a function
Files in my bin
directory (but only if it doesn’t have any
other extension), should start in sh-mode
:
(setq auto-mode-alist (append auto-mode-alist '(("/bin/" . sh-mode))))
Sure, everything here is in Emacs Lisp, but this section helps me write more of that.
The most important change to Emacs Lisp is colorizing the variables:
(add-hook 'emacs-lisp-mode-hook 'color-identifiers-mode)
One of the cooler features of Emacs is the ParEdit mode which keeps all parenthesis balanced in Lisp-oriented languages. See this cheatsheet.
(require 'paredit)
Associate the following Lisp-based modes with ParEdit:
(defun turn-on-paredit () (paredit-mode t))
Associate the following Lisp-based modes with ParEdit:
(add-hook 'emacs-lisp-mode-hook 'turn-on-paredit)
(add-hook 'lisp-mode-hook 'turn-on-paredit)
(add-hook 'lisp-interaction-mode-hook 'turn-on-paredit)
(add-hook 'scheme-mode-hook 'turn-on-paredit)
(add-hook 'clojure-mode-hook 'turn-on-paredit)
(add-hook 'cider-repl-mode-hook 'turn-on-paredit)
(add-hook 'sibiliant-mode-hook 'turn-on-paredit)
According to the ParEdit documentation, we can allow a Return
keypress to insert a couple of indented newlines, if within an
s-expression. While within paredit
, simply press )
to shrink
back up the extra whitespace.
(defun electrify-return-if-match (arg)
"If the text after the cursor matches `electrify-return-match' then
open and indent an empty line between the cursor and the text. Move the
cursor to the new line."
(interactive "P")
(let ((case-fold-search nil))
(if (looking-at "[\]}\)\"]")
(save-excursion (newline-and-indent)))
(newline arg)
(indent-according-to-mode)))
Finally, bind the function to a key:
(add-hook 'paredit-mode-hook
(lambda ()
(local-set-key (kbd "RET") 'electrify-return-if-match)))
Might as well pretty up the lambdas, and other functions using the new 24.4 prettify-symbols-mode:
This approach seems to work and looks pretty good:
(when (fboundp 'global-prettify-symbols-mode)
(defconst lisp--prettify-symbols-alist
'(("lambda" . ?λ)
("curry" . ?»)
("rcurry" . ?«)
("comp" . ?∘)
("compose" . ?∘)
("." . ?•)))
(global-prettify-symbols-mode 1))
Words with dashes don’t separate words in Lisp:
(dolist (c (string-to-list ":_-?!#*"))
(modify-syntax-entry c "w" emacs-lisp-mode-syntax-table))
Note:: Need to change this to work with the v24.4 super-word.
While writing and documenting Emacs Lisp code, it would be helpful to insert the results of evaluation of an s-expression directly into the code as a comment:
(defun eval-and-comment-output ()
"Add the output of the sexp as a comment after the sexp"
(interactive)
(save-excursion
(end-of-line)
(condition-case nil
(princ (concat " ; -> " (pp-to-string (eval (preceding-sexp))))
(current-buffer))
(error (message "Invalid expression")))))
And since it is Emacs Lisp, let’s bind globally:
(global-set-key (kbd "C-x e") 'eval-and-comment-output)
See emacs-clojure.el for details on working with Clojure. Not sure if I should just load it directly, like:
(require 'init-clojure)
As soon as a I have a project that requires Java (and doesn’t allow me to work on either Clojure or Scala, I’ll update my old Java initialization section.
See my emacs-ruby.el file for details on working with Ruby. Typically, my emacs-local.el file would do the work of requiring this for particular hosts or projects.
(require 'init-ruby)
See emacs-python.el for details on working with Python. Not sure if I should just load it directly, like:
(require 'init-python)
See emacs-javascript.el for details on working with JavaScript.
(require 'init-javascript)
See emacs-web.el for details on working with HTML and its ilk.
(require 'init-web)
See emacs-org-mode.el for details on my Org-Mode settings.
(require 'init-org-mode)
Git is already part of Emacs. However, Magit is sweet.
(require 'magit)
(global-set-key (kbd "C-x g") 'magit-status)
(define-key personal-global-map (kbd "g") 'magit-status)
Temporarily drop into Magit while looking at the file system
(through the standard load-file
) without actually loading a file.
(define-key ido-completion-map
(kbd "C-x g") 'ido-enter-magit-status)
I like having Magit to run in a full screen mode, and took this
defadvice
idea from Sven Magnars:
(defadvice magit-status (around magit-fullscreen activate)
(window-configuration-to-register :magit-fullscreen)
ad-do-it
(delete-other-windows))
Now, we have to have the q
command recover the window session
that was stored in a window register:
(defun magit-quit-session ()
"Restores the previous window configuration and kills the magit buffer"
(interactive)
(kill-buffer)
(jump-to-register :magit-fullscreen))
(define-key magit-status-mode-map (kbd "q") 'magit-quit-session)
Install and use the Git Gutter Fringe to see what lines are dirty from Git’s perspective.
(if (window-system)
(when (require 'git-gutter-fringe nil t)
(global-git-gutter-mode +1)
(setq-default indicate-buffer-boundaries 'left)
(setq-default indicate-empty-lines +1)))
To see a blame mode, use either vc-annotate
(C-x v g
) or
magit-blame-mode
.
Simple function to stage and commit the current file. Even asks to save the current file.
(defun ha/commit-file (&optional args)
"Stage and commit the current file. With a prefix argument, ARGS,
ammends the previous commit."
(interactive "P")
(magit-stage-file (buffer-file-name))
(if args
(magit-commit-amend)
(magit-commit)))
(global-set-key (kbd "C-x G") 'ha/commit-file)
Perhaps we can do Github pull requests from within Emacs, after
reading this blog entry. Just do # g g
in Magit to list the pull
requests.
(when (require 'magit-gh-pulls nil t)
(add-hook 'magit-mode-hook 'turn-on-magit-gh-pulls))
Don’t use Markdown nearly as much as I used to, but I’m surprised that the following extension-associations aren’t the default:
(autoload 'markdown-mode "markdown-mode.el"
"Major mode for editing Markdown files" t)
(add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
(add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
Using the surround
function, I create some wrapper
functions to make it easier to bold text in Markdown files:
(defun markdown-bold () "Wraps the region with double asterisks."
(interactive)
(surround-text "**"))
(defun markdown-italics () "Wraps the region with asterisks."
(interactive)
(surround-text "*"))
(defun markdown-code () "Wraps the region with equal signs."
(interactive)
(surround-text "`"))
Now I can associate some keystrokes to markdown-mode
:
(add-hook 'markdown-mode-hook
(lambda ()
(local-set-key (kbd "A-b") 'markdown-bold)
(local-set-key (kbd "s-b") 'markdown-bold) ;; For Linux
(local-set-key (kbd "A-i") 'markdown-italics)
(local-set-key (kbd "s-i") 'markdown-italics)
(local-set-key (kbd "A-=") 'markdown-code)
(local-set-key (kbd "s-=") 'markdown-code)))
Now that Atlassian changed this Wiki system so that confluence.el
doesn’t work anymore (yeah, not an improvement, Atlassian), I can
still use the confluence-edit-mode
for anything with a .wiki
extension.
(autoload 'confluence-edit-mode "confluence-edit-mode.el"
"Major mode for editing Wiki documents" t)
(add-to-list 'auto-mode-alist '("\\.wiki\\'" . confluence-edit-mode))
I would also like to create and use some formatting wrappers.
(defun wiki-bold () "Wraps the region with single asterisks."
(interactive)
(surround-text "*"))
(defun wiki-italics () "Wraps the region with underbars."
(interactive)
(surround-text "_"))
(defun wiki-code () "Wraps the region with curly brackets."
(interactive)
(surround-text "{{" "}}"))
Now I can associate some keystrokes to markdown-mode
:
(add-hook 'confluence-edit-mode-hook
(lambda ()
(local-set-key (kbd "A-b") 'wiki-bold)
(local-set-key (kbd "A-i") 'wiki-italics)
(local-set-key (kbd "A-=") 'wiki-code)))
Install the Graphviz project using Homebrew:
brew install graphviz
brew link graphviz
brew install plantuml
To get PlantUML working in Emacs, first, download the Jar and place
in the ~/bin
directory. We then set the “mode” working for
editing the files:
(setq plantuml-jar-path (concat (getenv "HOME") "/bin/plantuml.jar"))
Second, to get PlantUML working in org-mode, set a different variable:
(setq org-plantuml-jar-path (concat (getenv "HOME") "/bin/plantuml.jar"))
This section became involved, and has moved on to emacs-browser file.
(require 'init-browser)
See emacs-eshell.el for details of configuring and using EShell.
(require 'init-eshell)
I find reading Twitter and IRC in Emacs a good idea. Really. Small bits of the Emacs window are accessible and whatnot.
(require 'circe nil t)
Using the jabber.el project to connect up to Google Talk and what
not. To begin, make sure you brew install gnutls
(when (require 'jabber nil t)
(setq starttls-use-gnutls t
starttls-gnutls-program "gnutls-cli"
starttls-extra-arguments '("--starttls" "--insecure"))
(setq
jabber-history-enabled t
jabber-use-global-history nil
jabber-backlog-number 40
jabber-backlog-days 30)
(defun my-jabber-chat-delete-or-bury ()
(interactive)
(if (eq 'jabber-chat-mode major-mode)
(condition-case e
(delete-frame)
(error
(if (string= "Attempt to delete the sole visible or iconified frame"
(cadr e))
(bury-buffer))))))
(define-key jabber-chat-mode-map [escape]
'my-jabber-chat-delete-or-bury)
(when (require 'autosmiley nil t)
(add-hook 'jabber-chat-mode-hook 'autosmiley-mode)))
To chat simply press: C-x C-j C-c
… hahaha. I doubt I can
remember that one. Perhaps.
Make sure that PATH
variable for finding binary files can is the
same as what Emacs will look for binary files. This little magic,
starts up a shell, gets its path, and then uses that for the
exec-path
:
(when window-system
(let ((path-from-shell (shell-command-to-string "/bin/bash -l -c 'echo $PATH'")))
(setenv "PATH" path-from-shell)
(setq exec-path (split-string path-from-shell path-separator))))
If we are running in a windowed environment where we can set up fonts and whatnot, call the ‘mac’ stuff… which will still work for Linux too.
(if (window-system)
(require 'init-client)
(require 'init-server))
Before we finish, we need to check if there is a local file for us
to load and evaluate. We assume the local file has been tangled
and provides the init-local
key:
(require 'init-local nil t)
After the first load, we can reload this with a require:
(provide 'init-main)
Before you can build this on a new system, make sure that you put
the cursor over any of these properties, and hit: C-c C-c