This is my Emacs configuration. Most of it is stored as Emacs Lisp source blocks in in this Org-mode file.
This file is called “config.org” and not “init.org” because during the loading process, it creates a correspondingly-named “.el” file, and there already is an “init.el” here. Note also that “README.org” is a link here.
In order to install this config, simply clone this repository into
~/.emacs.d/
:
git clone https://github.com/DarwinAwardWinner/dotemacs.git ~/.emacs.d
If you have an old .emacs
file or .emacs.d
directory that you want
to save, move them out of the way first so that they don’t interfere
with the new config.
The first time you start up Emacs after installing this configuration,
the code in init.el
will install org-mode and then use that to run
the Emacs lisp code in this file, which will in turn install the many
packages used by this config. This could take quite a while.
Subsequent startups will skip the install process and should be
relatively fast.
You can run the config in batch mode at the command line without actually launching Emacs as an editor using a command like this:
$ emacs -Q -batch -nw -l ~/.emacs.d/init.el
This will load the entire config, forcing an installation of all required packages, and you can watch the progress in your terminal. Then when you launch Emacs, it will start up much more quickly (although it will still take several seconds to start).
All code in this config is licensed under the GPL3 (See the =LICENSE= file in this repo or the copy included with Emacs).
Exceptions to this may be noted in cases where code has been copied from another source with a different license.
The =init.el= file does all the bootstrapping necessary to load the configuration in this Org-mode file. It is required as a separate non-Org-mode file because this config does not use the version of Org-mode included with Emacs, so the proper version of Org mode must be installed before loading this file. Otherwise, loading this file first would auto-load the built-in version of Org-mode and prevent use of the newly-installed version until Emacs was restarted.
Note that the bootstrapping code also installs the Straight package manager, which it uses to install Org mode. Here we complete the setup so Straight can be used to manage all pacakges below.
The =early-init.el= file contains any code that must be executed early during Emacs’ startup process.
I am confident that Emacs will not run out of memory during init, so there’s no point performing many small GC passes during init. Instead, GC is disabled during init and re-enabled afterward, resulting in one big GC pass at the end.
This is recommended by the Straight package manager.
In order to enable lexical binding in the config, this must be the first line of Emacs Lisp:
;; -*- lexical-binding: t -*-
This section sets up a functions and other aspects of the environment that will be needed later in the configuration, so they need to be defined/prepared up front.
If multiple Emacsen are starting up at the same time, one of them may encounter a file locked by the other. Normally, it would prompt the user about what to do, but if it is running in batch mode, this will not work. This advice tells Emacs to instead simply wait for up to 30 seconds for the file to become unlocked.
;; Needed for loop macro
(require 'cl-macs)
;; userlock.el doesn't provide a feature
(load "userlock")
(defvar lock-file-noninteractive-wait-time 30
"Seconds to wait for a file lock before signaling `file-locked'.
If Emacs is running in batch mode, instead of asking the user
whether it should edit a locked file, it will simply wait up to
this long for the file to become unlocked.
If this variable is nil, Emacs will wait forever for a locked file
to become unlocked.")
(define-advice ask-user-about-lock
(:around (oldfun file opponent) wait-when-noninteractive)
"When running in batch mode, always wait for file locks to clear.
Normally, when Emacs attempts to open a file that is currently
locked by another Emacs process, it will ask the user how to
handle the conflict. However, this is not an option in batch
mode. Instead, in batch mode, Emacs will simply wait until the
file becomes unlocked and then continue. If the file is still
locked after waiting for `lock-file-noninteractive-wait-time'
seconds, a `file-locked' error will be signalled."
(if noninteractive
(cl-loop
initially do (message "Waiting for %S to be unlocked." file)
with still-locked = t
with wait-time = lock-file-noninteractive-wait-time
while still-locked
;; This function is only called after a lock was detected, so
;; start() by waiting 1 second before checking again.
do (sit-for 1)
;; Count down from wait-time to zero, if it's non-nil
while (or (null wait-time)
(< 0 (setq wait-time (1- wait-time))))
;; Check if the file is still locked
for still-locked = (file-locked-p file)
finally return
(if still-locked
(signal 'file-locked (list file opponent))
;; `t' means "steal the file", which should be fine since
;; it's no longer locked. But first we wait one more second.
;; (sit-for 1)
t)
)
(funcall oldfun file opponent)))
This code builds up the without-user-input
macro, which is like
progn
except that if BODY makes any attempt to read user input, all
further execution is canceled and the form returns nil (note that it
does not signal an error, it simply returns).
(require 'cl-macs)
(defmacro without-minibuffer (&rest body)
"Like `progn', but stop and return nil if BODY tries to use the minibuffer.
Also disable dialogs while evaluating BODY forms, since dialogs
are just an alternative to the minibuffer."
(declare (indent 0))
`(catch 'tried-to-use-minibuffer
(minibuffer-with-setup-hook
(lambda (&rest args) (throw 'tried-to-use-minibuffer nil))
(let ((use-dialog-box)) ; No cheating by using dialogs instead of minibuffer
,@body))))
(defmacro without-functions (flist &rest body)
"Evaluate BODY, but stop and return nil if BODY calls any of the functions named in FLIST."
(declare (indent 1))
(let* (;; Functions are disabled by setting their body to this
;; temporarily.
(fbody
'((&rest args) (throw 'forbidden-function nil)))
;; This will form the first argument to `flet'
(function-redefinitions
(mapcar (lambda (fname) (cons fname fbody)) flist)))
`(catch 'forbidden-function
(cl-flet ,function-redefinitions
,@body))))
(defmacro without-user-input (&rest body)
"Like `progn', but prevent any user interaction in BODY."
(declare (indent 0))
`(without-functions (read-event)
(without-minibuffer
;; Tell BODY that it is running noninteractively
(let ((noninteractive t))
,@body))))
This allows emacsclient
to connect. We avoid starting the server in
batch mode since there is no point in that case. We also avoid
starting the server if it’s already running. We start the server as
soon as possible to make it less likely that a second instance of
Emacs will attempt to start because it didn’t find a server already
running.
Errors are ignored in case there are two instances of Emacs running. The first Emacs will start the server, and the second will silently fail, since a server is already running.
(unless (or noninteractive (bound-and-true-p server-process))
(condition-case err
(without-user-input (server-start))
(error (display-warning 'config (format "Error starting server: %S" err)))))
This tells use-package
(and by extension req-package
) to use
Straight to install packages by default. See here for more
information.
(setq straight-use-package-by-default t)
This config uses ~req-package~ to configure packages, using Straight
to install them automatically. So we first need to install req-package
and its dependencies (particularly ~use-package~).
(straight-use-package
'(req-package
:type git
:flavor melpa
:host github
:repo "edvorg/req-package"
;; This branch contains the required fixes for el-get support
:branch "develop"))
(require 'req-package)
This installs and loads the s
and f
packages for string and filename
manipulation. These are used in various places throughout this config,
so we install them right now.
(req-package f :force t)
(req-package s :force t)
Regardless of which directory Emacs is started from, I want the
initial non-file buffers such as *scratch*
and *Messages*
to have
their default-directory
set to my home directory. This code goes
through all non-file buffers whose default directories are the emacs
starting directory or the root directory, and changes their default
directories to my home directory.
This code only runs during init. If the config is reloaded later after init, this will not run again.
(unless after-init-time
(let ((startup-dir default-directory))
(unless (f-same? default-directory "~")
(dolist (buf (buffer-list))
(ignore-errors
(with-current-buffer buf
(when (and (null (buffer-file-name buf))
(not (bound-and-true-p dired-directory))
(or (f-same? default-directory startup-dir)
(f-root? default-directory)))
(message "Changing default dir from %s to ~/ in %s"
default-directory (buffer-name buf))
(cd "~"))))))))
We define a function to defer evaluation until the end of initialization.
(defun eval-after-init (form)
"Like `eval', but waits until after init.
During emacs initialization, this registers FORM to be evaluated
in `after-init-hook'. After initialization, this is equivalent
to `(eval FORM)'."
(if after-init-time
(eval form)
;; We don't use `add-hook' here because we want to add the form
;; unconditionally, even if it is a duplicate.
(add-to-list 'after-init-hook `(lambda () ,form))))
This defines a macro that saves the modified status of current buffer and restores it after evaluating body.
(defmacro preserve-buffer-modified-p (&rest body)
"Evaluate BODY, then restore buffer modified status.
This can be used to edit the contents of a buffer while telling
Emacs that the buffer is still not modified."
(declare (indent 0))
`(let ((bmp (buffer-modified-p)))
(prog1
(progn ,@body)
(set-buffer-modified-p bmp))))
(defmacro define-activator (mode)
"Define `turn-on-MODE', a function that turns on MODE.
MODE is a symbol naming a function defined with
`define-minor-mode' or similar. Specifically, it should turn on
the mode when called with a positive number."
(let ((activator-name (intern (concat "turn-on-" (symbol-name mode))))
(docstring (format "Turn on `%s'." mode)))
`(defun ,activator-name ()
,docstring
(interactive)
(,mode 1))))
For any code that wants to save some state to disk (e.g. undo-tree), I configure it to save its state somewhere in this directory.
(make-directory (f-join user-emacs-directory "persistence") 'recursive)
My shell configuration adds a lot of things to PATH dynamically
(pyenv, perlbrew, etc.), so rather than try to emulate all that logic
in Emacs, we simply run a shell and tell it to print out the
environment variables we care about. Then we set them in Emacs. For
PATH, we also set the Elisp variable exec-path
, which is not
auto-updated when you modify the environment variable.
This step needs to be done early, because some later configuration items depend on having the full PATH available.
(req-package exec-path-from-shell
:force t
:config (exec-path-from-shell-initialize))
(Note: This needs to be done on every system that I use this config on.)
If this function is undefined (which happens with Emacs is compiled without GUI support), git-gutter-fringe won’t load. See nschum/fringe-helper.el#5.
;; Define as a no-op if not already defined
(unless (fboundp 'define-fringe-bitmap)
(defun define-fringe-bitmap (bitmap &rest _)
"This is a no-op placeholder function."
;; Return the symbol, just like the normal function does.
bitmap))
This section declares all the packages required by the config and sets up variables, key bindings, and such for some of them.
Eval the following Elisp code to re-sort the below entries (this code line is not in a source block because it is not part of the actual configuration and should not be executed upon init):
CODE (mapc (apply-partially #'org-sort-entries nil) (nreverse '(?O ?a))) CODE
This allows TAB and S-TAB to increase and decrease the nesting depth (and corresponding indentation) of the current lisp expression.
(req-package adjust-parens
:commands adjust-parens-mode
:init
(define-activator adjust-parens-mode)
:hook ((lisp-interaction-mode emacs-lisp-mode) .
turn-on-adjust-parens-mode))
amx is an enhanced M-x.
(req-package amx)
Anzu mode displays the total number of matches and which one is currently highlighted while doing an isearch.
(req-package anzu)
This loads apache-mode and sets it up to detect the vim “syntax=apache” declaration.
(req-package apache-mode
:mode ("/apache2/.*\\.conf\\'" . apache-mode)
:init (progn
(defun apache-magic-mode-detect ()
(string-match-p "^\\s-*#.*\\bsyntax=apache\\b" (buffer-string)))
(add-to-list 'magic-mode-alist '(apache-magic-mode-detect . apache-mode))))
(req-package apt-sources-list)
(req-package async)
Auto-complete mode provides IDE-style popup completions while editing.
(req-package auto-complete
:init (global-auto-complete-mode 1))
This package slightly dims the background of inactive windows so as to highlight which window is currently active.
(req-package auto-dim-other-buffers)
(req-package autopair
:config
(autopair-global-mode 1)
(setq autopair-skip-whitespace 'chomp)
(setq autopair-skip-criteria 'always)
(define-advice autopair--post-command-handler
(:after (&rest args) realign-org-tags)
"Re-align org-mode headline tags after doing autopair."
(when (and (eq major-mode 'org-mode)
(org-at-heading-p))
(org-align-tags)))
:defer nil)
This changes the cursor from a 1-character block to a bar in between characters.
(req-package bar-cursor)
Beacon mode causes the “spotlight” to shine on the cursor whenever the window scrolls, in order to highlight the new position of the cursor.
(req-package beacon)
(req-package bind-key)
This binds Shift+click to open a link
(req-package browse-url
:bind ("<s-mouse-1>" . browse-url-at-mouse))
(req-package bs
:bind ("C-x C-b" . bs-show))
Buttercup is a testing framework that I use to test several of my Emacs Lisp packages.
(req-package buttercup
:straight
(buttercup
:type git
:flavor melpa
:files (:defaults "bin" "buttercup-pkg.el")
:host github
:repo "jorgenschaefer/emacs-buttercup"
:fork (:host github
:repo "DarwinAwardWinner/emacs-buttercup")))
(req-package caddyfile-mode
:mode (("Caddyfile\\." . caddyfile-mode)
("caddy\\.conf\\'" . caddyfile-mode))
:config
(defun my-caddyfile-hook ()
(setq-local tab-width 4)
(setq-local indent-tabs-mode nil))
(add-hook 'caddyfile-mode-hook #'my-caddyfile-hook))
(req-package cask)
(req-package cask-mode)
(req-package centered-cursor-mode)
(req-package cl-lib)
This package higlights cl-lib functions and macros, and also higlights old-styls cl functions and macros in orange as a reminder not to use them.
(req-package cl-lib-highlight
:config
(cl-lib-highlight-initialize)
(cl-lib-highlight-warn-cl-initialize))
Replace perl-mode
with cperl-mode
in auto-mode-alist
and
interpreter-mode-alist
. Also associate the “.t” extension with perl
(perl test files). Last, define a keyboard shortcut for
cperl-perldoc
.
(req-package cperl-mode
:init
(progn
(mapc
(lambda (x)
(when (eq (cdr x) 'perl-mode)
(setcdr x 'cperl-mode)))
auto-mode-alist)
(mapc
(lambda (x)
(when (eq (cdr x) 'perl-mode)
(setcdr x 'cperl-mode)))
interpreter-mode-alist))
:bind (:map cperl-mode-map
("C-c C-d" . cperl-perldoc))
:mode ("\\.[tT]\\'" . cperl-mode))
(req-package creole-mode
:mode (".creole\\'" . creole-mode))
(req-package crontab-mode)
This provides some useful utility functions, many of which are more advanced versions of existing commands.
(req-package crux
:bind (("C-c C-e" . crux-eval-and-replace)
("C-x 4 t" . crux-transpose-windows)
([remap move-beginning-of-line] . crux-move-beginning-of-line)))
https://qiita.com/tm_tn/items/6c9653847412d115bec0
;; (req-package cwl-mode)
See the flycheck config for the remainder of the config.
Decide provides functions for dice rolling and similar tasks.
(req-package decide)
This hides or shortens the names of minor modes in the modeline.
The below code sets up a custom variable diminished-minor-modes
to
control the diminishing of modes.
(req-package diminish
:config
(defun diminish-undo (mode)
"Restore mode-line display of diminished mode MODE to its minor-mode value.
Do nothing if the arg is a minor mode that hasn't been diminished.
Interactively, enter (with completion) the name of any diminished mode (a
mode that was formerly a minor mode on which you invoked M-x diminish).
To restore all diminished modes to minor status, answer `all'.
The response to the prompt shouldn't be quoted. However, in Lisp code,
the arg must be quoted as a symbol, as in (diminish-undo 'all)."
(interactive
(if diminished-mode-alist
(list (read (completing-read
"Restore what diminished mode: "
(cons (list "all")
(mapcar (lambda (x) (list (symbol-name (car x))))
diminished-mode-alist))
nil t nil 'diminish-history-symbols)))
(error "No minor modes are currently diminished.")))
(if (eq mode 'all)
(cl-loop for dmode in diminished-mode-alist
for mode-name = (car dmode)
do (diminish-undo mode-name))
(let ((minor (assq mode minor-mode-alist))
(diminished (assq mode diminished-mode-alist)))
(or minor
(error "%S is not currently registered as a minor mode" mode))
(when diminished
(setq diminished-mode-alist (remove diminished diminished-mode-alist))
(setcdr minor (cdr diminished))))))
(defun diminish-setup (symbol newlist)
;; Replace symbols with one-element lists, so that each element of
;; NEWLIST is a valid arglist for `diminish'.
(setq newlist
(mapcar (lambda (x) (if (listp x) x (list x)))
newlist))
(set-default symbol newlist)
;; Un-diminish all modes
(diminish-undo 'all)
;; Diminish each mode the new list
(mapc (lambda (x)
(unless (listp x)
(setq x (list x)))
(when (assq (car x) minor-mode-alist)
(message "Diminishing %S" x)
(diminish (car x) (cdr x))))
newlist))
(defcustom diminished-minor-modes '()
"Minor modes to be diminished, and their diminished text, if any."
:group 'diminish
:type '(alist :key-type (symbol :tag "Mode")
:value-type (choice :tag "To What"
(const :tag "Hide completely" "")
(string :tag "Abbreviation")))
:set 'diminish-setup)
(defun diminish-init ()
(diminish-setup 'diminished-minor-modes diminished-minor-modes))
(eval-after-init
'(diminish-init)))
This is a series of elisp provided by the Debian package dpkg-dev-el. Some of them have uses outside Debian, so it’s nice to have a way to install them without dpkg.
https://packages.debian.org/sid/dpkg-dev-el https://salsa.debian.org/debian/emacs-goodies-el/tree/master/elisp/dpkg-dev-el
(req-package debian-changelog-mode
:straight
(debian-changelog-mode
:type git
:flavor melpa
:host nil
:repo "https://salsa.debian.org/emacsen-team/dpkg-dev-el.git"
:files ("debian-changelog-mode.el")))
(req-package debian-control-mode
:straight
(debian-control-mode
:type git
:flavor melpa
:host nil
:repo "https://salsa.debian.org/emacsen-team/dpkg-dev-el.git"
:files ("debian-control-mode.el")))
(req-package debian-copyright
:straight
(debian-copyright
:type git
:flavor melpa
:host nil
:repo "https://salsa.debian.org/emacsen-team/dpkg-dev-el.git"
:files ("debian-copyright.el")))
(req-package readme-debian
:straight
(readme-debian
:type git
:flavor melpa
:host nil
:repo "https://salsa.debian.org/emacsen-team/dpkg-dev-el.git"
:files ("readme-debian.el")))
This allows Emacs to support EditorConfig files. See http://editorconfig.org/
(req-package editorconfig
:config (editorconfig-mode 1))
(req-package ess
:require f
:init
;; Ensure that TRAMP is loaded before ESS, since loading ESS before
;; TRAMP causes problems
(require 'tramp)
:config
;; Open R profile file in R-mode
(add-to-list 'auto-mode-alist '("\\.Rprofile\\'" . R-mode))
;; (setq ess-default-style 'OWN)
(customize-set-variable
'ess-own-style-list
;; Based on (cdr (assoc 'C++ ess-style-alist))
'((ess-indent-offset . 4)
(ess-offset-arguments . open-delim)
(ess-offset-arguments-newline . prev-line)
(ess-offset-block . prev-line)
(ess-offset-continued . straight)
(ess-align-nested-calls "ifelse")
(ess-align-arguments-in-calls "function[ ]*(")
(ess-align-continuations-in-calls . t)
(ess-align-blocks control-flow)
(ess-indent-from-lhs arguments)
(ess-indent-from-chain-start . t)
(ess-indent-with-fancy-comments)))
;; Don't let ESS trigger TRAMP when entering minibuffer
(define-advice ess-r-package-auto-activate (:before-until () not-in-minibuffer)
"Don't run this function in a minibuffer"
(minibufferp))
(require 'f)
;; When an R file is in a directory named "scripts", suggest the
;; parent directory as the starting directory.
(defun my-ess-directory-function ()
(cond (ess-directory)
((string= "scripts" (f-filename (f-full default-directory)))
(f-parent default-directory))
(t nil)))
(setq ess-directory-function #'my-ess-directory-function))
(req-package esup
:defer t)
(req-package filelock)
;; (req-package flycheck
;; :require f
;; :config
;; ;; (or
;; ;; (and (stringp flycheck-cwl-schema-path)
;; ;; (f-exists-p flycheck-cwl-schema-path))
;; ;; (setq flycheck-cwl-schema-path
;; ;; (car (f-glob "~/.pyenv/versions/*/lib/python*/site-packages/cwltool/schemas/v1.0/CommonWorkflowLanguage.yml")))
;; ;; (display-warning 'init "Could not find a CWL schema. See https://qiita.com/tm_tn/items/6c9653847412d115bec0 for more info."))
;; )
This package puts change indicators in the buffer fringe to indicate what parts of the file have been added, deleted, or modified since the last Git commit.
(req-package git-gutter)
(req-package git-gutter-fringe)
Git-wip saves a hidden commit after each file is saved, thus saving a
full history of all your edits since the last real commit. We need a
special config to install the git-wip
command-line script along with
the Emacs Lisp file, and to tell Emacs where to find the script.
(req-package git-wip-mode
:straight
(git-wip-mode
:type git
:host github
:repo "bartman/git-wip"
:files ("emacs/git-wip-mode.el" "git-wip"))
:config
(setq git-wip-path
(f-join (f-dirname (locate-library "git-wip-mode"))
"git-wip")))
This automatically inserts a header into any new elisp file.
(req-package header2
:config
(progn
(define-advice make-header (:after (&rest args) add-lexbind-variable)
"Add `lexical-binding: t' to header."
(when (eq major-mode 'emacs-lisp-mode)
(save-excursion
(add-file-local-variable-prop-line 'lexical-binding t))))
(defsubst header-not-part-of-emacs ()
"Insert line declaring that this file is not part of Emacs."
(when (eq major-mode 'emacs-lisp-mode)
(insert header-prefix-string "This file is NOT part of GNU Emacs.\n")))
(defsubst header-completely-blank ()
"Insert an empty line to file header (not even `header-prefix-string')."
(insert "\n"))
(setq header-copyright-notice
(format-time-string "Copyright (C) %Y Ryan C. Thompson\n"))
;; Set up headers when creating an elisp file
(add-hook 'emacs-lisp-mode-hook #'auto-make-header)
;; Update headers on save
(add-hook 'write-file-hooks #'auto-update-file-header)
;; Override `header-eof' to not insert a separator line
(defun header-eof ()
"Insert comment indicating end of file."
(goto-char (point-max))
(insert "\n")
(insert comment-start
(concat (and (= 1 (length comment-start)) header-prefix-string)
(if (buffer-file-name)
(file-name-nondirectory (buffer-file-name))
(buffer-name))
" ends here"
(or (nonempty-comment-end) "\n"))))
;; Function to insert `provide' statement at end of file; This is
;; used in `make-header-hook'.
(defun header-provide-statement ()
"Insert `provide' statement."
(save-excursion
(goto-char (point-max))
(insert
(format "\n%s"
(pp-to-string `(provide ',(intern (f-base (buffer-file-name)))))))))
;; Prevent `auto-make-header' from setting the buffer modified flag
(define-advice auto-make-header
(:around (orig-fun &rest args) dont-set-buffer-modified)
"Don't set the buffer modified flag."
(preserve-buffer-modified-p
(apply orig-fun args)))))
(req-package highlight-function-calls
:init
(define-activator highlight-function-calls-mode)
:hook ((lisp-interaction-mode emacs-lisp-mode) . turn-on-highlight-function-calls-mode))
(req-package highlight-stages)
This is needed for org-html-fontify-code
.
(req-package htmlize)
(req-package ido-complete-space-or-hyphen
:straight
(ido-complete-space-or-hyphen
:type git
:flavor melpa
:host github
:repo "DarwinAwardWinner/ido-complete-space-or-hyphen"))
This uses my bleeding-edge branch of ido-completing-read+.
(req-package ido-completing-read+
:straight
(ido-completing-read+
:type git
:flavor melpa
:files ("ido-completing-read+.el" "ido-completing-read+-pkg.el")
:host github
:repo "DarwinAwardWinner/ido-completing-read-plus"))
(req-package ido-yes-or-no)
We only load this when image.el
is loaded.
(req-package image+
:after image)
This package provides shortcuts to quickly open the user’s init file for editing.
(req-package iqa
:config
(iqa-setup-default)
(setq iqa-user-init-file (f-join user-emacs-directory "config.org")))
(req-package json-mode)
This indicates in the mode-line for each Emacs Lisp file whether lexical binding is enabled for that file.
(req-package lexbind-mode
:init (add-hook 'emacs-lisp-mode-hook (apply-partially #'lexbind-mode 1)))
This package allows Emacs to emulate the Mac OS behavior of staying open after the last window is closed, by creating a new window and hiding it until Emacs is reactivated.
;; (req-package mac-pseudo-daemon
;; :straight
;; (mac-pseudo-daemon
;; :type git
;; :host github
;; :repo "DarwinAwardWinner/mac-pseudo-daemon"
;; :branch "rewrite"
;; :files ("mac-pseudo-daemon.el")))
(req-package pseudo-daemon
:straight
(pseudo-daemon
:type git
:host github
:repo "DarwinAwardWinner/mac-pseudo-daemon"
:branch "rewrite"
:files ("pseudo-daemon.el")))
This sets up magit, the Emacs Git interface.
The defvar suppresses Magit upgrade instructions. The magit-init
advice causes Magit to display the status buffer for an new repository
immediately after a git init
(but only when called interactively).
(req-package magit
;; We override the recipe to choose the main branch
:straight
(magit
:type git
:flavor melpa
:files ("lisp/magit*.el" "lisp/git-rebase.el" "docs/magit.texi"
"docs/AUTHORS.md" "LICENSE" "Documentation/magit.texi"
"Documentation/AUTHORS.md"
(:exclude "lisp/magit-libgit.el" "lisp/magit-libgit-pkg.el"
"lisp/magit-section.el" "lisp/magit-section-pkg.el")
"magit-pkg.el")
:host github
:repo "magit/magit"
:branch "main")
:bind (("C-c g" . magit-status))
:init
;; This needs to be set or else magit will warn about things.
(defvar magit-last-seen-setup-instructions "1.4.0")
:config
(define-advice magit-init (:after (&rest args) show-status)
"Show the status buffer after initialization if interactive."
(when (called-interactively-p 'interactive)
(magit-status-setup-buffer (car args)))))
;; Magit is apparently missing an autoload for `magit-process-file',
;; which is called by a function in `post-command-hook', resulting in
;; an unusable emacs unless the autoload is added manually.
(req-package magit-process
;; This isn't a separate package, so don't try to install it
:straight nil
:require magit
:commands (magit-process-file))
This package allows magit to refresh the status buffer whenever a file is modified. This mode causes problems on remote (TRAMP) files, so we only enable it for local files.
(req-package magit-filenotify
:require magit
:init
(defun turn-on-magit-filenotify-mode-if-local ()
(magit-filenotify-mode
(if (file-remote-p default-directory)
0
1)))
(add-hook 'magit-status-mode-hook
'turn-on-magit-filenotify-mode-if-local))
This mode is for editing Markdown files.
(req-package markdown-mode
:mode ("\\.\\(md\\|mkdn\\)$" . markdown-mode))
;; Needed for editing code blocks in a separate buffer
(req-package edit-indirect)
This is an optional dependency of markdown-mode.
(req-package edit-indirect
:defer t)
Ring the bell by flashing the mode line of the active window.
(req-package mode-line-bell)
Noflet provides an enhanced version of flet
, and more importantly,
provides proper indentation support for flet-like macros.
(req-package noflet)
This package allows the user to dynamically change the number of
context lines around matches in an occur-mode
buffer using the plus
and minus keys (and 0 key to reset)
(req-package occur-context-resize
:init (add-hook 'occur-mode-hook 'occur-context-resize-mode))
The default implementation of the org-in-src-block-p
function is
broken and always returns nil, so we reimplement it correctly here. We
also add a function to insert a new src block into an org-mode buffer.
Note that we put :straight nil
here, since this code is inside an
org file, so org-mode already installed and loaded by the time this
code runs.
Org-mode supports asynchronous export functionality. By default, each
async process loads the user’s full init file, but we don’t want that,
because that would be too slow. So instead, whenever we run an async
Org export job, we generate a minimal init file that just sets the
load-path
from this Emacs, disables backup & lock files, and then
loads the appropriate Org-mode functionality. Then we tell the export
process to use this file instead or the usual init file.
TODO: Advise org-store-link
to prompt for a CUSTOM_ID property when
used on a headline without one.
(req-package org
:commands org-clocking-buffer
:config
;; Custom src-block behaviors
(progn
(defun org-insert-src-block (src-code-type)
"Insert a `SRC-CODE-TYPE' type source code block in org-mode."
(interactive
(let ((src-code-types
'("emacs-lisp" "python" "C" "sh" "java" "js" "clojure" "C++" "css"
"calc" "asymptote" "dot" "gnuplot" "ledger" "lilypond" "mscgen"
"octave" "oz" "plantuml" "R" "sass" "screen" "sql" "awk" "ditaa"
"haskell" "latex" "lisp" "matlab" "ocaml" "org" "perl" "ruby"
"scheme" "sqlite")))
(list (ido-completing-read "Source code type: " src-code-types))))
(progn
(newline-and-indent)
(insert (format "#+BEGIN_SRC %s\n" src-code-type))
(newline-and-indent)
(insert "#+END_SRC\n")
(forward-line -2)
(org-edit-src-code)))
(defun org-insert-or-edit-src-block (src-code-type &optional interactive-call)
"Insert a source code block in org-mode or edit an existing one."
(interactive (list nil t))
(if (and (org-in-src-block-p)
;; Matching the logic in `org-edit-src-code'
(org-src--on-datum-p (org-element-at-point)))
(org-edit-src-code)
(if interactive-call
(call-interactively 'org-insert-src-block)
(org-insert-src-block src-code-type)))))
;; Allow an emphasized expression to extend over 15 lines
(progn
(setcar (nthcdr 2 org-emphasis-regexp-components) " \t\r\n\"'")
(setcar (nthcdr 4 org-emphasis-regexp-components) 15)
(org-set-emph-re 'org-emphasis-regexp-components org-emphasis-regexp-components))
;; Enable org links that roll dice
(progn
(defvar roll-dice-command "roll")
(defun roll-dice (&rest args)
(interactive "sRoll dice: ")
(let ((result
(s-trim
(shell-command-to-string
(mapconcat #'shell-quote-argument (cons roll-dice-command args) " ")))))
(when (called-interactively-p)
(message result))
result))
(defun org-rolldice-open (path)
(let ((spec (read-string "Roll dice: " path)))
(message (roll-dice spec))))
(org-link-set-parameters
"roll"
:follow #'org-rolldice-open
;; This must be a lambda so it is self-contained
:export (lambda (link desc format) (or desc link))))
;; Use a minimal init file for org exporting
(defun setup-org-export-async-init-file ()
(interactive)
(require 'gnus)
(setq org-export-async-init-file (f-join user-emacs-directory ".temp-org-export-async-init.el"))
(let ((init-comment
"This auto-generated minimal init file is used for quick initialization of important variables in org-mode async exports, without needing to load the full config.")
(init-form
`(progn
;; Set some important variables
,@(cl-loop
for binding in
'((gc-cons-percentage 0.6) ; Disable GC for batch script
load-path
(create-lockfiles nil)
(make-backup-files nil)
org-emphasis-regexp-components)
if (symbolp binding)
collect (list 'setq binding (list 'quote (symbol-value binding)))
else
collect (cons 'setq binding))
;; Bring all customized org variables along
,@(cl-loop
for sym being the symbols
for symname = (symbol-name sym)
if (and (eq sym (indirect-variable sym))
(custom-variable-p sym)
(s-matches-p "^org\\(?:tbl\\)?-" (symbol-name sym))
(not (eq 'standard (custom-variable-state
sym (symbol-value sym)))))
collect (list 'setq sym (list 'quote (symbol-value sym))))
;; Always going to need these
(require 'org)
(require 'org-compat)
;; This one defines a function for some reason
(require 'org-macs)
;; This seems to be needed for some reason
(defalias 'org-file-name-concat #'file-name-concat)
(require 'ox)
;; (Try to) Load any org packages currently loaded in
;; the parent
,@(cl-loop
for feat in features
if (s-matches-p "^org\\(?:tbl\\)?-" (symbol-name feat))
collect (list 'require (list 'quote feat) nil t))
;; Try to copy any org-related autoloads from the parent
,@(cl-loop
for sym being the symbols
for symname = (symbol-name sym)
for symfun = (symbol-function sym)
if (and (autoloadp symfun)
(s-matches-p "^org\\(?:tbl\\)?-" (symbol-name sym)))
collect (list 'fset (list 'quote sym) (list 'quote symfun)))
;; We have to call this function after modifying the
;; variable (which should happen above)
(org-set-emph-re 'org-emphasis-regexp-components org-emphasis-regexp-components)
)))
(with-temp-file org-export-async-init-file
(emacs-lisp-mode)
(insert ";; " init-comment)
(fill-paragraph)
(insert "\n\n" (gnus-pp-to-string init-form) "\n")
(add-file-local-variable 'eval '(auto-revert-mode 1)))))
(eval-after-init
'(setup-org-export-async-init-file))
;; Fix an error in Emacs 28
(define-advice org-eldoc-documentation-function (:filter-args (&rest _ignored) ignore-args)
"Make the function accept and ignore any arguments."
nil)
:bind (("C-c l" . org-store-link)
("C-c a" . org-agenda)
:map org-mode-map
("C-c C-'" . org-insert-or-edit-src-block)
:map org-src-mode-map
("C-c C-'" . org-edit-src-exit)
("C-c C-c" . org-edit-src-exit)))
;; Need this loaded or Eldoc throws errors in org buffers. TODO: Could
;; probably be replaced with an autoload for `org-get-outline-path'
(autoload 'org-get-outline-path "org-refile")
This is now separated out from the main repo
(req-package org-contrib
:require org
:straight
(org-contrib
:type git
:flavor melpa
:host github
:repo "emacsmirror/org-contrib")
:defer t)
(req-package ox-extra
;; No install, it's just to configure one of the packages in
;; org-contrib
:require org-contrib
:straight nil
:config
(ox-extras-activate '(latex-header-blocks ignore-headlines)))
This code makes the anchor IDs generated when exporting org files to HTML less random. It sets the seed to a specific value before executing the export, which means that it should always generate the same anchors IDs given the same set of headlines.
(req-package ox-html
:require org
:straight nil
:config
(defmacro with-reproducible-rng (seed &rest body)
"Execute BODY with reproducible RNG.
Before executing BODY, the random number generator will be
initialized with SEED, which should be a string (see `random').
Hence, the sequence of random numbers returned by `random' within
BODY will be reproducible. After BODY finishes, the random number
generatore will be reinitialized from system entropy, and will
therefore no longer be predictable.
\(There does not seem to be a way to save and restore a specific
RNG state, so the RNG state after executing this macro will not
be the same as it was prior.)"
(declare (indent 1))
`(unwind-protect
(progn
(random (or ,seed ""))
,@body)
(random t)))
(define-advice org-html-export-to-html (:around (orig-fun &rest args) reproducible-rng)
"Use a reproducible RNG stream for HTML export.
This results in the same pseudo-random anchor IDs for
the same set of headlines every time."
(with-reproducible-rng "org-html-export"
(apply orig-fun args))))
;; (req-package org-superstar
;; :require org
;; :init (add-hook 'org-mode-hook (lambda () (org-superstar-mode 1))))
(req-package org-sidebar
:require org
:bind (:map org-mode-map
("C-c t" . org-sidebar-tree-toggle)))
This doesn’t seem to work properly in Emacs 28.
;; (req-package org-sticky-header
;; :require org
;; :init
;; (define-activator org-sticky-header-mode)
;; :hook (org-mode . turn-on-org-sticky-header-mode))
;; (req-package org-table-sticky-header
;; :require org
;; :init
;; (define-activator org-table-sticky-header-mode)
;; :hook (org-mode . turn-on-org-table-sticky-header-mode))
(req-package org-appear
:require org
:init
(define-activator org-appear-mode)
;; Disabled by default for now because it's quite glitchy.
;; :hook (org-mode . turn-on-org-appear-mode)
:config
;; Don't allow this function to throw errors, because that's a no-no
;; in `post-command-hook'.
(define-advice org-appear--post-cmd (:around (orig-fun &rest args) demote-errors)
(with-demoted-errors "Error during `org-appear--post-cmd': %S"
(apply orig-fun args)))
;; Hide things that org-appear has made visible before running
;; certain functions that rely on those things being hidden.
(defun org-appear--hide-current-elem ()
(ignore-errors
(when org-appear-mode
(let ((current-elem (org-appear--current-elem)))
(when current-elem
(org-appear--hide-invisible current-elem))))))
(define-advice org-appear-mode (:after (&rest args) set-pre-command-hook)
(if org-appear-mode
(add-hook 'pre-command-hook #'org-appear--hide-current-elem nil t)
(remove-hook 'pre-command-hook #'org-appear--hide-current-elem t)))
;; (defmacro org-appear-hide-before (fun)
;; `(define-advice ,fun (:before (&rest args) org-appear-hide-before)
;; "Fill consistently regardless of org-appear element status."
;; (org-appear--hide-current-elem)))
;; (org-appear-hide-before org-fill-paragraph)
;; (org-appear-hide-before org-table-align)
;; (org-appear-hide-before org-ctrl-c-ctrl-c)
;; (org-appear-hide-before org-table-justify-field-maybe)
)
;; Error during ‘org-appear--post-cmd’: (user-error "Cannot move further up") [2 times]
(when (version<= "27.1" emacs-version)
(req-package org-special-block-extras))
(req-package org-modern)
This is used to check packages for package.el compliance.
(req-package package-lint)
Paradox provides an improved interface to package.el.
(req-package paradox)
This mode allows editing files with multiple major modes, such as Rmarkdown files, where some parts of the file are Markdown and others are R code.
(req-package polymode)
(req-package poly-R
:mode ("\\.Rmd\\'" . poly-markdown+r-mode))
This package allows replacing certain words with symbols, for example replacing “lambda” with λ in Lisp code. The replacement is purely visual, and the files are saved with the original words.
(req-package pretty-symbols
:config
(progn
(defun pretty-symbols-enable-if-available ()
"Enable pretty-symbols in buffer if applicable.
If current buffer's `major-mode' has any pretty symbol
substitution rules associated with it, then enable
`pretty-symbols-mode', otherwise do nothing."
(when (apply #'derived-mode-p
(delete-dups
(cl-mapcan (lambda (x) (cl-copy-list (nth 3 x)))
pretty-symbol-patterns)))
(pretty-symbols-mode 1)))
(add-hook 'after-change-major-mode-hook #'pretty-symbols-enable-if-available)))
“.pyi” is the file extension for the Python typeshed’s type
annotations. These files are valid (but incomplete) Python syntax, so
regular python-mode
is just fine.
(req-package python
:mode ("\\.pyi" . python-mode))
(req-package rainbow-delimiters
:init
(add-hook 'prog-mode-hook #'rainbow-delimiters-mode-enable))
(req-package reveal-in-osx-finder)
(req-package selectrum)
(req-package selectrum-prescient)
(req-package shrink-whitespace
:commands shrink-whitespace)
(req-package slime)
(req-package smooth-scrolling
:straight
(smooth-scrolling
:type git
:flavor melpa
:host github
:repo "aspiers/smooth-scrolling"
:fork (:host github
:repo "DarwinAwardWinner/smooth-scrolling")))
(req-package snakemake-mode)
(req-package system-specific-settings)
(req-package tempbuf
:config
(defun mode-symbol (sym)
"Append \"-mode\" to SYM unless it already ends in it."
(let ((symname (symbol-name sym)))
(intern
(concat symname
(unless (s-suffix? "-mode" symname)
"-mode")))))
(defun tempbuf-protect ()
"Prevent tempbuf from killing visible or unsaved buffers."
(when (or (get-buffer-window)
(buffer-modified-p))
(throw 'tempbuf-skip-kill nil)))
(add-hook 'tempbuf-kill-hook 'tempbuf-protect)
(defun tempbuf-major-mode-hook ()
"Turn on `tempbuf-mode' in current buffer if buffer's `major-mode' is in `tempbuf-temporary-major-modes'.
Else turn off `tempbuf-mode'."
(if (apply #'derived-mode-p tempbuf-temporary-major-modes)
(turn-on-tempbuf-mode)
(turn-off-tempbuf-mode)))
(defun tempbuf-setup-temporary-major-modes (symbol newval)
(set-default symbol (mapcar 'mode-symbol newval))
;; Set tempbuf-mode correctly in existing buffers.
(mapc (lambda (buf)
(with-current-buffer buf
(tempbuf-major-mode-hook)))
(buffer-list)))
(defcustom tempbuf-temporary-major-modes nil
"Major modes in which `tempbuf-mode' should be activated.
This will cause buffers of these modes to be automatically killed
if they are inactive for a short while."
:group 'tempbuf
:set 'tempbuf-setup-temporary-major-modes
:type '(repeat (symbol :tag "Mode")))
(add-hook 'after-change-major-mode-hook 'tempbuf-major-mode-hook)
;; This mode requires special handling because it somehow avoids
;; using `after-change-major-mode-hook', I think.
(eval-after-load 'ess-custom
'(add-hook 'ess-help-mode-hook 'tempbuf-major-mode-hook)))
(req-package undo-tree
:config
(require 'epa-hook)
(define-advice undo-tree-save-history-from-hook
(:around (orig-fun &rest args) no-save-gpg-hist)
"Don't save history to disk for gpg files"
(unless (and (stringp buffer-file-name)
(s-matches-p epa-file-name-regexp (buffer-file-name)))
(apply orig-fun args))))
(req-package volatile-highlights
:config
(put 'vhl/define-extension 'lisp-indent-function 1)
(vhl/define-extension 'adjust-parens
'lisp-indent-adjust-parens
'lisp-dedent-adjust-parens)
(vhl/install-extension 'adjust-parens)
(vhl/define-extension 'undo-tree
'undo-tree-yank 'undo-tree-move)
(vhl/install-extension 'undo-tree)
;; Clear volatile highlights after 1 second
(setq vhl/idle-clear-timer
(run-with-idle-timer 1 t #'vhl/clear-all)))
(define-advice vhl/add-range (:before-while (&rest args) disable-in-read-only-buffers)
"Don't do volatile highlights in read-only buffers"
(not buffer-read-only))
(req-package which-key
:defer t)
(req-package with-simulated-input)
(req-package ws-butler)
(req-package yaml-mode)
;; (req-package elsa)
;; (req-package flycheck-elsa
;; :require flycheck elsa)
Forge seems to be broken right now
;; Straight fails to auto-detect these dependencies, so we have to
;; declare them here?
(req-package emacsql-sqlite)
(req-package closql
:require emacsql-sqlite)
(req-package ghub)
(req-package forge
:require closql ghub)
;; I'm unsure how this fits into the dependency hierarchy, but I think
;; it's fine because it's only a dependency at runtime, so load order
;; doesn't matter.
(req-package sqlite3)
;; (req-package crosshairs)
These are some packages in package-selected-packages
as of the last
time I used that. I need to go through and figure out which of these
are incidental and which I actually wanted but forgot to configure.
- ace-window
- adaptive-wrap
- aggressive-indent
- apples-mode
- avy
- better-shell
- commander
- counsel
- editorconfig
- ess-smart-underscore
- feature-mode
- filelock
- fireplace
- github-browse-file
- github-clone
- gitignore-mode
- guide-key
- hardhat
- haskell-mode
- help-fns+
- highlight-escape-sequences
- ht
- hydra
- ido-load-library
- isearch+
- jedi
- julia-mode
- keydef
- lexbind-mode
- log4e
- lv
- macrostep
- magit-find-file
- md-readme
- multiple-cursors
- nameless
- pcre2el
- py-isort
- pyenv-mode
- restart-emacs
- scratch-ext
- shrink-whitespace
- slime
- string-edit
- swiper
- sx
- systemd
- toc-org
- transmission
- transpose-frame
- treepy
- trinary
- ztree
The req-package
forms above only declare the set of packages to be
installed and loaded. They don’t actually do anything until the line
of code below is run. At this time, req-package
resolves any
dependencies between packages and then installs and loads them in the
correct order to satisfy those dependencies.
We use without-user-input
here mainly because sometimes packages (e.g.
sqlite3) want to ask the user questions during install, but they will
avoid doing so if Emacs says it’s noninteractive.
(without-user-input
(req-package-finish))
This is the file where everything set via M-x customize
goes.
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(if (f-exists? custom-file)
(load custom-file)
(display-warning 'init "Could not load custom file"))
This section contains a set of tweaks to Emacs behavior that are not specific to a single package and cannot be accomplished by customizing variables.
(Currently none)
On OS X (and probably other platforms), “ls” may not refer to GNU ls. If GNU ls is installed on these platforms, it is typically installed under the name “gls” instead. So if “gls” is available, we prefer to use it.
(if (executable-find "gls")
(setq insert-directory-program "gls"))
This calls report-emacs-bug
, then report-emacs-insert-to-mailer
,
then cleans up the bug buffers.
The backquoting interpolation is used to copy the interactive form
from report-emacs-bug
.
(eval
`(defun report-emacs-bug-via-mailer (&rest args)
"Report a bug in GNU Emacs.
Prompts for bug subject. Opens external mailer."
,(interactive-form 'report-emacs-bug)
(save-window-excursion
(apply 'report-emacs-bug args)
(report-emacs-bug-insert-to-mailer)
(mapc (lambda (buf)
(with-current-buffer buf
(let ((buffer-file-name nil))
(kill-buffer (current-buffer)))))
(list "*Bug Help*" (current-buffer))))))
This is where I keep Emacs, but you’ll probably need to edit this if you want look at the definitions of Emacs primitive functions.
(setq find-function-C-source-directory "~/src/emacs/src")
I map Option -> Super and Command -> Meta in Emacs on Mac OS, which is the opposite of what it is by default, because I need Emacs’ meta key to be directly below X. However, there are a handful of shortcuts involving Command/Option that I don’t want swapped, so I need to swap their Super/Meta bindings to cancel out the swapping of Super and Meta themselves.
;; Use `eval-after-load' to ensure that this always happens after
;; loading custom.el, since that sets the Command/Option modifiers.
(eval-after-init
;; Only swap on Mac OS
'(when (or (featurep 'ns)
(eq system-type 'darwin))
;; Only swap bindings if keys were actually swapped
(when (and (eq ns-command-modifier 'meta)
(eq ns-option-modifier 'super))
;; Super is the Alt/option key
(bind-key "s-<left>" 'left-word)
(bind-key "s-<right>" 'right-word)
(bind-key "s-<backspace>" 'backward-kill-word)
(bind-key "s-<kp-delete>" 'kill-word)
(bind-key "s-`" 'tmm-menubar)
;; Meta is the command key
(bind-key "M-<left>" 'move-beginning-of-line)
(bind-key "M-<right>" 'move-end-of-line)
(bind-key "M-<backspace>" 'ignore)
(bind-key "M-<kp-delete>" 'ignore)
(bind-key "M-`" 'other-frame)
(require 'cl)
;; Need to fix `org-meta(left|right)' as well. TODO: switch to
;; noflet after this is merged:
;; https://github.com/nicferrier/emacs-noflet/pull/17
(define-advice org-metaleft (:around (orig-fun &rest args) osx-command)
(flet ((backward-word (&rest args)))
(defun backward-word (&rest args)
(interactive)
(call-interactively #'move-beginning-of-line))
(apply orig-fun args)))
(define-advice org-metaright (:around (orig-fun &rest args) osx-command)
(flet ((forward-word (&rest args)))
(defun forward-word (&rest args)
(interactive)
(call-interactively #'move-end-of-line))
(apply orig-fun args))))))
Mac OS binds Home and End to go to start/end of buffer by default. We want the windows-like behavior, where Home/End go to start/end of line, and Control+Home/End go to start/end of buffer.
(bind-key "<home>" 'move-beginning-of-line)
(bind-key "<end>" 'move-end-of-line)
(define-advice indent-region
(:around (orig-fun &rest args) indent-defun)
"Indent containing defun if mark is not active."
(if (and transient-mark-mode
(not mark-active))
(save-excursion
(mark-defun)
(call-interactively #'indent-region))
(apply orig-fun args)))
(bind-key "RET" #'newline-and-indent)
electric-indent-mode
has a bad interaction with markdown-mode
, so
we disable it in markdown buffers only.
(add-hook 'markdown-mode-hook
(apply-partially #'electric-indent-local-mode 0))
(cl-loop
for hook in
'(lisp-interaction-mode-hook emacs-lisp-mode-hook)
do (add-hook hook #'eldoc-mode))
This allows TRAMP to use sudo on remote hosts.
(require 'tramp)
(add-to-list 'tramp-default-proxies-alist
'(nil "\\`root\\'" "/ssh:%h:"))
(add-to-list 'tramp-default-proxies-alist
(list (regexp-quote (system-name)) nil nil))
See this page for infromation on why this is needed for security.
(defvar tramp-backup-forbidden-methods
'("su" "sudo")
"TRAMP methods for which backup files should be forbidden")
(defun backup-enable-predicate-custom (name)
"See `tramp-backup-forbidden-methods'"
(and (normal-backup-enable-predicate name)
(not
(let ((method (file-remote-p name 'method)))
(when (stringp method)
(member method tramp-backup-forbidden-methods))))))
(setq backup-enable-predicate #'backup-enable-predicate-custom)
This ensures that backup files for TRAMP remote files are created on the local machine. This is safe because backup files for su/sudo files are disabled entirely above.
(setq tramp-backup-directory-alist nil)
(add-to-list 'auto-mode-alist '("\\.gitignore\\'" . conf-mode))
(defmacro without-messages (&rest body)
"Evaluate BODY but ignore all messages.
This temporarily binds the `message' function to `ignore' while
executing BODY."
(declare (indent 0))
`(noflet ((message (&rest ignore) nil))
,@body))
The following additions ensure that the saved desktop file is always up-to-date.
When running in hooks, it’s not disastrous if we can’t save the desktop for some reason, and we don’t want to bother the user, so we wrap the normal saving function to force it to do nothing instead of asking for user input.
(defun desktop-autosave-in-desktop-dir ()
"Like `desktop-save-in-desktop-dir' but aborts if input is required.
If `desktop-save-in-desktop-dir' tries to solicit user input,
this aborts and returns nil instead. Also, it disables all
messages during desktop saving. This is intended for use in place
of `desktop-save-in-desktop-dir' in hooks where you don't want to
bother the user if something weird happens."
(interactive)
(without-user-input
(without-messages
(desktop-save-in-desktop-dir))))
(add-hook 'auto-save-hook 'desktop-autosave-in-desktop-dir)
This will ensure that all open files are saved in the desktop. An idle timer and tripwire variable are used used to avoid saving the desktop multiple times when multiple files are opened or closed in rapid succession.
(defvar desktop-mode-desktop-is-stale nil
"This is set to non-nil when a file is opened or closed.")
(defun desktop-mode-set-stale ()
"If current buffer has a file, set the stale desktop flag."
(when buffer-file-name
(setq desktop-mode-desktop-is-stale t)))
(defun desktop-mode-set-current ()
"Unconditionally clear the stale desktop flag."
(setq desktop-mode-desktop-is-stale nil))
(add-hook 'kill-buffer-hook #'desktop-mode-set-stale)
(add-hook 'find-file-hook #'desktop-mode-set-stale)
(add-hook 'desktop-after-read-hook #'desktop-mode-set-current)
(defun desktop-mode-save-if-stale ()
(when desktop-mode-desktop-is-stale
(desktop-autosave-in-desktop-dir)
(desktop-mode-set-current)))
;; Desktop will be saved 0.1 seconds after any file is opened or
;; closed.
(run-with-idle-timer 0.1 t #'desktop-mode-save-if-stale)
The desktop-owner
function should only ever return the PID of an
Emacs process that’s currently running. This advice replaces the PID
of a dead or non-Emacs process with nil, thus allowing the current
Emacs to pry the desktop file from the cold dead hands of the previous
one without asking permisssion.
(defun pid-command-line (pid)
"Return the command line for process with the specified PID.
If PID is not a currently running process, returns nil."
(ignore-errors
(car (process-lines "ps" "-p" (format "%s" pid) "-o" "args="))))
(define-advice desktop-owner (:filter-return (retval) pry-from-cold-dead-hands)
"Only return the PID of an Emacs process or nil.
If the return value is not the PID of a currently running Emacs
owned by the current user, it is replaced with nil on the
assumption that the previous owner died an untimely death, so
that the current emacs can cleanly claim its inheritence."
(ignore-errors
(let ((owner-cmd (pid-command-line retval)))
(unless (and owner-cmd
(string-match-p
"emacs"
(downcase (file-name-base owner-cmd))))
(setq retval nil))))
retval)
If desktop-save
needs to ask a question and Emacs is idle for a long
time (multiple auto-save intervals), it is possible to get multiple
nested calls to save the desktop. This is obviously undesirable. The
below code turns any recursive call to desktop-save
with the same
DIRNAME
into a no-op.
(defvar desktop-save-recursion-guard-dirname nil)
(define-advice desktop-save (:around (orig-fun dirname &rest args) prevent-recursion)
"Prevent recursive calls to `desktop-save'.
Recursive calls will only be prevented when they have the same
DIRNAME."
(if (string= dirname desktop-save-recursion-guard-dirname)
(message "Preventing recursive call to `desktop-save' for %S" dirname)
(let ((desktop-save-recursion-guard-dirname dirname))
(apply orig-fun dirname args))))
By default, enable-command
and disable-command
append their
declarations to user-init-file
. But I want them appended to custom.el
instead.
(define-advice en/disable-command (:around (orig-fun &rest args) put-in-custom-file)
"Put declarations in `custom-file'."
(let ((user-init-file custom-file))
(apply orig-fun args)))
My settings put all backup files in one directory (see
backup-directory-alist
). So when diff
prompts for the second file,
it starts in that backup directory. I would rather have it start in
the same directory as the first file.
;; (define-advice diff (:before (&rest args) same-dir-for-both-files)
;; "Only prompt with backup file in same directory.
;; When called interactively, `diff' normally offers to compare
;; against the latest backup file of the selected file. But this
;; isn't great if that backup file is in a dedicated backup
;; directory far away from the original directory. So this advice
;; only allows it to offer backup files from the same directory.
;; This advice doesn't actually modify the function's behavior in
;; any way. It simply overrides the interactive form."
;; (interactive
;; (let* ((newf (if (and buffer-file-name (file-exists-p buffer-file-name))
;; (read-file-name
;; (concat "Diff new file (default "
;; (file-name-nondirectory buffer-file-name) "): ")
;; nil buffer-file-name t)
;; (read-file-name "Diff new file: " nil nil t)))
;; (oldf (file-newest-backup newf)))
;; (setq oldf (if (and oldf (file-exists-p oldf)
;; (f-same? (f-dirname newf) (f-dirname oldf)))
;; (read-file-name
;; (concat "Diff original file (default "
;; (file-name-nondirectory oldf) "): ")
;; (file-name-directory oldf) oldf t)
;; (read-file-name "Diff original file: "
;; (file-name-directory newf) nil t)))
;; (list oldf newf (diff-switches)))))
(ignore-errors (load "~/.emacs.d/ido.el"))
This is actually an ido bug: see:
For some reason the x-colors
variable has started to get the wrong
value, so I’ve copied the code to set it correctly out of
common-win.el.
(setq x-colors
(if (featurep 'ns) (funcall #'ns-list-colors)
(purecopy
'("gray100" "grey100" "gray99" "grey99" "gray98" "grey98" "gray97"
"grey97" "gray96" "grey96" "gray95" "grey95" "gray94" "grey94"
"gray93" "grey93" "gray92" "grey92" "gray91" "grey91" "gray90"
"grey90" "gray89" "grey89" "gray88" "grey88" "gray87" "grey87"
"gray86" "grey86" "gray85" "grey85" "gray84" "grey84" "gray83"
"grey83" "gray82" "grey82" "gray81" "grey81" "gray80" "grey80"
"gray79" "grey79" "gray78" "grey78" "gray77" "grey77" "gray76"
"grey76" "gray75" "grey75" "gray74" "grey74" "gray73" "grey73"
"gray72" "grey72" "gray71" "grey71" "gray70" "grey70" "gray69"
"grey69" "gray68" "grey68" "gray67" "grey67" "gray66" "grey66"
"gray65" "grey65" "gray64" "grey64" "gray63" "grey63" "gray62"
"grey62" "gray61" "grey61" "gray60" "grey60" "gray59" "grey59"
"gray58" "grey58" "gray57" "grey57" "gray56" "grey56" "gray55"
"grey55" "gray54" "grey54" "gray53" "grey53" "gray52" "grey52"
"gray51" "grey51" "gray50" "grey50" "gray49" "grey49" "gray48"
"grey48" "gray47" "grey47" "gray46" "grey46" "gray45" "grey45"
"gray44" "grey44" "gray43" "grey43" "gray42" "grey42" "gray41"
"grey41" "gray40" "grey40" "gray39" "grey39" "gray38" "grey38"
"gray37" "grey37" "gray36" "grey36" "gray35" "grey35" "gray34"
"grey34" "gray33" "grey33" "gray32" "grey32" "gray31" "grey31"
"gray30" "grey30" "gray29" "grey29" "gray28" "grey28" "gray27"
"grey27" "gray26" "grey26" "gray25" "grey25" "gray24" "grey24"
"gray23" "grey23" "gray22" "grey22" "gray21" "grey21" "gray20"
"grey20" "gray19" "grey19" "gray18" "grey18" "gray17" "grey17"
"gray16" "grey16" "gray15" "grey15" "gray14" "grey14" "gray13"
"grey13" "gray12" "grey12" "gray11" "grey11" "gray10" "grey10"
"gray9" "grey9" "gray8" "grey8" "gray7" "grey7" "gray6" "grey6"
"gray5" "grey5" "gray4" "grey4" "gray3" "grey3" "gray2" "grey2"
"gray1" "grey1" "gray0" "grey0"
"LightPink1" "LightPink2" "LightPink3" "LightPink4"
"pink1" "pink2" "pink3" "pink4"
"PaleVioletRed1" "PaleVioletRed2" "PaleVioletRed3" "PaleVioletRed4"
"LavenderBlush1" "LavenderBlush2" "LavenderBlush3" "LavenderBlush4"
"VioletRed1" "VioletRed2" "VioletRed3" "VioletRed4"
"HotPink1" "HotPink2" "HotPink3" "HotPink4"
"DeepPink1" "DeepPink2" "DeepPink3" "DeepPink4"
"maroon1" "maroon2" "maroon3" "maroon4"
"orchid1" "orchid2" "orchid3" "orchid4"
"plum1" "plum2" "plum3" "plum4"
"thistle1" "thistle2" "thistle3" "thistle4"
"MediumOrchid1" "MediumOrchid2" "MediumOrchid3" "MediumOrchid4"
"DarkOrchid1" "DarkOrchid2" "DarkOrchid3" "DarkOrchid4"
"purple1" "purple2" "purple3" "purple4"
"MediumPurple1" "MediumPurple2" "MediumPurple3" "MediumPurple4"
"SlateBlue1" "SlateBlue2" "SlateBlue3" "SlateBlue4"
"RoyalBlue1" "RoyalBlue2" "RoyalBlue3" "RoyalBlue4"
"LightSteelBlue1" "LightSteelBlue2" "LightSteelBlue3" "LightSteelBlue4"
"SlateGray1" "SlateGray2" "SlateGray3" "SlateGray4"
"DodgerBlue1" "DodgerBlue2" "DodgerBlue3" "DodgerBlue4"
"SteelBlue1" "SteelBlue2" "SteelBlue3" "SteelBlue4"
"SkyBlue1" "SkyBlue2" "SkyBlue3" "SkyBlue4"
"LightSkyBlue1" "LightSkyBlue2" "LightSkyBlue3" "LightSkyBlue4"
"LightBlue1" "LightBlue2" "LightBlue3" "LightBlue4"
"CadetBlue1" "CadetBlue2" "CadetBlue3" "CadetBlue4"
"azure1" "azure2" "azure3" "azure4"
"LightCyan1" "LightCyan2" "LightCyan3" "LightCyan4"
"PaleTurquoise1" "PaleTurquoise2" "PaleTurquoise3" "PaleTurquoise4"
"DarkSlateGray1" "DarkSlateGray2" "DarkSlateGray3" "DarkSlateGray4"
"aquamarine1" "aquamarine2" "aquamarine3" "aquamarine4"
"SeaGreen1" "SeaGreen2" "SeaGreen3" "SeaGreen4"
"honeydew1" "honeydew2" "honeydew3" "honeydew4"
"DarkSeaGreen1" "DarkSeaGreen2" "DarkSeaGreen3" "DarkSeaGreen4"
"PaleGreen1" "PaleGreen2" "PaleGreen3" "PaleGreen4"
"DarkOliveGreen1" "DarkOliveGreen2" "DarkOliveGreen3" "DarkOliveGreen4"
"OliveDrab1" "OliveDrab2" "OliveDrab3" "OliveDrab4"
"ivory1" "ivory2" "ivory3" "ivory4"
"LightYellow1" "LightYellow2" "LightYellow3" "LightYellow4"
"khaki1" "khaki2" "khaki3" "khaki4"
"LemonChiffon1" "LemonChiffon2" "LemonChiffon3" "LemonChiffon4"
"LightGoldenrod1" "LightGoldenrod2" "LightGoldenrod3" "LightGoldenrod4"
"cornsilk1" "cornsilk2" "cornsilk3" "cornsilk4"
"goldenrod1" "goldenrod2" "goldenrod3" "goldenrod4"
"DarkGoldenrod1" "DarkGoldenrod2" "DarkGoldenrod3" "DarkGoldenrod4"
"wheat1" "wheat2" "wheat3" "wheat4"
"NavajoWhite1" "NavajoWhite2" "NavajoWhite3" "NavajoWhite4"
"burlywood1" "burlywood2" "burlywood3" "burlywood4"
"AntiqueWhite1" "AntiqueWhite2" "AntiqueWhite3" "AntiqueWhite4"
"bisque1" "bisque2" "bisque3" "bisque4"
"tan1" "tan2" "tan3" "tan4"
"PeachPuff1" "PeachPuff2" "PeachPuff3" "PeachPuff4"
"seashell1" "seashell2" "seashell3" "seashell4"
"chocolate1" "chocolate2" "chocolate3" "chocolate4"
"sienna1" "sienna2" "sienna3" "sienna4"
"LightSalmon1" "LightSalmon2" "LightSalmon3" "LightSalmon4"
"salmon1" "salmon2" "salmon3" "salmon4"
"coral1" "coral2" "coral3" "coral4"
"tomato1" "tomato2" "tomato3" "tomato4"
"MistyRose1" "MistyRose2" "MistyRose3" "MistyRose4"
"snow1" "snow2" "snow3" "snow4"
"RosyBrown1" "RosyBrown2" "RosyBrown3" "RosyBrown4"
"IndianRed1" "IndianRed2" "IndianRed3" "IndianRed4"
"firebrick1" "firebrick2" "firebrick3" "firebrick4"
"brown1" "brown2" "brown3" "brown4"
"magenta1" "magenta2" "magenta3" "magenta4"
"blue1" "blue2" "blue3" "blue4"
"DeepSkyBlue1" "DeepSkyBlue2" "DeepSkyBlue3" "DeepSkyBlue4"
"turquoise1" "turquoise2" "turquoise3" "turquoise4"
"cyan1" "cyan2" "cyan3" "cyan4"
"SpringGreen1" "SpringGreen2" "SpringGreen3" "SpringGreen4"
"green1" "green2" "green3" "green4"
"chartreuse1" "chartreuse2" "chartreuse3" "chartreuse4"
"yellow1" "yellow2" "yellow3" "yellow4"
"gold1" "gold2" "gold3" "gold4"
"orange1" "orange2" "orange3" "orange4"
"DarkOrange1" "DarkOrange2" "DarkOrange3" "DarkOrange4"
"OrangeRed1" "OrangeRed2" "OrangeRed3" "OrangeRed4"
"red1" "red2" "red3" "red4"
"lavender blush" "LavenderBlush" "ghost white" "GhostWhite"
"lavender" "alice blue" "AliceBlue" "azure" "light cyan"
"LightCyan" "mint cream" "MintCream" "honeydew" "ivory"
"light goldenrod yellow" "LightGoldenrodYellow" "light yellow"
"LightYellow" "beige" "floral white" "FloralWhite" "old lace"
"OldLace" "blanched almond" "BlanchedAlmond" "moccasin"
"papaya whip" "PapayaWhip" "bisque" "antique white"
"AntiqueWhite" "linen" "peach puff" "PeachPuff" "seashell"
"misty rose" "MistyRose" "snow" "light pink" "LightPink" "pink"
"hot pink" "HotPink" "deep pink" "DeepPink" "maroon"
"pale violet red" "PaleVioletRed" "violet red" "VioletRed"
"medium violet red" "MediumVioletRed" "violet" "plum" "thistle"
"orchid" "medium orchid" "MediumOrchid" "dark orchid"
"DarkOrchid" "purple" "blue violet" "BlueViolet" "medium purple"
"MediumPurple" "light slate blue" "LightSlateBlue"
"medium slate blue" "MediumSlateBlue" "slate blue" "SlateBlue"
"dark slate blue" "DarkSlateBlue" "midnight blue" "MidnightBlue"
"navy" "navy blue" "NavyBlue" "dark blue" "DarkBlue"
"light steel blue" "LightSteelBlue" "cornflower blue"
"CornflowerBlue" "dodger blue" "DodgerBlue" "royal blue"
"RoyalBlue" "light slate gray" "light slate grey"
"LightSlateGray" "LightSlateGrey" "slate gray" "slate grey"
"SlateGray" "SlateGrey" "dark slate gray" "dark slate grey"
"DarkSlateGray" "DarkSlateGrey" "steel blue" "SteelBlue"
"cadet blue" "CadetBlue" "light sky blue" "LightSkyBlue"
"sky blue" "SkyBlue" "light blue" "LightBlue" "powder blue"
"PowderBlue" "pale turquoise" "PaleTurquoise" "turquoise"
"medium turquoise" "MediumTurquoise" "dark turquoise"
"DarkTurquoise" "dark cyan" "DarkCyan" "aquamarine"
"medium aquamarine" "MediumAquamarine" "light sea green"
"LightSeaGreen" "medium sea green" "MediumSeaGreen" "sea green"
"SeaGreen" "dark sea green" "DarkSeaGreen" "pale green"
"PaleGreen" "lime green" "LimeGreen" "dark green" "DarkGreen"
"forest green" "ForestGreen" "light green" "LightGreen"
"green yellow" "GreenYellow" "yellow green" "YellowGreen"
"olive drab" "OliveDrab" "dark olive green" "DarkOliveGreen"
"lemon chiffon" "LemonChiffon" "khaki" "dark khaki" "DarkKhaki"
"cornsilk" "pale goldenrod" "PaleGoldenrod" "light goldenrod"
"LightGoldenrod" "goldenrod" "dark goldenrod" "DarkGoldenrod"
"wheat" "navajo white" "NavajoWhite" "tan" "burlywood"
"sandy brown" "SandyBrown" "peru" "chocolate" "saddle brown"
"SaddleBrown" "sienna" "rosy brown" "RosyBrown" "dark salmon"
"DarkSalmon" "coral" "tomato" "light salmon" "LightSalmon"
"salmon" "light coral" "LightCoral" "indian red" "IndianRed"
"firebrick" "brown" "dark red" "DarkRed" "magenta"
"dark magenta" "DarkMagenta" "dark violet" "DarkViolet"
"medium blue" "MediumBlue" "blue" "deep sky blue" "DeepSkyBlue"
"cyan" "medium spring green" "MediumSpringGreen" "spring green"
"SpringGreen" "green" "lawn green" "LawnGreen" "chartreuse"
"yellow" "gold" "orange" "dark orange" "DarkOrange" "orange red"
"OrangeRed" "red" "white" "white smoke" "WhiteSmoke" "gainsboro"
"light gray" "light grey" "LightGray" "LightGrey" "gray" "grey"
"dark gray" "dark grey" "DarkGray" "DarkGrey" "dim gray"
"dim grey" "DimGray" "DimGrey" "black"))))
By default “.ltx” is assoiated with LaTeX files, but not “.latex”.
(add-to-list 'auto-mode-alist '("\\.latex\\'" . latex-mode))
(add-to-list 'auto-mode-alist
'("\\.gitconfig\\'" . conf-mode))
(add-to-list 'auto-mode-alist
(cons (concat (regexp-quote (f-join ".git" "config")) "\\'")
'conf-mode))
For some unknown reason, on my system xdg-email
does nothing (but
still exits successfully) when started through start-process
. So we
use call-process
instead.
(define-advice report-emacs-bug-insert-to-mailer
(:around (orig-fun &rest args) use-call-process)
"Use `call-process' instead of `start-process'.
For some reason \"xdg-email\" doesn't work from `start-process',
so we use `call-process' instead. This is fine because both the
OS X \"open\" and unix \"xdg-email\" commands exit
immediately."
(noflet ((start-process (name buffer program &rest program-args)
(apply #'call-process program nil buffer nil program-args)))
(apply orig-fun args)))
(defun insert-to-mailer (&optional arg-ignored)
"Send the message to your preferred mail client.
This requires either the macOS \"open\" command, or the freedesktop
\"xdg-email\" command to be available.
This function accepts a prefix argument for consistency with
`message-send', but the prefix argument has no effect."
(interactive)
(save-excursion
;; FIXME? use mail-fetch-field?
(let* ((to (progn
(goto-char (point-min))
(forward-line)
(and (looking-at "^To: \\(.*\\)")
(match-string-no-properties 1))))
(subject (progn
(forward-line)
(and (looking-at "^Subject: \\(.*\\)")
(match-string-no-properties 1))))
(body (progn
(forward-line 2)
(buffer-substring-no-properties (point) (point-max)))))
(if (and to subject body)
(if (report-emacs-bug-can-use-osx-open)
(start-process "/usr/bin/open" nil "open"
(concat "mailto:" to
"?subject=" (url-hexify-string subject)
"&body=" (url-hexify-string body)))
(start-process "xdg-email" nil "xdg-email"
"--subject" subject
"--body" body
(concat "mailto:" to)))
(error "Subject, To or body not found")))))
(defun insert-to-mailer-and-exit (&optional arg)
"Send message like `insert-to-mailer', then, if no errors, exit from mail buffer.
This function accepts a prefix argument for consistency with
`message-send-and-exit', but the prefix argument has no effect."
(interactive "P")
(let ((buf (current-buffer))
(actions message-exit-actions))
(when (and (insert-to-mailer arg)
(buffer-name buf))
(message-bury buf)
(if message-kill-buffer-on-exit
(kill-buffer buf))
(message-do-actions actions)
t)))
(define-mail-user-agent 'external-mailer-user-agent
(get 'message-user-agent 'composefunc)
#'insert-to-mailer-and-exit
(get 'message-user-agent 'abortfunc)
(get 'message-user-agent 'hookvar))
Emacs functions that modify the local variables in the propline also add an extraneous trailing semicolon. This advice deletes it.
(define-advice modify-file-local-variable-prop-line
(:around (orig-fun &rest args) cleanup-semicolon)
"Delete the trailing semicolon."
(atomic-change-group
(apply orig-fun args)
(save-excursion
(goto-char (point-min))
(let ((replace-lax-whitespace t))
(replace-string "; -*-" " -*-" nil
(point) (progn (end-of-line) (point)))))))
Emacs sh-mode doesn’t automatically associate *.zsh
with zsh. This
enables that. It also enables it for a few other zsh-related files.
;; Files ending in .zsh
(add-to-list 'auto-mode-alist '("\\.zsh\\'" . sh-mode))
;; zsh startup files
(add-to-list 'auto-mode-alist '("\\.\\(zshrc\\|zshenv\\|zprofile\\|zlogin\\|zlogout\\)\\>" . sh-mode))
;; Ensure that sh-mode uses zsh as shell for these files
(defun sh-mode-set-zsh-by-file-name ()
(when (and buffer-file-name
(string-match-p "\\.zsh\\(rc\\|env\\|\\'\\)" buffer-file-name))
(sh-set-shell "zsh")))
(add-hook 'sh-mode-hook 'sh-mode-set-zsh-by-file-name)
Emacs has a command to sort lines, but not to sort words in a region.
(defun sort-words (reverse beg end)
"Sort words in region alphabetically, in REVERSE if negative.
Prefixed with negative \\[universal-argument], sorts in reverse.
The variable `sort-fold-case' determines whether alphabetic case
affects the sort order.
See `sort-regexp-fields'."
(interactive "*P\nr")
(sort-regexp-fields reverse "\\w+" "\\&" beg end))
Git-gutter doesn’t play nice with TRAMP remotes
(defun git-gutter-find-file-hook ()
(git-gutter-mode
(if (file-remote-p (buffer-file-name))
0
1)))
(add-hook 'find-file-hook #'git-gutter-find-file-hook)
If a file begins with a shebang (i.e. “#!”), make it executable after saving it.
(add-hook 'after-save-hook
'executable-make-buffer-file-executable-if-script-p)
(defun python-newline-and-indent ()
"Custom python indentation function.
This works like normal, except that if point is in the
indentation of the current line, the newly created line will
not be indented any further than the current line. This fixes
the annoying tendency of python-mode to always indent to the
maximum possible indentation level on every new line."
(interactive)
(let* ((starting-column (current-column))
(starting-indentation (current-indentation))
(started-in-indentation (<= starting-column starting-indentation)))
(newline-and-indent)
(when (and started-in-indentation
(> (current-indentation) starting-indentation))
(save-excursion
(back-to-indentation)
(delete-region (point) (progn (forward-line 0) (point)))
(indent-to-column starting-indentation))
(back-to-indentation))))
(define-key python-mode-map (kbd "RET") #'python-newline-and-indent)
(defun turn-off-electric-indent-local-mode ()
(electric-indent-local-mode 0))
(add-hook 'python-mode-hook #'turn-off-electric-indent-local-mode)
When an R script is in a directory named “scripts”, suggest the parent directory as the starting directory.
(require 'f)
(defun my-ess-directory-function ()
(cond (ess-directory)
((string= "scripts" (f-filename (f-full default-directory)))
(f-parent default-directory))
(t nil)))
(setq ess-directory-function #'my-ess-directory-function)
When I use “C-x C-e” on a req-package
declaration, I usually want
the package to be installed immediately without having to call
req-package-finish
manually. This advice does that.
(define-advice eval-last-sexp (:around (orig-fun &rest args) req-package-eagerly)
"Call `req-package-finish' afterward if evaluating a `req-package' form."
(let ((is-req-package
(eq (car-safe (elisp--preceding-sexp))
'req-package)))
(prog1 (apply orig-fun args)
(ignore-errors
(when is-req-package
(req-package-finish))))))
These are disabled for now due to the error documented here:
Update: These are now disabled because they have started messing up the spacing of things, which is weird because they’re supposed to take up the same space as the normal characters they replace.
;; ;;; Fira code
;; ;; This works when using emacs --daemon + emacsclient
;; (add-hook 'after-make-frame-functions (lambda (frame) (set-fontset-font t '(#Xe100 . #Xe16f) "Fira Code Symbol")))
;; ;; This works when using emacs without server/client
;; (set-fontset-font t '(#Xe100 . #Xe16f) "Fira Code Symbol")
;; ;; I haven't found one statement that makes both of the above situations work, so I use both for now
;; (defconst fira-code-font-lock-keywords-alist
;; (mapcar (lambda (regex-char-pair)
;; `(,(car regex-char-pair)
;; (0 (prog1 ()
;; (compose-region (match-beginning 1)
;; (match-end 1)
;; ;; The first argument to concat is a string containing a literal tab
;; ,(concat " " (list (decode-char 'ucs (cadr regex-char-pair)))))))))
;; '(("\\(www\\)" #Xe100)
;; ("[^/]\\(\\*\\*\\)[^/]" #Xe101)
;; ("\\(\\*\\*\\*\\)" #Xe102)
;; ("\\(\\*\\*/\\)" #Xe103)
;; ("\\(\\*>\\)" #Xe104)
;; ("[^*]\\(\\*/\\)" #Xe105)
;; ("\\(\\\\\\\\\\)" #Xe106)
;; ("\\(\\\\\\\\\\\\\\)" #Xe107)
;; ("\\({-\\)" #Xe108)
;; ("\\(\\[\\]\\)" #Xe109)
;; ("\\(::\\)" #Xe10a)
;; ("\\(:::\\)" #Xe10b)
;; ("[^=]\\(:=\\)" #Xe10c)
;; ("\\(!!\\)" #Xe10d)
;; ("\\(!=\\)" #Xe10e)
;; ("\\(!==\\)" #Xe10f)
;; ("\\(-}\\)" #Xe110)
;; ("\\(--\\)" #Xe111)
;; ("\\(---\\)" #Xe112)
;; ("\\(-->\\)" #Xe113)
;; ("[^-]\\(->\\)" #Xe114)
;; ("\\(->>\\)" #Xe115)
;; ("\\(-<\\)" #Xe116)
;; ("\\(-<<\\)" #Xe117)
;; ("\\(-~\\)" #Xe118)
;; ("\\(#{\\)" #Xe119)
;; ("\\(#\\[\\)" #Xe11a)
;; ("\\(##\\)" #Xe11b)
;; ("\\(###\\)" #Xe11c)
;; ("\\(####\\)" #Xe11d)
;; ("\\(#(\\)" #Xe11e)
;; ("\\(#\\?\\)" #Xe11f)
;; ("\\(#_\\)" #Xe120)
;; ("\\(#_(\\)" #Xe121)
;; ("\\(\\.-\\)" #Xe122)
;; ("\\(\\.=\\)" #Xe123)
;; ("\\(\\.\\.\\)" #Xe124)
;; ("\\(\\.\\.<\\)" #Xe125)
;; ("\\(\\.\\.\\.\\)" #Xe126)
;; ("\\(\\?=\\)" #Xe127)
;; ("\\(\\?\\?\\)" #Xe128)
;; ("\\(;;\\)" #Xe129)
;; ("\\(/\\*\\)" #Xe12a)
;; ("\\(/\\*\\*\\)" #Xe12b)
;; ("\\(/=\\)" #Xe12c)
;; ("\\(/==\\)" #Xe12d)
;; ("\\(/>\\)" #Xe12e)
;; ("\\(//\\)" #Xe12f)
;; ("\\(///\\)" #Xe130)
;; ("\\(&&\\)" #Xe131)
;; ("\\(||\\)" #Xe132)
;; ("\\(||=\\)" #Xe133)
;; ("[^|]\\(|=\\)" #Xe134)
;; ("\\(|>\\)" #Xe135)
;; ("\\(\\^=\\)" #Xe136)
;; ("\\(\\$>\\)" #Xe137)
;; ("\\(\\+\\+\\)" #Xe138)
;; ("\\(\\+\\+\\+\\)" #Xe139)
;; ("\\(\\+>\\)" #Xe13a)
;; ("\\(=:=\\)" #Xe13b)
;; ("[^!/]\\(==\\)[^>]" #Xe13c)
;; ("\\(===\\)" #Xe13d)
;; ("\\(==>\\)" #Xe13e)
;; ("[^=]\\(=>\\)" #Xe13f)
;; ("\\(=>>\\)" #Xe140)
;; ("\\(<=\\)" #Xe141)
;; ("\\(=<<\\)" #Xe142)
;; ("\\(=/=\\)" #Xe143)
;; ("\\(>-\\)" #Xe144)
;; ("\\(>=\\)" #Xe145)
;; ("\\(>=>\\)" #Xe146)
;; ("[^-=]\\(>>\\)" #Xe147)
;; ("\\(>>-\\)" #Xe148)
;; ("\\(>>=\\)" #Xe149)
;; ("\\(>>>\\)" #Xe14a)
;; ("\\(<\\*\\)" #Xe14b)
;; ("\\(<\\*>\\)" #Xe14c)
;; ("\\(<|\\)" #Xe14d)
;; ("\\(<|>\\)" #Xe14e)
;; ("\\(<\\$\\)" #Xe14f)
;; ("\\(<\\$>\\)" #Xe150)
;; ("\\(<!--\\)" #Xe151)
;; ("\\(<-\\)" #Xe152)
;; ("\\(<--\\)" #Xe153)
;; ("\\(<->\\)" #Xe154)
;; ("\\(<\\+\\)" #Xe155)
;; ("\\(<\\+>\\)" #Xe156)
;; ("\\(<=\\)" #Xe157)
;; ("\\(<==\\)" #Xe158)
;; ("\\(<=>\\)" #Xe159)
;; ("\\(<=<\\)" #Xe15a)
;; ("\\(<>\\)" #Xe15b)
;; ("[^-=]\\(<<\\)" #Xe15c)
;; ("\\(<<-\\)" #Xe15d)
;; ("\\(<<=\\)" #Xe15e)
;; ("\\(<<<\\)" #Xe15f)
;; ("\\(<~\\)" #Xe160)
;; ("\\(<~~\\)" #Xe161)
;; ("\\(</\\)" #Xe162)
;; ("\\(</>\\)" #Xe163)
;; ("\\(~@\\)" #Xe164)
;; ("\\(~-\\)" #Xe165)
;; ("\\(~=\\)" #Xe166)
;; ("\\(~>\\)" #Xe167)
;; ("[^<]\\(~~\\)" #Xe168)
;; ("\\(~~>\\)" #Xe169)
;; ("\\(%%\\)" #Xe16a)
;; ;; ("\\(x\\)" #Xe16b) This ended up being hard to do properly so i'm leaving it out.
;; ("[^:=]\\(:\\)[^:=]" #Xe16c)
;; ("[^\\+<>]\\(\\+\\)[^\\+<>]" #Xe16d)
;; ("[^\\*/<>]\\(\\*\\)[^\\*/<>]" #Xe16f))))
;; (defun add-fira-code-symbol-keywords ()
;; (font-lock-add-keywords nil fira-code-font-lock-keywords-alist))
;; (add-hook 'prog-mode-hook
;; #'add-fira-code-symbol-keywords)
If wheel-left and wheel-right are left unbound, Emacs rings the bell when they are used, which can easily happen when scrolling on a touchpad. To prevent the bell from ringing, we add no-op bindings to these events.
(bind-key "<wheel-left>" 'ignore)
(bind-key "<wheel-right>" 'ignore)
Calling suspend-frame
on a GUI frame causes it to remain frozen when
unminimized. So we prevent suspend-frame
from working on GUI frames.
(define-advice suspend-frame (:around (orig-fun &rest args) disable-on-gui-frames)
"Disable `suspend-frame' on GUI frames."
(if (display-graphic-p (selected-frame))
(message "`suspend-frame' disabled for GUI frames.")
(apply orig-fun args)))
(define-advice ido-file-lessp (:around (orig-fun &rest args) respect-ido-case-fold)
"Make sure that Ido file sorting respects `ido-case-fold'."
(if ido-case-fold
(let ((args-lower (mapcar #'downcase args)))
;; Compare case-insensitively, unless the file names differ
;; only in case.
(if (apply #'string= args-lower)
(apply orig-fun args)
(apply orig-fun args-lower)))
(apply orig-fun args)))
(define-advice ido-file-extension-lessp (:around (orig-fun &rest args) respect-ido-case-fold)
"Make sure that Ido file sorting respects `ido-case-fold'."
(if ido-case-fold
(let ((args-lower (mapcar #'downcase args)))
;; Compare case-insensitively, unless the file names differ
;; only in case.
(if (apply #'string= args-lower)
(apply orig-fun args)
(apply orig-fun args-lower)))
(apply orig-fun args)))
hl-line-mode
means that the last-applied face at point will always be
hl-line, which isn’t very useful. This adds advice to transiently
un-highlight the current line before running face-at-point
, ensuring
that it returns whatever it would have returned if hl-line-mode was
disabled. There is no need to re-highlight the line, as the line
highlght is always re-applied after each command anyway.
(require 'hl-line)
(define-advice face-at-point (:before (&rest _ignored) avoid-hl-line)
(ignore-errors
(when hl-line-mode
(hl-line-unhighlight)))
(ignore-errors
(when global-hl-line-mode
(global-hl-line-unhighlight))))
The cycle-spacing
command encompasses the functionality of
just-one-space
and several other commands, so we remap just-one-space
(M-SPC
by default) to it.
(define-key global-map [remap just-one-space] 'cycle-spacing)
The DWIM versions are equivalent to the -word
versions except that if
the region is active, they operate on the entire region instead.
(define-key global-map [remap upcase-word] #'upcase-dwim)
(define-key global-map [remap downcase-word] #'downcase-dwim)
(define-key global-map [remap capitlize-word] #'capitlize-dwim)
Note that this frees up C-x C-u
and C-x C-l
, the bindings for
up/downcase-region
respectively.
(defun twiddle-mode (mode)
"If MODE is activated, then deactivate it and then activate it again.
If MODE is not active, do nothing."
(when (eval mode)
(funcall mode 0)
(funcall mode 1)))
Diff hunk refinement is really slow for large hunks, so we establish a word threshold above which hunk refinement will not occur.
(defcustom diff-refine-max-words nil
"If non-nil, disable refinement for large hunks.
If non-nil, this variable should be set to an integer, which is the
limit for the number of words in a hunk to be refined. Hunks
containing more than this number of words will not be refined.
Note that the word count includes the entire hunk: additions,
deletions, and context (and header?)."
:group 'diff-mode
:type '(choice
(const :tag "No limit" nil)
(integer
:tag "Word limit" :value 1000
:validate
(lambda (widget)
(let ((v (widget-value widget)))
(if (and (integerp v)
(> v 0))
nil
(widget-put widget :error "This field should contain a positive integer")
widget))))))
(define-advice diff--refine-hunk (:before-while (start end &rest _ignored) diff-refine-max-words)
"Implement logic for `diff-refine-max-words'."
;; With `before-while', we need to return non-nil to indicate that
;; hunk refinement should be allowed to occur.
(condition-case nil
(or
(null diff-refine-max-words)
(let ((hunk-word-count (count-words start end)))
(<= hunk-word-count diff-refine-max-words)))
;; In case of error, do whatever would have happened if this
;; advice didn't exist.
(error t)))
(make-directory
(file-name-directory
(with-temp-buffer
(setq buffer-file-name (expand-file-name "~/temp.txt"))
(make-auto-save-file-name)))
t)
Sometimes after filling a long line with C-q
, auto-fill-mode
, etc.,
the window is still scrolled away from column 0, which isn’t useful
any more. So we define advice to automatically scroll back to column 0
after any filling operation (unless doing so would put point outside
the window).
(defun scroll-to-column0-after-fill (&rest _ignored)
"Scroll current window to column 0 if possible.
If scrolling the window to column 0 would pout point outside the
window, no scrolling is done.
This function is intended to be called after calling a filling
function such as `fill-paragraph'."
(when (and (> (window-hscroll))
(<= (current-column) (window-width)))
(set-window-hscroll (get-buffer-window (current-buffer)) 0)))
(cl-loop
for fun in '(fill-paragraph do-auto-fill org-fill-paragraph)
do (advice-add fun
:after #'scroll-to-column0-after-fill
'(:name "After filling, scroll back to column 0 if possible")))
(defun clean-kill-ring (&optional buffer)
"Remove entries matching BUFFER from `kill-ring'.
Also clears PRIMARY and SECONDARY selections by setting them to
the empty string and clears CLIPBOARD by setting it to the first
remaining element of `kill-ring', which should clear the
clipboard for other applications as well."
(interactive)
;; Just do this so current kill definitely doesn't contain any
;; sensitive info. This element will be removed anyway since the
;; empty string matches anything.
(kill-new "")
(setq kill-ring
(cl-loop
with bufstring = (with-current-buffer (or buffer (current-buffer)) (buffer-string))
for kill in kill-ring
unless (string-match-p (regexp-quote kill) bufstring)
collect kill))
(gui-set-selection 'CLIPBOARD (or (car kill-ring) ""))
(gui-set-selection 'PRIMARY "")
(gui-set-selection 'SECONDARY "")
(message "Kill ring cleared of entries matching buffer %S" (buffer-name buffer)))
See https://emacs.stackexchange.com/a/76352/2046 and https://lists.gnu.org/archive/html/emacs-orgmode/2023-03/msg00299.html.
;;; work-around for org-ctags obnoxious behavior
(with-eval-after-load 'org-ctags (setq org-open-link-functions nil))
(with-eval-after-load 'ol
(org-link-set-parameters
"maybe"
:follow
(lambda (path prefix)
(condition-case err
(org-link-open-from-string (format "[[%s]]" path))
(error (message "Failed to open maybe link %S" path))))
;; This must be a lambda so it is self-contained
:export
(lambda (path desc backend &optional info)
(when (symbolp backend)
(setq backend (org-export-get-backend backend)))
;; Generate the non-maybe version of the link, and call the
;; backend's appropriate transcoder on it, but catch any error
;; thrown and just replace the link with its text instead.
(let* ((real-link
(with-temp-buffer
(save-excursion (insert "[[" path "][" (or desc path) "]]"))
(org-element-link-parser)))
(real-link-transcoder (cdr (assoc 'link (org-export-get-all-transcoders backend)))))
(condition-case err
(funcall real-link-transcoder real-link desc info)
(error
(message "Skipping error during maybe link transcoding: %S" err)
(or desc path)))))))
For some reason, Emacs sometimes starts up with the scratch buffer in Fundamental mode with no initial message. Until the root cause is isolated, we just fix this manually afterward
(eval-after-init
'(progn
(with-current-buffer (get-scratch-buffer-create)
(normal-mode)
(when (and initial-scratch-message
(= (point-min) (point-max)))
(insert (substitute-command-keys initial-scratch-message))
(goto-char (point-max))
(set-buffer-modified-p nil)))))
This section uses the macros defined in system-specific-settings
to
set options that should vary depending on which system Emacs is
running on.
Normally we want the scroll bar and menu bar disabled for maximum text space. But in Mac OS, disabling them causes various things to break, so we want to enabled them there.
(require 'frame)
(add-hook
'after-make-frame-functions
(lambda (frame)
(with-selected-frame frame
(let ((mode-arg (if-system-type-match 'darwin 1 -1)))
(menu-bar-mode mode-arg)
(when (fboundp #'scroll-bar-mode)
(scroll-bar-mode mode-arg))))))
(when-system-type-match
(list 'darwin
'(regexp . "linux"))
(defvar trash-command "trash")
(defun system-move-file-to-trash (filename)
"Move file to OS X/linux trash.
This assumes that a program called `trash' is in your $PATH and
that this program will, when passed a single file path as an
argument, move that file to the trash."
(call-process trash-command nil nil nil filename)))
On OSX, the standard df command (BSD version, I think) is
insufficient, and we want GNU df instead, which is typically installed
as gdf
. And we may as well use gdf over df on any other system which
provides both as well. This implementation uses /opt/local/bin/gdf
preferentially, since that is the version installed by Macports.
(when (executable-find "gdf")
(setq directory-free-space-program "gdf"))
On Mac OS, Emacs uses ns_selection_fg_color
and
ns_selection_bg_color
to determine the colors for the region face.
When Mac OS activates dark mode, these colors change to dark blue, but
they don’t change back when dark mode is deactivated (at least not in
Emacs). So we avoid using these and instead set the colors exactly as
they are in light mode.
(require 'frame)
(defun fix-mac-region-colors (&optional frame)
"Set region face to a fixed color regardless of Mac Dark Mode
On Mac OS, Dark Mode messes with the color of the region in weird
ways that makes it visually unusable. Instead, we set the region
color to a static color that matches the non-dark-mode region
color."
(interactive)
(with-selected-frame (or frame (selected-frame))
(when (and (equal (face-attribute 'region :distant-foreground)
"ns_selection_fg_color")
(equal (face-attribute 'region :background)
"ns_selection_bg_color"))
(set-face-attribute
'region nil
:distant-foreground 'unspecified
:background "#BAD6FC"))))
;; Fix for future frames
(add-hook 'after-make-frame-functions #'fix-mac-region-colors)
;; Fix for current frames
(mapc #'fix-mac-region-colors (frame-list))
In GTK on Ubuntu, the default selection colors are way too light, almost invisible. We instead choose a desaturated version of the standard Ubuntu orange selection color.
(require 'frame)
;; Lighter Ubuntu orange: #ffb499
;; Lighter GNOME blue: #88beff
(defvar gtk-region-custom-color "#88beff")
(defun fix-gtk-region-colors (&optional frame)
"Set reasonable region colors on GNOME"
(interactive)
(with-selected-frame (or frame (selected-frame))
(when (and (equal (face-attribute 'region :distant-foreground)
"gtk_selection_fg_color")
(equal (face-attribute 'region :background)
"gtk_selection_bg_color"))
(set-face-attribute
'region nil
:distant-foreground 'unspecified
:background gtk-region-custom-color))))
;; Fix for future frames
(add-hook 'after-make-frame-functions #'fix-gtk-region-colors)
;; Fix for current frames
(mapc #'fix-gtk-region-colors (frame-list))