This ORG-mode file generates an Emacs configuration. I am calling it an Emacs+Nix IDE. That is, the Emacs configuration is integrated with hardcoded Nix store paths. This provides a kind of functional Emacs configuration. The integration between Emacs & Nix comes with lots of useful side effects. The raw Org file can always be downloaded here. You can create pull requests & issues on the GitHub repo. You can access my website at https://matthewbauer.us.
This is the script that I use to setup all of my new machines but it’s portable enough for anyone to use.
To install, just run this command from your shell:
curl https://matthewbauer.us/bauer/install | sh
Once it’s installed, you can open Emacs at any time with this command:
"$HOME/.nix-profile/bin/run"
If you don’t like it, it’s also very easy to uninstall. Just run:
nix-env -e bauer
nix-collect-garbage -d
After you’ve installed it, it’s easy to make changes. By default, the
configuration lives in ~/.local/share/bauer
. To make changes just follow this
process,
cd ~/.local/share/bauer
nix-build
./result/run README.org
The last line will spawn an Emacs frame in the Git repo. Anything in that file
can be change. Once you’ve made a change, just run M-x dev-restart
to rebuild
the configuration & restart Emacs. Make any changes you want to the README.org
file or any of the files in the site-lisp folder. Make sure you commit your
changes afterward by typing C-c p v
, then cc
. If you have already forked
this repo on GitHub, you can add it as a remote by typing Mg
followed by your
GitHub username within Magit. To push to it, just type Pr
then find your
username in the list & press enter. Pull requests are always welcome through
GitHub!
You can also use this configuration without Nix. This just gives you the Emacs configuration without any of the Nix integrations. All of the binaries will have to be provided externally. To get started, run the following:
mv ~/.emacs.d ~/.emacs.d.old
git clone https://github.com/matthewbauer/bauer \
~/.emacs.d
Recent versions of macOS have a “feature” called AppNap that causes unfocused apps to sleep in the background. This often results in things like compilation taking forever because it only does any work while focused. We almost never want Emacs to suspend on its own, so it’s important to disable this.
Luckily, it’s not very hard to do this. From the terminal, run:
defaults write org.gnu.Emacs NSAppSleepDisabled -bool YES
defaults write emacs NSAppSleepDisabled -bool YES
I’ve seen some of the org.gnu.Emacs form posted online. However, if you start Emacs from the command line, the second one is necessary since this is how Apple will identify the binary.
This is totally optional. If this doesn’t bother you, feel free to leave it, and it will probably slightly improve your energy usage, since it can suspend Emacs when unfocused.
This is the main part of the IDE. It is written in Emacs Lisp & will be loaded every time Emacs is started.
Increasing GC is a common way to speed up Emacs. gc-cons-threshold
sets at
what point Emacs should invoke its garbage collector Some people set it to a
really larger number permanently. This works well until the garbage is actually
collected (then you have to wait a long time). I’ve decided to just set it
temporarily to a large number so we only garbage collect once on startup. After
that we reset it to the standard value. Read @bling’s post for more info on
this.
;; -*- mode: emacs-lisp; coding: utf-8; -*-
(defvar file-name-handler-alist-backup
file-name-handler-alist)
(setq gc-cons-threshold most-positive-fixnum
file-name-handler-alist nil)
(add-hook 'after-init-hook
(lambda ()
(garbage-collect)
(setq gc-cons-threshold
(car (get 'gc-cons-threshold 'standard-value))
file-name-handler-alist
(append
file-name-handler-alist-backup
file-name-handler-alist))))
(setq read-process-output-max (* 1024 1024))
Disabled for now.
;; (eval-and-compile (require 'use-package))
;; (use-package benchmark-init
;; :demand
;; :config
;; ;; To disable collection of benchmark data after init is done.
;; (add-hook 'after-init-hook 'benchmark-init/deactivate))
Setup some initial aliases for Emacs. These give us an easy way to use these functions without actually require’ing them. Ideally, Emacs should pick these up through the automatic autoloading method, but that sometimes conflicts with the compiling phases used later.
(eval-and-compile
(autoload 'package-installed-p "package")
(autoload 'use-package-autoload-keymap "use-package")
(autoload 'pcomplete-arg "pcomplete")
(autoload 'pcomplete--here "pcomplete")
(autoload 'tramp-tramp-file-p "tramp")
(autoload 'tramp-dissect-file-name "tramp")
(defvar view-mode-map)
(defvar iso-transl-ctl-x-8-map)
(defvar dired-mode-map))
When we are within a terminal we want to be able to use the mouse, so
xterm-mouse-mode
is enabled here.
iTerm doesn’t seem to support xterm color reporting correctly, so we use the COLORFGBG method.
(unless (display-graphic-p)
(xterm-mouse-mode 1)
(when (string-equal (getenv "TERM_PROGRAM") "iTerm.app")
(add-hook 'after-make-frame-functions
#'(lambda
;; Take advantage of iterm2's CSI u support (https://gitlab.com/gnachman/iterm2/-/issues/8382).
(xterm--init-modify-other-keys)
;; Courtesy https://emacs.stackexchange.com/a/13957, modified per
;; https://gitlab.com/gnachman/iterm2/-/issues/8382#note_365264207
(defun character-apply-modifiers (c &rest modifiers)
"Apply modifiers to the character C.
MODIFIERS must be a list of symbols amongst (meta control shift).
Return an event vector."
(if (memq 'control modifiers) (setq c (if (and (<= ?a c) (<= c ?z))
(logand c ?\x1f)
(logior (lsh 1 26) c))))
(if (memq 'meta modifiers) (setq c (logior (lsh 1 27) c)))
(if (memq 'shift modifiers) (setq c (logior (lsh 1 25) c)))
(vector c))
(when (and (boundp 'xterm-extra-capabilities) (boundp 'xterm-function-map))
(let ((c 32))
(while (<= c 126)
(mapc (lambda (x)
(define-key xterm-function-map (format (car x) c)
(apply 'character-apply-modifiers c (cdr x))))
'(;; with ?.VT100.formatOtherKeys: 0
("\e\[27;3;%d~" meta)
("\e\[27;5;%d~" control)
("\e\[27;6;%d~" control shift)
("\e\[27;7;%d~" control meta)
("\e\[27;8;%d~" control meta shift)
;; with ?.VT100.formatOtherKeys: 1
("\e\[%d;3u" meta)
("\e\[%d;5u" control)
("\e\[%d;6u" control shift)
("\e\[%d;7u" control meta)
("\e\[%d;8u" control meta shift)))
(setq c (1+ c)))))
))
;; ;; xterm--report-background-handler init fails, but we can still
;; ;; use COLORFGBG
;; (defvar xterm-extra-capabilities)
;; (setq xterm-extra-capabilities '(modifyOtherKeys getSelection setSelection))
(autoload 'rxvt-set-background-mode "term/rxvt")
(add-hook 'window-setup-hook 'rxvt-set-background-mode)
))
set-defaults provides an easy way to override the default custom files. This means that when you customize a variable it will appear as ‘standard’ even though it’s not what the package originally defined as the default. This is useful for an Emacs distribution to provide better defaults while still letting the user override them. Look through the lispdoc of the package for documentation on how this works. Eventually, this will be added to MELPA for use in other Emacs distributions.
(require 'set-defaults)
These are some better defaults for Emacs. They shouldn’t require any packages
to be installed to work (those go in use-package). In addition, they should take
almost no time to run (meaning they probably shouldn’t have custom init hooks).
The format of arguments to set-defaults
is identical to the one used by
custom-set-variables
.
Default Variable | Default Value |
---|---|
apropos-do-all | t |
async-shell-command-buffer | ‘new-buffer |
auth-source-save-behavior | t |
auto-revert-avoid-polling | t |
auto-revert-check-vc-info | t |
auto-revert-interval | 2 |
auto-revert-verbose | nil |
backward-delete-char-untabify-method | ‘hungry |
bidi-inhibit-bpa | t |
bidi-paragraph-direction | ‘left-to-right |
bookmark-save-flag | 1 |
checkdoc-spellcheck-documentation-flag | t |
comint-input-ignoredups | t |
comint-move-point-for-matching-input | ‘end-of-line |
comint-move-point-for-output | ‘all |
comint-process-echoes | t |
comint-prompt-read-only | t |
comint-scroll-to-bottom-on-input | ‘this |
compilation-always-kill | t |
compilation-ask-about-save | nil |
compilation-context-lines | t |
compilation-scroll-output | ‘first-error |
compilation-skip-threshold | 1 |
completions-cycle-threshold | t |
completions-detailed | t |
completions-format | ‘vertical |
completion-ignore-case | t |
case-fold-search | t |
cursor-in-non-selected-windows | nil |
custom-search-field | nil |
delete-by-moving-to-trash | t |
delete-old-versions | t |
dired-dwim-target | t |
dired-hide-details-hide-symlink-targets | nil |
dired-omit-verbose | nil |
dired-recursive-copies | ‘top |
dired-recursive-deletes | ‘top |
ediff-window-setup-function | ‘ediff-setup-windows-plain |
eldoc-idle-delay | 0.4 |
enable-recursive-minibuffers | t |
eshell-cmpl-autolist | t |
eshell-cmpl-cycle-completions | nil |
eshell-cmpl-ignore-case | t |
eshell-default-target-is-dot | t |
eshell-destroy-buffer-when-process-dies | t |
eshell-hist-ignoredups | ‘erase |
eshell-list-files-after-cd | t |
eshell-review-quick-commands | t |
eval-expression-print-level | nil |
flymake-no-changes-timeout | nil |
flymake-start-syntax-check-on-newline | nil |
flyspell-highlight-properties | nil |
flyspell-issue-welcome-flag | nil |
fill-column | 80 |
frame-inhibit-implied-resize | t |
history-delete-duplicates | t |
ibuffer-default-display-maybe-show-predicates | t |
ibuffer-expert | t |
ibuffer-show-empty-filter-groups | nil |
ibuffer-shrink-to-minimum-size | t |
ibuffer-use-other-window | t |
imenu-auto-rescan | t |
indent-tabs-mode | nil |
indicate-buffer-boundaries | ‘left |
indicate-empty-lines | t |
isearch-allow-scroll | ‘unlimited |
isearch-lazy-count | t |
isearch-yank-on-move | ‘shift |
ispell-quietly | t |
ispell-silently-savep | t |
jit-lock-chunk-size | 4096 |
jit-lock-defer-time | 0.05 |
jit-lock-stealth-time | 1.25 |
kill-do-not-save-duplicates | t |
kill-whole-line | t |
load-prefer-newer | t |
mac-command-modifier | ‘meta |
mac-option-modifier | ‘super |
mac-right-option-modifier | nil |
next-error-recenter | t |
ns-function-modifier | ‘hyper |
ns-pop-up-frames | nil |
nsm-save-host-names | t |
nxml-sexp-element-flag | t |
nxml-slash-auto-complete-flag | t |
project-compilation-buffer-name-function | ‘project-prefixed-buffer-name |
read-buffer-completion-ignore-case | t |
read-extended-command-predicate | ‘command-completion-default-include-p |
resize-mini-windows | t |
ruby-insert-encoding-magic-comment | nil |
save-abbrevs | ‘silently |
save-interprogram-paste-before-kill | t |
scroll-conservatively | 101 |
scroll-preserve-screen-position | ‘always |
sentence-end-double-space | nil |
set-mark-command-repeat-pop | t |
shell-completion-execonly | nil |
shell-input-autoexpand | nil |
switch-to-buffer-in-dedicated-window | ‘pop |
switch-to-buffer-obey-display-actions | t |
tab-always-indent | ‘complete |
term-input-autoexpand | t |
term-input-ignoredups | t |
tls-checktrust | t |
undo-limit | 80000000 |
undo-outer-limit | 120000000 |
undo-strong-limit | 12000000 |
uniquify-buffer-name-style | ‘post-forward-angle-brackets |
use-package-always-defer | t |
version-control | t |
view-inhibit-help-message | t |
view-read-only | t |
whitespace-line-column | 120 |
window-combination-resize | t |
woman-imenu | t |
x-stretch-cursor | t |
xref-show-definitions-function | ‘xref-show-definitions-completing-read |
(apply 'set-defaults
(mapcar (lambda (x) (list (intern (car x))
(if (stringp (cadr x))
(car (read-from-string (cadr x)))
(cadr x)))) defaults))
Misc. defaults that don’t fit above. TODO: move these above.
(when (file-exists-p user-emacs-directory)
(make-directory (expand-file-name "auto-save/" user-emacs-directory) t)
(make-directory (expand-file-name "backup/" user-emacs-directory) t)
(make-directory (expand-file-name "undo-tree/" user-emacs-directory) t))
(set-defaults
'(auto-save-file-name-transforms `((".*"
,(expand-file-name "auto-save/"
user-emacs-directory) t)))
'(backup-directory-alist `((".*" .
,(expand-file-name "backup/"
user-emacs-directory))))
'(context-menu-functions
'(context-menu-ffap
context-menu-undo
context-menu-region
context-menu-middle-separator
context-menu-local
context-menu-minor
occur-context-menu
Man-context-menu
dictionary-context-menu
context-menu-buffers))
'(comint-password-prompt-regexp (concat
"\\(^ *\\|"
(regexp-opt
'("Enter" "enter" "Enter same" "enter same" "Enter the" "enter the"
"Enter Auth" "Old" "old" "New" "new" "'s" "login"
"Kerberos" "CVS" "UNIX" " SMB" "LDAP" "PEM" "SUDO"
"[sudo]" "Repeat" "Bad" "Retype")
t)
;; Allow for user name to precede password equivalent (Bug#31075).
" +.*\\)"
"\\(?:" (regexp-opt password-word-equivalents) "\\|Response\\)"
"\\(?:\\(?:, try\\)? *again\\| (empty for no passphrase)\\| (again)\\)?"
;; "[[:alpha:]]" used to be "for", which fails to match non-English.
"\\(?: [[:alpha:]]+ .+\\)?[[:blank:]]*[::៖][[:blank:]]*\\'"))
'(compilation-error-regexp-alist
'(absoft ada aix ant bash borland python-tracebacks-and-caml cmake cmake-info comma msft edg-1 edg-2
epc ftnchek gradle-kotlin gradle-android iar ibm irix java javac jikes-file maven jikes-line
clang-include gcc-include ruby-Test::Unit gmake gnu lcc makepp mips-1 mips-2 omake oracle perl
php rxp shellcheck sparc-pascal-file sparc-pascal-line sparc-pascal-example sun sun-ada watcom
4bsd gcov-file gcov-header gcov-nomark gcov-called-line gcov-never-called perl--Pod::Checker perl--Test
perl--Test2 perl--Test::Harness weblint guile-file guile-line typescript-tsc-plain typescript-tsc-pretty
("^\\(?1:\\(?:[^\11\12 0-9]\\|[0-9]+[^\0120-9]\\)\\(?:[^\12 :]\\| [^\12/-]\\|:[^\12 ]\\)*?\\):(\\(?2:[0-9]+\\),\\(?3:[0-9]+\\))-(\\(?4:[0-9]+\\),\\(?5:[0-9]+\\))" 1 (2 . 4) (3 . 5))
("^ \\([^:\n\t]+\\):\\([0-9]+\\):\\([0-9]+\\): $" 1 2 3 0)))
'(completion-styles '(basic partial-completion emacs22 substring flex))
'(custom-file (expand-file-name "settings.el" user-emacs-directory))
'(dired-listing-switches "-alhv --group-directories-first")
'(dired-omit-files "^\\.\\|^#.*#$")
'(dirtrack-list '("^[^:]*:\\(?:�\\[[0-9]+m\\)*\\([^\\$# �]+\\)" 1))
'(eshell-prompt-function
(lambda ()
(concat (when (tramp-tramp-file-p default-directory)
(concat
(tramp-file-name-user
(tramp-dissect-file-name default-directory))
"@"
(tramp-file-name-host
(tramp-dissect-file-name default-directory))
" "))
(let ((dir (eshell/pwd)))
(if (string= dir (getenv "HOME")) "~"
(let ((dirname (file-name-nondirectory dir)))
(if (string= dirname "") "/" dirname))))
(if (= (user-uid) 0) " # " " $ "))))
'(eshell-visual-commands
'("vi" "screen" "top" "less" "more" "lynx" "ncftp" "pine" "tin"
"trn" "elm" "ssh" "mutt" "tmux" "htop"
"alsamixer" "watch" "elinks" "links" "nethack" "vim"
"cmus" "nmtui" "nmtui-connect" "nmtui-edit" "ncdu"
"telnet" "rlogin"))
'(eshell-visual-subcommands '(("vagrant" "ssh")))
'(find-ls-option '("-print0 | xargs -P4 -0 ls -ldN" . "-ldN"))
'(find-ls-subdir-switches "-ldN")
'(frame-title-format
'(:eval
(if (buffer-file-name)
(abbreviate-file-name (buffer-file-name))
"%b")))
'(ibuffer-formats
'((mark modified read-only " " (name 16 -1) " "
(size 6 -1 :right) " " (mode 16 16) " " filename)
(mark " " (name 16 -1) " " filename)))
'(ibuffer-never-show-predicates '("\\*magit-\\(diff\\|process\\):"))
'(ispell-extra-args '("--sug-mode=ultra"))
'(mouse-wheel-scroll-amount '(1 ((shift) . 5) ((control))))
'(minibuffer-prompt-properties
'(read-only t
cursor-intangible t
point-entered minibuffer-avoid-prompt
face minibuffer-prompt))
'(package-archives
'(("melpa" . "https://melpa.org/packages/")
("org" . "http://orgmode.org/elpa/")
("gnu" . "https://elpa.gnu.org/packages/")))
'(savehist-additional-variables '(search-ring
regexp-search-ring
kill-ring
comint-input-ring
kmacro-ring
sr-history-registry
file-name-history
tablist-name-filter))
'(tramp-default-proxies-alist
'(((regexp-quote (system-name)) nil nil)
(nil "\\`root\\'" "/ssh:%h:")
(".*" "\\`root\\'" "/ssh:%h:")))
'(uniquify-ignore-buffers-re "^\\*")
'(vc-git-diff-switches '("-w" "-U3"))
'(vc-ignore-dir-regexp
"\\(\\(\\`\\(?:[\\/][\\/][^\\/]+[\\/]\\|/\\(?:net\\|afs\\|\\.\\.\\.\\)/\\)\\'\\)\\|\\(\\`/[^/|:][^/|]*:\\)\\)\\|\\(\\`/[^/|:][^/|]*:\\)")
'(whitespace-action '(cleanup))
'(whitespace-style '(face trailing lines space-before-tab empty))
'(whitespace-global-modes '(not erc-mode ses-mode))
'(compilation-save-buffers-predicate
(lambda ()
(let ((proj (project-current)))
(and proj
(memq (current-buffer) (project-buffers proj)))))))
Now, pull in generated paths from site-paths.el
. Nix will generate this file
automatically for us & different Emacs variables will be set to their Nix
store derivation paths. Everything should work fine if you don’t have this
available, though. If you are in Emacs & already have the IDE installed you
can inspect this file by typing C-h C-l site-paths
. It will look similar to a
settings.el
file where each line corresponds to a customizable variable.
Unlike settings.el
, each entry is path in the Nix store & we verify it
exists before setting it.
(load "site-paths" t)
set-envs
is provided by set-defaults. We can use it like
custom-set-variables
, just it calls setenv
instead of setq
. All of
these entries correspond to environment variables that we want to always be
set in the Emacs process.
(set-envs
'("VISUAL" "emacsclient -a emacs")
'("EDITOR" "emacsclient -a emacs")
'("NODE_NO_READLINE" "1")
'("PAGER" "cat")
'("PS1" "\\W > ")
)
Fix broken Git on Windows.
(when (eq window-system 'w32)
(setenv "GIT_ASKPASS" "git-gui--askpass"))
This file allows users to override the above defaults. This will mean you can use custom as you normally would in vanilla Emacs.
(when custom-file
(load custom-file t))
use-package is an Emacs package by John Weigley allowing users to easily configure other Emacs packages. It’s quite useful & it will be used extensively in this project.
Now to get use-package
we will require package.el
& initialize it if
site-paths is not setup (meaning we’re outside the Nix expression). Because
site-paths should be available (unless you don’t have Nix), we can skip this
step. All of this is marked ‘eval-and-compile’ to make sure the compiler picks
it up on build phase.
So, there are basically two modes for using this configuration. One when packages are installed externally (through Nix) & another where they are installed internally. This is captured in the variable ‘needs-package-init’ which will be t when we want to use the builtin package.el & will be nil when we want to just assume everything is available.
(eval-and-compile
(setq needs-package-init
(and (not (locate-library "site-paths"))
(not (and
(boundp 'use-package-list--is-running)
use-package-list--is-running)))))
First handle using package.el
. We will do all of the work of bootstrapping
here including running package-initialize
, ensuring use-package
, & delight
are installed.
(when needs-package-init
(require 'package)
(package-initialize)
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(unless (package-installed-p 'delight)
(package-refresh-contents)
(package-install 'delight)))
Actually require use-package
,
(eval-and-compile
(require 'delight)
(require 'bind-key)
(require 'use-package))
Now let’s handle the case where all of the packages are already provided. Basically, we’ll prevent use-package from running ‘ensure’ on anything.
(eval-and-compile
(setq use-package-always-ensure needs-package-init)
(when (not needs-package-init)
(setq use-package-ensure-function 'ignore
package-enable-at-startup nil
package--init-file-ensured t)))
Using bind-key, setup some simple key bindings. None of these should overwrite
Emacs’ default keybindings. Also, they should only require vanilla Emacs to work
(non-vanilla Emacs key bindings should be put in their use-package
declaration). These are meant to all be as close to vanilla Emacs as possible. I
try to avoid extremely specific key binds here.
What is overwritten can be seen with M-x describe-personal-keybindings
. The
goal is to overwrite as little as possible. When it is necessary to overwrite
Emacs keybinds, documentation on why should be provided.
First we include a library that provides some nice helper functions that will be used as key bindings.
(require 'bauer)
(require 'files)
Define some helper functions.
(defun web-search (start end)
(interactive "r")
(let ((q (buffer-substring-no-properties start end)))
(browse-url (concat "http://www.google.com/search?btnI&q="
(url-hexify-string q)))))
Override browse-url to handle man protocol better.
(require 'browse-url)
(defun my-browse-url (url &rest args)
"Ask a WWW browser to load URL.
Prompt for a URL, defaulting to the URL at or before point.
Invokes a suitable browser function which does the actual job.
The variable `browse-url-browser-function' says which browser function to
use. If the URL is a mailto: URL, consult `browse-url-mailto-function'
first, if that exists.
The additional ARGS are passed to the browser function. See the doc
strings of the actual functions, starting with `browse-url-browser-function',
for information about the significance of ARGS (most of the functions
ignore it).
If ARGS are omitted, the default is to pass `browse-url-new-window-flag'
as ARGS."
(interactive (browse-url-interactive-arg "URL: "))
(unless (called-interactively-p 'interactive)
(setq args (or args (list browse-url-new-window-flag))))
(when (and url-handler-mode
(not (file-name-absolute-p url))
(not (string-match "\\`[a-z]+:" url)))
(setq url (expand-file-name url)))
(let ((process-environment (copy-sequence process-environment))
(function (or (and (string-match "\\`mailto:" url)
browse-url-mailto-function)
(and (string-match "\\`man:" url)
browse-url-man-function)
browse-url-browser-function))
;; Ensure that `default-directory' exists and is readable (b#6077).
(default-directory (or (unhandled-file-name-directory default-directory)
(expand-file-name "~/"))))
;; When connected to various displays, be careful to use the display of
;; the currently selected frame, rather than the original start display,
;; which may not even exist any more.
(if (stringp (frame-parameter nil 'display))
(setenv "DISPLAY" (frame-parameter nil 'display)))
(if (and (consp function)
(not (functionp function)))
;; The `function' can be an alist; look down it for first match
;; and apply the function (which might be a lambda).
(catch 'done
(dolist (bf function)
(when (string-match (car bf) url)
(apply (cdr bf) url args)
(throw 'done t)))
(error "No browse-url-browser-function matching URL %s"
url))
;; Unbound symbols go down this leg, since void-function from
;; apply is clearer than wrong-type-argument from dolist.
(apply function url args))))
(advice-add 'browse-url :override 'my-browse-url)
(defun my-escape-quotes (begin end)
(interactive
(if (region-active-p)
(list (region-beginning) (region-end))
(list (line-beginning-position) (line-end-position))))
(save-excursion
(save-restriction
(narrow-to-region begin end)
(goto-char (point-min))
(while (search-forward "\"" nil t)
(replace-match "\\\"" t t)))))
(defun my-unescape-quotes (begin end)
(interactive
(if (region-active-p)
(list (region-beginning) (region-end))
(list (line-beginning-position) (line-end-position))))
(save-excursion
(save-restriction
(narrow-to-region begin end)
(goto-char (point-min))
(while (search-forward "\\\"" nil t)
(replace-match "\"" t t)))))
(with-eval-after-load 'project (defun project-find-regexp-with-unique-buffer (orig-fun &rest args) "An advice function that gives project-find-regexp a unique buffer name" (require 'xref) (let ((xref-buffer-name (format "%s %s" xref-buffer-name (car args)))) (apply orig-fun args))) (advice-add 'project-find-regexp :around #'project-find-regexp-with-unique-buffer))
Now we will call bind-keys
. We give it keys to bind & what function to run
when those keys are pressed. Note on syntax of bind-keys: if you are unfamiliar
with how Emacs key binding works, you should read through this article. Some
things done below include:
- Make frame and window management a little bit easier. These are all used to
better navigations.
- Scale text size for different context. Defaults to 12pt fonts.
- Add evaluator keys, useful for executing lisp expressions.
- Add some read-only mode keybindings.
Key combination | Action |
---|---|
<s-return> | toggle-frame-fullscreen |
s-C-<left> | enlarge-window-horizontally |
s-C-<right> | shrink-window-horizontally |
s-C-<down> | shrink-window |
s-C-<up> | enlarge-window |
<S-s-up> | shrink-window |
<S-s-down> | enlarge-window |
<s-down> | windmove-down |
<s-up> | windmove-up |
<s-left> | windmove-left |
<s-right> | windmove-right |
C-x 5 3 | iconify-frame |
C-x 5 4 | toggle-frame-fullscreen |
<C-return> | other-window |
<C-M-return> | other-window |
s-o | other-window |
ESC o | other-window |
s-1 | other-frame |
C-c m b | eval-buffer |
C-c m e | eval-last-sexp |
C-c m i | eval-expression |
C-c m d | eval-defun |
C-c m n | eval-print-last-sexp |
C-c m r | eval-region |
C-c C-u | rename-uniquely |
C-c C-o | browse-url-at-point |
H-l | browse-url-at-point |
H-c | compile |
s-c | compile |
s-r | revert-buffer |
M-s d | find-grep-dired |
M-s F | find-grep |
M-s G | grep |
C-x r q | save-buffers-kill-terminal |
C-c C-<return> | delete-blank-lines |
C-<f10> | menu-bar-mode |
C-x M-g | browse-url-at-point |
M-s f | find-name-dired |
s-SPC | cycle-spacing |
C-c w w | whitespace-mode |
M-g l | goto-line |
<C-M-backspace> | backward-kill-sexp |
C-x v H | vc-region-history |
C-c SPC | just-one-space |
C-c f | flush-lines |
C-c o | customize-option |
C-c O | customize-group |
C-c F | customize-face |
C-c q | fill-region |
C-c s | replace-string |
C-c u | rename-uniquely |
C-c z | clean-buffer-list |
C-c = | count-matches |
C-c ; | comment-or-uncomment-region |
C-c [ | align-regexp |
s-/ | comment-or-uncomment-region |
M-s l | sort-lines |
M-s m | multi-occur |
M-s M | multi-occur-in-matching-buffers |
C-c i i | imenu |
s-v | scroll-down-command |
s-M-e | end-of-defun |
s-M-a | beginning-of-defun |
s-? | xref-find-references |
s-. | xref-find-definitions |
s-, | xref-go-back |
s-> | xref-go-forward |
s-% | query-replace |
C-c \ u | my-unescape-quotes |
C-c \ e | my-escape-quotes |
(mapc (lambda (x) (bind-key (car x) (intern (cadr x)))) keybinds)
Some bauer-specific custom keybindings.
(bind-keys
([f12] . next-error)
([f11] . previous-error)
([shift f12] . previous-error)
([mouse-9] . next-buffer)
([mouse-8] . previous-buffer))
(bind-keys
("C-c I" . bauer-find-config)
:prefix-map bauer-git
:prefix "s-g"
("l" . magit-clone)
:prefix-map bauer-help
:prefix "s-h"
("k" . describe-personal-keybindings)
("p" . ffap)
("m" . man)
("w" . woman))
Terminal mode key bindings follow. Scrolling in the term with the mouse should move text.
(unless window-system
(global-set-key (kbd "<mouse-4>") 'scroll-down-line)
(global-set-key (kbd "<mouse-5>") 'scroll-up-line))
macOS-specific bindings follow. Fullscreen handling should use the macOS feature, while by default it uses a custom Emacs stuff. In addition, drag and drop needs a special binding.
(when (eq window-system 'mac)
(defun mac-fullscreen ()
(interactive)
(let ((fullscreen (frame-parameter nil 'fullscreen)))
(if (memq fullscreen '(fullscreen fullboth))
(let ((fullscreen-restore (frame-parameter nil 'fullscreen-restore)))
(if (memq fullscreen-restore '(maximized fullheight fullwidth))
(set-frame-parameter nil 'fullscreen fullscreen-restore)
(set-frame-parameter nil 'fullscreen nil)))
(modify-frame-parameters
nil `((fullscreen . fullscreen) (fullscreen-restore . ,fullscreen))))))
(bind-key "C-x 5 4" 'mac-fullscreen)
(when (fboundp 'ns-drag-n-drop-as-text)
(global-set-key [M-s-drag-n-drop]
'ns-drag-n-drop-as-text)))
Add special quotes and arrows to ctrl x 8 keymap.
(bind-keys
:package iso-transl
:map iso-transl-ctl-x-8-map
("' /" . "′")
("\" /" . "″")
("\" (" . "“")
("\" )" . "”")
("' (" . "‘")
("' )" . "’")
("4 < -" . "←")
("4 - >" . "→")
("4 b" . "←")
("4 f" . "→")
("4 p" . "↑")
("4 n" . "↓")
("<down>" . "⇓")
("<S-down>" . "↓")
("<left>" . "⇐")
("<S-left>" . "←")
("<right>" . "⇒")
("<S-right>" . "→")
("<up>" . "⇑")
("<S-up>" . "↑")
("," . "…"))
Bind help map keys.
(bind-keys
:map help-map
("C-r" . woman)
("j" . woman)
("C-j" . man)
("C-s" . web-search))
More keys that have custom functions. TODO: move these above
(bind-keys
("C-x ~" . (lambda () (interactive) (find-file "~")))
("C-x /" . (lambda () (interactive) (find-file "/")))
("C-x 4 C-x ~" . (lambda () (interactive) (find-file-other-window "~")))
("C-x 4 C-x /" . (lambda () (interactive) (find-file-other-window "/")))
("C-x M-p" . (lambda () (interactive)
(save-excursion (other-window 1)
(quit-window))))
("C-M--" . (lambda () (interactive)
(update-font-size -1 t)))
("C-M-=" . (lambda () (interactive)
(update-font-size 1 t)))
("C-M-0" . (lambda () (interactive)
(update-font-size 12 nil))))
Delete current buffer
;;;###autoload (defun delete-file-and-buffer () "Kill the current buffer and deletes the file it is visiting." (interactive) (let ((filename (buffer-file-name))) (when filename (if (vc-backend filename) (vc-delete-file filename) (progn (delete-file filename) (message "Deleted file %s" filename) (kill-buffer))))))
Installer provides installation & upgrading functionality. You can upgrade the
IDE at any time by typing M-x upgrade
from within Emacs. You may have to
restart Emacs for the upgrade to take place. See installer.el for documentation.
(require 'installer nil t)
Each of these entries are use-package
calls that will both install & load
the package for us. The most important are listed first in “Essentials”.
“Built-in” Emacs packages are also configured. Next comes the “Programming
Language” modes. Finally, we list some miscellaneous modes.
This is an alphabetized listing of all Emacs packages needed by the IDE. To
resort, go to one of the package group headings & type C-c ^ a
.
These are the best & most useful modes available to us in Emacs world.
Automatically indent code as you type. Only enabled for Lisp currently.
(use-package aggressive-indent
:hook ((emacs-lisp-mode
inferior-emacs-lisp-mode
ielm-mode
lisp-mode
inferior-lisp-mode
isp-interaction-mode
slime-repl-mode) . aggressive-indent-mode))
A better version of ‘completing-read’ that is isn’t as heavy duty as company.
(use-package orderless
:custom
(completion-styles '(substring orderless basic))
(completion-category-defaults nil)
(completion-category-overrides '((file (styles basic partial-completion)))))
(use-package vertico
:hook (minibuffer-setup . cursor-intangible-mode)
:demand
:config
(vertico-mode)
(defun crm-indicator (args)
(cons (format "[CRM%s] %s"
(replace-regexp-in-string
"\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
crm-separator)
(car args))
(cdr args)))
(advice-add #'completing-read-multiple :filter-args #'crm-indicator)
)
(use-package corfu
:demand
:custom
(corfu-cycle t) ;; Enable cycling for `corfu-next/previous'
(corfu-preselect 'prompt) ;; Always preselect the prompt
:bind
(:map corfu-map
("TAB" . corfu-next)
([tab] . corfu-next)
("S-TAB" . corfu-previous)
([backtab] . corfu-previous))
:config
(global-corfu-mode))
(use-package consult
;; Replace bindings. Lazily loaded due by `use-package'.
:bind (;; C-c bindings (mode-specific-map)
("C-c M-x" . consult-mode-command)
("C-c h" . consult-history)
("C-c k" . consult-kmacro)
;; ("C-c m" . consult-man)
("C-c i" . consult-info)
([remap Info-search] . consult-info)
;; C-x bindings (ctl-x-map)
([remap repeat-complex-command] . consult-complex-command)
([remap switch-to-buffer] . consult-buffer)
([remap switch-to-buffer-other-window] . consult-buffer-other-window)
([remap switch-to-buffer-other-frame] . consult-buffer-other-frame)
([remap bookmark-jump] . consult-bookmark)
([remap project-switch-to-buffer] . consult-project-buffer)
;; Custom M-# bindings for fast register access
("M-#" . consult-register-load)
("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
("C-M-#" . consult-register)
;; Other custom bindings
([remap yank-pop] . consult-yank-pop)
;; M-g bindings (goto-map)
("M-g e" . consult-compile-error)
("M-g f" . consult-flymake) ;; Alternative: consult-flycheck
([remap goto-line] . consult-goto-line) ;; orig. goto-line
("M-g M-g" . consult-goto-line) ;; orig. goto-line
("M-g o" . consult-outline) ;; Alternative: consult-org-heading
("M-g m" . consult-mark)
("M-g k" . consult-global-mark)
("M-g i" . consult-imenu)
("M-g I" . consult-imenu-multi)
;; M-s bindings (search-map)
("M-s d" . consult-find)
("M-s D" . consult-locate)
("M-s g" . consult-grep)
("M-s G" . consult-git-grep)
("M-s r" . consult-ripgrep)
("M-s l" . consult-line)
("M-s L" . consult-line-multi)
("M-s k" . consult-keep-lines)
("M-s u" . consult-focus-lines)
;; Isearch integration
("M-s e" . consult-isearch-history)
:map isearch-mode-map
("M-e" . consult-isearch-history) ;; orig. isearch-edit-string
("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string
("M-s l" . consult-line) ;; needed by consult-line to detect isearch
("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch
;; Minibuffer history
:map minibuffer-local-map
("M-s" . consult-history) ;; orig. next-matching-history-element
("M-r" . consult-history)) ;; orig. previous-matching-history-element
;; Enable automatic preview at point in the *Completions* buffer. This is
;; relevant when you use the default completion UI.
:hook (completion-list-mode . consult-preview-at-point-mode)
;; The :init configuration is always executed (Not lazy)
:init
(setq register-preview-delay 0.5
register-preview-function #'consult-register-format)
(advice-add #'register-preview :override #'consult-register-window)
:config
(consult-customize
consult-theme :preview-key '(:debounce 0.2 any)
consult-ripgrep consult-git-grep consult-grep
consult-bookmark consult-recent-file consult-xref
consult--source-bookmark consult--source-file-register
consult--source-recent-file consult--source-project-recent-file
:preview-key '(:debounce 0.4 any))
)
(use-package marginalia
:demand
;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding
;; available in the *Completions* buffer, add it to the
;; `completion-list-mode-map'.
:bind (:map minibuffer-local-map
("M-A" . marginalia-cycle))
:config
;; Marginalia must be activated in the :init section of use-package such that
;; the mode gets enabled right away. Note that this forces loading the
;; package.
(marginalia-mode))
Envrc is able to execute a project’s .envrc file for you automatically. Unfortunately, it doesn’t support asynchronous updates yet, so this can occasionally block Emacs.
(use-package inheritenv)
;; envrc must come late in the init.el file so add-hook adds it first
;; in `find-file-hook'.
(use-package envrc
:ensure nil
:bind (
:package project
:map project-prefix-map
("," . envrc-reload))
:custom (envrc-none-lighter nil)
:demand t
:config
(envrc-global-mode))
This mode will try to detect your indentation.
(use-package dtrt-indent
:delight
:custom (dtrt-indent-verbosity 0)
:hook (prog-mode . dtrt-indent-mode))
Emacs shell provides a shell written in Elisp. Run eshell by typing C-c e
or M-x eshell
.
(use-package eshell
:bind (("C-c M-t" . eshell)
("C-c x" . eshell)
("C-c e" . eshell))
:hook (;; (eshell-first-time-mode-hook . eshell-read-history)
(eshell-first-time-mode-hook . (lambda () (add-hook 'eshell-expand-input-functions 'eshell-spawn-external-command))))
:preface
(defvar eshell-isearch-map
(let ((map (copy-keymap isearch-mode-map)))
(define-key map [(control ?m)] 'eshell-isearch-return)
(define-key map [return] 'eshell-isearch-return)
(define-key map [(control ?r)] 'eshell-isearch-repeat-backward)
(define-key map [(control ?s)] 'eshell-isearch-repeat-forward)
(define-key map [(control ?g)] 'eshell-isearch-abort)
(define-key map [backspace] 'eshell-isearch-delete-char)
(define-key map [delete] 'eshell-isearch-delete-char)
map)
"Keymap used in isearch in Eshell.")
(defun eshell-spawn-external-command (beg end)
"Parse and expand any history references in current input."
(save-excursion
(goto-char end)
(when (looking-back "&!" beg)
(delete-region (match-beginning 0) (match-end 0))
(goto-char beg)
(insert "spawn ")))))
Setup eldoc integration.
(use-package esh-help
:preface
(autoload 'esh-help-eldoc-command "esh-help")
(defun esh-help-turn-on ()
(interactive)
(setq-local eldoc-documentation-function
'esh-help-eldoc-command)
(setq eldoc-documentation-function
'esh-help-eldoc-command)
(eldoc-mode 1))
:hook (eshell-mode . esh-help-turn-on))
(use-package em-dired
:preface
(autoload 'em-dired-new "em-dired")
:ensure nil
:bind (:package dired
:map dired-mode-map
("e" . em-dired))
:hook (eshell-mode . em-dired-mode)
:init
(advice-add 'eshell :before 'em-dired-new))
Gnus is an infamous email client & news reader.
(use-package gnus
:hook ((dired-mode . turn-on-gnus-dired-mode)))
Intelligently adjust Emacs garbage collection size.
(use-package gcmh
:delight
:demand
:config
(gcmh-mode 1)
:custom
(gcmh-idle-delay 'auto)
(gcmh-auto-idle-delay-factor 10)
(gcmh-high-cons-threshold (* 16 1024 1024)))
Magit is a Git porcelain for Emacs. All of the features from the Git command line are available in an intuitive Emacs buffer.
(use-package magit
:preface
(autoload 'magit-toplevel "magit")
(autoload 'magit-read-string-ns "magit")
(autoload 'magit-get "magit")
;; (autoload 'magit-define-popup-action "magit")
(autoload 'magit-remote-arguments "magit")
(defun magit-dired-other-window ()
(interactive)
(dired-other-window (magit-toplevel)))
;; :hook ((git-commit-mode . git-commit-save-message)
;; (git-commit-mode . turn-on-auto-fill))
:custom (magit-blame-disable-modes '(fci-mode view-mode yascroll-bar-mode))
:custom (magit-process-find-password-functions '(magit-process-password-auth-source))
:custom (magit-process-password-prompt-regexps '(
"^\\(Enter \\)?[Pp]assphrase\\( for \\(RSA \\)?key '.*'\\)?: ?$"
"^\\(Enter \\)?[Pp]assword\\( for '?\\(https?://\\)?\\(?99:[^']*\\)'?\\)?: ?$"
"Please enter the passphrase for the ssh key"
"Please enter the passphrase to unlock the OpenPGP secret key"
"^.*'s password: ?$"
"^Yubikey for .*: ?$"
"^Enter PIN for .*: ?$"
"^\\[sudo\\] password for .*: ?$"))
:custom (magit-clone-set-remote.pushDefault t)
:custom (magit-log-auto-more t)
:custom (magit-remote-add-set-remote.pushDefault t)
:custom (magit-save-repository-buffers 'dontask)
:commands (magit-clone)
:if (locate-file "git" exec-path)
:bind (("C-x g" . magit-status)
("C-x p v" . magit-status)
("C-x p m" . magit-status)
("C-x G" . magit-dispatch)
:package magit
:map magit-mode-map
("C-o" . magit-dired-other-window))
:init (setq auto-revert-buffer-list-filter 'magit-auto-revert-repository-buffer-p))
Magit forge.
(use-package forge
:after magit)
(use-package git-timemachine
:commands (git-timemachine))
MMM mode lets you edit multiple languages within one buffer.
(use-package mmm-mode
:custom (mmm-submode-decoration-level 2)
:config
(use-package mmm-auto
:ensure nil))
Multiple cursors give you more cursors. It is bound to C->
& C-<
.
(use-package multiple-cursors
:bind
(("<C-S-down>" . mc/mark-next-like-this)
("<C-S-up>" . mc/mark-previous-like-this)
("C->" . mc/mark-next-like-this)
("C-<" . mc/mark-previous-like-this)
("M-<mouse-1>" . mc/add-cursor-on-click)
("C-c C-<" . mc/mark-all-like-this)
("C-!" . mc/mark-next-symbol-like-this)
("C-S-c C-S-c" . mc/edit-lines)))
Org mode is an impressive suite of text editing solutions. It gives you an outliner but also much much more.
(use-package org
:ensure org-contrib
:custom (org-latex-listings-langs
'((emacs-lisp "Lisp")
(lisp "Lisp")
(clojure "Lisp")
(c "C")
(cc "C++")
(fortran "fortran")
(perl "Perl")
(cperl "Perl")
(python "Python")
(ruby "Ruby")
(html "HTML")
(xml "XML")
(tex "TeX")
(latex "[LaTeX]TeX")
(shell-script "bash")
(gnuplot "Gnuplot")
(ocaml "Caml")
(caml "Caml")
(sql "SQL")
(sqlite "sql")
(makefile "make")
(R "r")
(nix "{}")
(nil "{}")
(yaml "{}")
(gitattributes "{}")
(gitignore "{}")
(shell "{}")
(gitconfig "{}")))
:custom (org-latex-default-packages-alist
'(("utf8" "inputenc" t
("pdflatex"))
("T1" "fontenc" t
("pdflatex"))
("" "graphicx" t nil)
("" "grffile" t nil)
("" "longtable" nil nil)
("" "wrapfig" nil nil)
("" "rotating" nil nil)
("normalem" "ulem" t nil)
("" "amsmath" t nil)
("" "textcomp" t nil)
("" "amssymb" t nil)
("" "capt-of" nil nil)
("" "hyperref" nil nil)
("" "parskip" nil nil)
("" "alltt" nil nil)
("" "upquote" nil nil)
("" "listings" nil nil)))
:custom (org-catch-invisible-edits 'smart)
:custom (org-confirm-babel-evaluate nil)
:custom (org-export-with-toc nil)
:custom (org-html-htmlize-output-type 'css)
:custom (org-log-done 'time)
:custom (org-special-ctrl-a/e t)
:custom (org-support-shift-select t)
:hook ((org-mode . (lambda ()
(add-hook 'completion-at-point-functions
'pcomplete-completions-at-point nil t)))
(org-mode . (lambda () (visual-line-mode -1)))
(org-mode . (lambda () (setq-local scroll-margin 3)))
(message-mode . turn-on-orgtbl)
;; (org-mode . (lambda ()
;; (autoload 'org-eldoc-documentation-function "org-eldoc")
;; (setq-local eldoc-documentation-function
;; 'org-eldoc-documentation-function)))
)
:config
;; (org-babel-do-load-languages 'org-babel-load-languages
;; '((sql . t)
;; (shell . t)))
)
(use-package org-download
:hook (dired-mode . org-download-enable))
(use-package org-present)
Smart hungry delete automatically delete lots of whitespace in a row.
(use-package smart-hungry-delete
:if (>= emacs-major-version 25)
:bind (:map prog-mode-map
("<backspace>" .
smart-hungry-delete-backward-char)
("C-d" .
smart-hungry-delete-forward-char))
:hook ((prog-mode .
smart-hungry-delete-default-prog-mode-hook)
(c-mode-common .
smart-hungry-delete-default-c-mode-common-hook)
(python-mode .
smart-hungry-delete-default-c-mode-common-hook)
(text-mode .
smart-hungry-delete-default-text-mode-hook)))
Sudo-edit lets you open a file using sudo (it actually goes through TRAMP to achieve this).
(use-package sudo-edit
:bind (("C-c C-r" . sudo-edit)))
This is the theme I use & it works well for this configuration. It is dark with high contrast. We will only enable it when we are running with GUI Emacs.
(use-package apropospriate-theme
:if window-system
:hook ((mac-effective-appearance-change . (lambda ()
(pcase (plist-get (mac-application-state) :appearance)
("NSAppearanceNameAqua" (load-theme 'apropospriate-light t))
("NSAppearanceNameDarkAqua" (load-theme 'apropospriate-dark t))))))
:init
(add-to-list 'custom-theme-load-path
(file-name-directory
(locate-library "apropospriate-theme")))
(if (eq (frame-parameter nil 'background-mode) 'light)
(load-theme 'apropospriate-light t)
(load-theme 'apropospriate-dark t)))
While apropospriate is the default, other themes can be used as well! For instance spacemacs-theme can be enabled:
(use-package spacemacs-common
:ensure spacemacs-theme
:if window-system
:init
(add-to-list 'custom-theme-load-path
(file-name-directory
(locate-library "spacemacs-theme-pkg")))
(load-theme 'spacemacs-dark t))
Try out packages without installing them.
(use-package try)
which-key will tell you what key bindings are available give a prefix. Test it
out by pressing C-x
& waiting a few seconds. Each key listed is bound to a
function.
(use-package which-key
:demand
:custom (which-key-lighter "")
:custom (which-key-idle-delay 0.4)
:custom (which-key-idle-secondary-delay 0.4)
:commands (which-key-mode)
:config (which-key-mode 1))
(use-package wgrep)
(use-package embark
:bind (("C-c a" . embark-act)))
(use-package embark-consult
;; comes bundled with Embark; no `:ensure t' necessary
:after (embark consult))
These are available automatically, so these use-package
blocks just
configure them.
Autorevert mode makes files update when they have changed on disk. Unfortunately this can have some issues in cases where Emacs uses the wrong file. Need to investigate how to fix this.
(use-package autorevert
:demand
:config (global-auto-revert-mode t))
Provides links to bugs listed in source code.
(use-package bug-reference
:custom (bug-reference-bug-regexp "\\(\\(?:[Ii]ssue \\|[Ff]ixe[ds] \\|[Rr]esolve[ds]? \\|[Cc]lose[ds]?\\|[Pp]\\(?:ull [Rr]equest\\|[Rr]\\) \\|(\\)#\\([0-9]+\\))?\\)")
:hook ((prog-mode . bug-reference-prog-mode)
(text-mode . bug-reference-mode)))
Base mode used for shell and terminal modes.
(defvar comint-input-ring-prefix ": [[:digit:]]+:[[:digit:]]+;"
"Possible prefix that may come before history elements. In
Zshell with extended_history, this is useful.")
(defvar ffap-url-at-point)
(use-package comint
:ensure nil
:hook ((comint-mode . (lambda () (setq-local ffap-url-at-point "/ssh:")))
(comint-mode . (lambda () (toggle-truncate-lines 1))))
:preface
(autoload 'comint-write-input-ring "comint")
(autoload 'comint-read-input-ring "comint")
(autoload 'comint-send-invisible "comint")
(defun turn-on-comint-history (history-file)
(setq comint-input-ring-file-name history-file)
(comint-read-input-ring 'silent))
(defun save-history ()
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(comint-write-input-ring))))
:config
(advice-add 'comint-read-input-ring
:override (lambda (&optional silent)
(cond ((or (null comint-input-ring-file-name)
(equal comint-input-ring-file-name ""))
nil)
((not (file-readable-p comint-input-ring-file-name))
(or silent
(message "Cannot read history file %s"
comint-input-ring-file-name)))
(t
(let* ((file comint-input-ring-file-name)
(count 0)
;; Some users set HISTSIZE or `comint-input-ring-size'
;; to huge numbers. Don't allocate a huge ring right
;; away; there might not be that much history.
(ring-size (min 1500 comint-input-ring-size))
(ring (make-ring ring-size)))
(with-temp-buffer
(insert-file-contents file)
;; Save restriction in case file is already visited...
;; Watch for those date stamps in history files!
(goto-char (point-max))
(let (start end history)
(while (and (< count comint-input-ring-size)
(re-search-backward comint-input-ring-separator
nil t)
(setq end (match-beginning 0)))
(setq start
(if (re-search-backward comint-input-ring-separator
nil t)
(progn
(when (looking-at (concat comint-input-ring-separator
comint-input-ring-prefix))
;; Skip zsh extended_history stamps
(re-search-forward comint-input-ring-prefix
nil t))
(match-end 0))
(progn
(goto-char (point-min))
(if (looking-at comint-input-ring-prefix)
(progn
(re-search-forward comint-input-ring-prefix
nil t)
(match-end 0))
(point-min)))))
(setq history (buffer-substring start end))
(goto-char start)
(when (and (not (string-match comint-input-history-ignore
history))
(or (null comint-input-ignoredups)
(ring-empty-p ring)
(not (string-equal (ring-ref ring 0)
history))))
(when (= count ring-size)
(ring-extend ring (min (- comint-input-ring-size ring-size)
ring-size))
(setq ring-size (ring-size ring)))
(ring-insert-at-beginning ring history)
(setq count (1+ count))))))
(setq comint-input-ring ring
comint-input-ring-index nil)))))))
(use-package comint-hyperlink
:ensure nil
:commands (comint-hyperlink-process-output)
:init (add-to-list 'comint-output-filter-functions 'comint-hyperlink-process-output))
(use-package compile
:bind (("C-c C-c" . compile)
:map compilation-mode-map
("o" . compile-goto-error))
:preface
(autoload 'ansi-color-process-output "ansi-color")
(defun show-compilation ()
(interactive)
(let ((compile-buf
(catch 'found
(dolist (buf (buffer-list))
(if (string-match "\\*compilation\\*"
(buffer-name buf))
(throw 'found buf))))))
(if compile-buf
(switch-to-buffer-other-window compile-buf)
(call-interactively 'compile)))))
(use-package conf-mode
:mode (("/\\.merlin\\'" . conf-mode)
("_oasis\\'" . conf-mode)
("_tags\\'" . conf-mode)
("_log\\'" . conf-mode)))
(use-package delsel
:demand
:config (delete-selection-mode t))
(use-package dired
:ensure nil
:preface
(autoload 'dired-get-filename "dired")
(autoload 'term-set-escape-char "term")
(defun dired-run-command (&optional filename)
"Run file at point in a new buffer."
(interactive)
(unless filename
(setq filename (expand-file-name
(dired-get-filename t t)
default-directory)))
(let ((buffer (make-term
(file-name-nondirectory filename)
filename))
(buffer-read-only nil))
(with-current-buffer buffer
;; (term-mode)
(term-char-mode)
(term-set-escape-char ?\C-x))
(set-process-sentinel
(get-buffer-process buffer)
(lambda (proc event)
(when (not (process-live-p proc))
(kill-buffer (process-buffer proc)))))
(switch-to-buffer buffer)))
:bind (("C-c J" . dired-double-jump)
:package dired
:map dired-mode-map
("C-c C-c" . compile)
("r" . term)
("M-@" . shell)
("M-*" . eshell)
("W" . browse-url-of-dired-file)
("@" . dired-run-command)))
(use-package dired-column
:ensure nil
:bind (:package dired
:map dired-mode-map
("o" . dired-column-find-file)))
(use-package dired-subtree
:bind (:package dired
:map dired-mode-map
("<tab>" . dired-subtree-toggle)
("TAB" . dired-subtree-toggle)
("<backtab>" . dired-subtree-cycle)))
(use-package dired-x
:ensure nil
:hook ((dired-mode . dired-omit-mode))
:bind (("s-\\" . dired-jump-other-window)
:package dired
:map dired-mode-map
(")" . dired-omit-mode)))
(use-package display-line-numbers
:if (>= emacs-major-version 26)
:hook ((prog-mode . display-line-numbers-mode)
(conf-mode . display-line-numbers-mode)))
Provides some info for the thing at the point.
(use-package eldoc
:hook ((emacs-lisp-mode . eldoc-mode)
(eval-expression-minibuffer-setup . eldoc-mode)
(lisp-mode-interactive-mode . eldoc-mode)
(typescript-mode . eldoc-mode)
(haskell-mode . eldoc-mode)
(python-mode . eldoc-mode)
(eshell-mode . eldoc-mode)
(org-mode . eldoc-mode)))
Setup these modes:
- electric-quote
- electric-indent
- electric-layout
(use-package electric
:if (>= emacs-major-version 25)
:hook ((prog-mode . electric-quote-local-mode)
(text-mode . electric-quote-local-mode)
(org-mode . electric-quote-local-mode)
(message-mode . electric-quote-local-mode)
(prog-mode . electric-indent-local-mode)
(prog-mode . electric-layout-mode)
(haskell-mode . (lambda () (electric-indent-local-mode -1)))
(nix-mode . (lambda () (electric-indent-local-mode -1)))))
Setup electric-pair-mode for prog-modes. Also disable it when smartparens is setup.
(use-package elec-pair
:if (>= emacs-major-version 25)
:hook
((prog-mode . electric-pair-local-mode)
(eval-expression-minibuffer-setup . electric-pair-local-mode)
(smartparens-mode . (lambda ()
(electric-pair-local-mode -1)))))
eww is enabled so we can open files in non-graphical environments.
(use-package eww
:if (and (not window-system)
(not (string-equal
(getenv "TERM_PROGRAM")
"Apple_Terminal")))
:commands (eww-browse-url eww-reload)
:config
(add-hook 'eww-mode-hook (lambda ()
(add-hook 'text-scale-mode-hook (lambda (&rest _) (eww-reload t)) nil t)
(add-hook 'window-size-change-functions (lambda (&rest _) (eww-reload t)) nil t)))
:init
(setq browse-url-browser-function 'eww-browse-url))
Make scripts executable automatically.
(use-package executable
:hook
((after-save .
executable-make-buffer-file-executable-if-script-p)))
(use-package ffap
:bind (([remap find-file] . find-file-at-point)
([remap find-file-other-window] . ffap-other-window)
([remap find-file-read-only] . ffap-read-only)
([remap find-alternate-file] . ffap-alternate-file)
([remap find-file-other-window] . ffap-other-window)
([remap find-file-other-frame] . ffap-other-frame)
([remap find-file-read-only-other-window] . ffap-read-only-other-window)
([remap find-file-read-only-other-frame] . ffap-read-only-other-frame)
([remap dired] . dired-at-point)
([remap dired-other-window] . ffap-dired-other-window)
([remap dired-other-frame] . ffap-dired-other-frame)
([remap list-directory] . ffap-list-directory))
:hook ((gnus-summary-mode . ffap-gnus-hook)
(gnus-article-mode . ffap-gnus-hook)
(vm-mode . ffap-ro-mode-hook)
(rmail-mode . ffap-ro-mode-hook)))
(use-package files
:ensure nil
;; :demand
;; :config (auto-save-visited-mode 1)
:preface
(defun find-file--line-number (orig-fun filename
&optional wildcards)
"Turn files like file.js:14:10 into file.js and going to line 14, col 10."
(save-match-data
(let* ((matched (string-match
"^\\(.*?\\):\\([0-9]+\\):?\\([0-9]*\\)$"
filename))
(line-number (and matched
(match-string 2 filename)
(string-to-number
(match-string 2 filename))))
(col-number (and matched
(match-string 3 filename)
(string-to-number (match-string 3 filename))))
(filename (if matched
(match-string 1 filename)
filename)))
(apply orig-fun (list filename wildcards))
(when line-number
;; goto-line is for interactive use
(goto-char (point-min))
(forward-line (1- line-number))
(when (> col-number 0)
(forward-char (1- col-number)))))))
:config
(advice-add 'find-file
:around #'find-file--line-number)
)
(use-package flyspell
:if (locate-file
(if (boundp 'ispell-program-name)
ispell-program-name "aspell")
exec-path)
:hook ((text-mode . flyspell-mode)
(prog-mode . flyspell-prog-mode)
(git-commit-mode . flyspell-mode))
:bind (:map flyspell-mode-map
("C-M-i" . nil)))
(use-package goto-addr
:hook (((prog-mode conf-mode) . goto-address-prog-mode)
((help-mode org-mode text-mode) . goto-address-mode)
(git-commit-mode . goto-address-mode)
(shell-mode . goto-address-mode)))
(use-package hl-line
:hook ((prog-mode . hl-line-mode)
(org-mode . hl-line-mode)
(dired-mode . hl-line-mode)))
(use-package ibuffer
:commands (ibuffer-do-sort-by-alphabetic)
:bind ([remap list-buffers] . ibuffer))
(use-package imenu-list
:bind ("s-g i" . imenu-list))
(use-package mouse
:ensure nil
:hook ((text-mode . context-menu-mode)
(prog-mode . context-menu-mode)))
(use-package paren
:hook ((prog-mode . show-paren-mode)
(smartparens-mode . (lambda () (show-paren-mode -1)))))
(use-package pp
:bind (([remap eval-expression] . pp-eval-expression))
;; :init
;;(global-unset-key (kbd "C-x C-e"))
:hook ((lisp-mode emacs-lisp-mode) . always-eval-sexp)
:preface
(defun always-eval-sexp ()
(define-key (current-local-map)
(kbd "C-x C-e")
'pp-eval-last-sexp)))
(use-package prog-mode
:ensure nil
:hook (;; (prog-mode . prettify-symbols-mode)
;; (lisp-mode . prettify-symbols-lisp)
;; (c-mode . prettify-symbols-c)
;; (c++-mode . prettify-symbols-c++)
;; ((js-mode js2-mode) . prettify-symbols-js)
(prog-mode . (lambda ()
(setq-local scroll-margin 3))))
:preface
(defun prettify-symbols-prog ()
(push '("<=" . ?≤) prettify-symbols-alist)
(push '(">=" . ?≥) prettify-symbols-alist))
(defun prettify-symbols-lisp ()
(push '("/=" . ?≠) prettify-symbols-alist)
(push '("sqrt" . ?√) prettify-symbols-alist)
(push '("not" . ?¬) prettify-symbols-alist)
(push '("and" . ?∧) prettify-symbols-alist)
(push '("or" . ?∨) prettify-symbols-alist))
(defun prettify-symbols-c ()
(push '("<=" . ?≤) prettify-symbols-alist)
(push '(">=" . ?≥) prettify-symbols-alist)
(push '("!=" . ?≠) prettify-symbols-alist)
(push '("&&" . ?∧) prettify-symbols-alist)
(push '("||" . ?∨) prettify-symbols-alist)
(push '(">>" . ?») prettify-symbols-alist)
(push '("<<" . ?«) prettify-symbols-alist))
(defun prettify-symbols-c++ ()
(push '("<=" . ?≤) prettify-symbols-alist)
(push '(">=" . ?≥) prettify-symbols-alist)
(push '("!=" . ?≠) prettify-symbols-alist)
(push '("&&" . ?∧) prettify-symbols-alist)
(push '("||" . ?∨) prettify-symbols-alist)
(push '(">>" . ?») prettify-symbols-alist)
(push '("<<" . ?«) prettify-symbols-alist)
(push '("->" . ?→) prettify-symbols-alist))
(defun prettify-symbols-js ()
(push '("function" . ?λ) prettify-symbols-alist)
(push '("=>" . ?⇒) prettify-symbols-alist)))
(use-package persistent-mode
:ensure nil)
(use-package savehist
:hook (after-init . savehist-mode))
(use-package saveplace
:if (>= emacs-major-version 25)
:hook (after-init . save-place-mode))
(use-package shell
:bind (("C-c C-s" . shell)
("H-s" . shell)
("M-@" . shell))
:hook ((shell-mode . ansi-color-for-comint-mode-on)
(shell-mode . dirtrack-mode)
;; (shell-mode . (lambda ()
;; (turn-on-comint-history (expand-file-name "sh-history"
;; user-emacs-directory))))
)
:config
(require 'tramp)
(defun shell-set-remote-shell-path-maybe (orig-fun &rest args)
(when (file-remote-p default-directory)
(if (file-exists-p (concat (file-remote-p default-directory) shell-file-name))
(set (make-local-variable 'explicit-shell-file-name) shell-file-name)
(if (file-exists-p (concat (file-remote-p default-directory) tramp-default-remote-shell))
(set (make-local-variable 'explicit-shell-file-name) tramp-default-remote-shell))))
(apply orig-fun args))
(advice-add 'shell :around 'shell-set-remote-shell-path-maybe))
(use-package simple
:ensure nil
:demand
:bind
(("C-`" . list-processes)
([remap upcase-word] . upcase-dwim)
([remap downcase-word] . downcase-dwim)
([remap capitalize-word] . capitalize-dwim)
:map minibuffer-local-map
("<escape>" . abort-recursive-edit)
("M-TAB" . previous-complete-history-element)
("<M-S-tab>" . next-complete-history-element))
:hook ((text-mode . visual-line-mode)
(text-mode . auto-fill-mode))
:config (column-number-mode))
(use-package subword
:hook ((java-mode . subword-mode)))
(use-package so-long
:if (>= emacs-major-version 27)
:demand
:config (global-so-long-mode 1))
(use-package term
:commands (term-char-mode)
:hook ((term-mode .
(lambda ()
(setq term-prompt-regexp
"^[^#$%>\n]*[#$%>] *")
(setq-local transient-mark-mode nil)
(auto-fill-mode -1))))
:preface
(autoload 'tramp-tramp-file-p "tramp")
(autoload 'tramp-dissect-file-name "tramp"))
(use-package text-mode
:no-require
:ensure nil
:hook ((text-mode . turn-on-auto-fill)))
(use-package timeclock)
(require 'timeclock)
(defvar timeclock-report-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [?g] #'timeclock-report-redo)
(define-key map [?q] #'timeclock-report-quit)
map)
"Keymap for `timeclock-report-mode'.")
(easy-menu-define timeclock-report-mode-menu timeclock-report-mode-map
"Ledger report menu"
'("Timeclock Report"
["Reload" timeclock-report-redo]
["Quit" timeclock-report-quit]
))
(define-derived-mode timeclock-report-mode special-mode "Timeclock-Report"
"A mode for viewing timeclock reports."
(hack-dir-local-variables-non-file-buffer)
(setq-local revert-buffer-function 'timeclock-report-redo))
(defvar timeclock-report-buffer-name "*Timeclock Report*")
(defun timeclock-report-quit ()
(interactive)
(unless (buffer-live-p (get-buffer timeclock-report-buffer-name))
(user-error "No timeclock report buffer"))
(quit-windows-on timeclock-report-buffer-name 'kill))
(defun timeclock-report-redo (&optional _ignore-auto _noconfirm)
(interactive)
(unless (derived-mode-p 'timeclock-report-mode)
(user-error "Not in a timeclock-report-mode buffer"))
(timeclock-reread-log)
(let ((cur-buf (current-buffer)))
(when (get-buffer timeclock-report-buffer-name)
(with-silent-modifications
(erase-buffer)
(timeclock-report-mode)
(timeclock-generate-report)
(newline))
(pop-to-buffer cur-buf))))
(defun timeclock-report ()
"Timeclock report."
(interactive)
(timeclock-reread-log)
(with-current-buffer
(pop-to-buffer (get-buffer-create timeclock-report-buffer-name) 'display-buffer-pop-up-window)
(with-silent-modifications
(erase-buffer)
(timeclock-report-mode)
(timeclock-generate-report)
(newline))))
(defun timeclock-ledger-report ()
"Timeclock ledger report."
(interactive)
(let* ((timeclock-file buffer-file-name)
(buffer-name (format "*Ledger Report [%s]*" timeclock-file))
(ledger-report-name "timelog")
(report-cmd (format "%s --color --force-color -f %s reg -D \"expr(commodity == 's')\""
(shell-quote-argument ledger-binary-path)
(shell-quote-argument timeclock-file))))
(with-current-buffer
(pop-to-buffer (get-buffer-create buffer-name))
(with-silent-modifications
(erase-buffer)
(ledger-report-mode)
(set (make-local-variable 'ledger-report-cmd) report-cmd)
(ledger-do-report report-cmd)))))
(defvar timeclock-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-i") 'timeclock-in)
(define-key map (kbd "C-c C-o") 'timeclock-out)
(define-key map (kbd "C-c C-c") 'timeclock-change)
(define-key map (kbd "C-c C-r") 'timeclock-reread-log)
(define-key map (kbd "C-c C-u") 'timeclock-update-mode-line)
(define-key map (kbd "C-c C-w") 'timeclock-when-to-leave-string)
(define-key map (kbd "C-c C-t") 'timeclock-report)
(define-key map (kbd "C-c C-e") 'timeclock-ledger-report)
;; Reset the `text-mode' override of this standard binding
(define-key map (kbd "C-M-i") 'completion-at-point)
map)
"Keymap for `timeclock-mode'.")
(easy-menu-define timeclock-mode-menu timeclock-mode-map
"Menu for `timeclock-mode'."
'("Timeclock"
["Clock in" timeclock-in]
["Clock out" timeclock-out]
["Change project" timeclock-change]
["Reread log" timeclock-reread-log]
["Update modeline" timeclock-update-mode-line]
["When to leave" timeclock-when-to-leave-string]
"---"
["Customize Timeclock Mode" (lambda () (interactive) (customize-group 'timeclock))]
"---"
["Report" timeclock-report]
["Ledger Report" timeclock-ledger-report]
))
(defgroup timeclock-faces nil "Timeclock mode highlighting" :group 'timeclock)
(defface timeclock-font-directive-face
`((t :inherit font-lock-preprocessor-face))
"Default face for timeclock"
:group 'timeclock-faces)
(defface timeclock-font-uncleared-face
`((t :inherit warning))
"Uncleared face fortimeclock"
:group 'timeclock-faces)
(defface timeclock-font-cleared-face
`((t :inherit shadow))
"Cleared face for timeclock"
:group 'timeclock-faces)
(defface timeclock-font-comment-face
`((t :inherit font-lock-comment-face))
"Face for Timeclock comments"
:group 'timeclock-faces)
(defface timeclock-font-date-face
`((t :inherit font-lock-keyword-face))
"Face for Timeclock dates"
:group 'timeclock-faces)
(defun timeclock-font-face-by-state (num faces)
"Choose one of two faces depending on a timeclock directive character.
NUM specifies a match group containing the character.
FACES has the form (CLEARED UNCLEARED).
Return CLEARED if the character specifies a cleared transaction,
UNCLEARED otherwise."
(if (member (match-string num) '("I" "O"))
(nth 0 faces)
(nth 1 faces)))
(defvar timeclock-font-lock-keywords
`((,(concat "^\\([IiOo]\\)"
"\\(?:[[:blank:]]+\\([^[:blank:]\n]+"
"\\(?:[[:blank:]]+[^[:blank:]\n]+\\)?\\)"
"\\(?:[[:blank:]]+\\(.*?\\)"
"\\(?:\t\\|[ \t]\\{2,\\}\\(.*?\\)"
"\\(?:\t\\|[ \t]\\{2,\\}\\(;.*\\)\\)?\\)?\\)?\\)?$")
(1 'timeclock-font-directive-face)
(2 'timeclock-font-date-face nil :lax)
(3 (timeclock-font-face-by-state 1 '(timeclock-font-cleared-face
timeclock-font-directive-face)) nil :lax)
(4 (timeclock-font-face-by-state 1 '(timeclock-font-cleared-face
timeclock-font-uncleared-face)) nil :lax)
(5 'timeclock-font-comment-face nil :lax)))
"Expressions to highlight in Timeclock mode.")
;;;###autoload
(define-derived-mode timeclock-mode text-mode "Timeclock"
"A major mode to timelog files."
(setq font-lock-defaults
'(timeclock-font-lock-keywords t nil nil nil))
(when (and (boundp 'display-time-mode) display-time-mode)
(timeclock-mode-line-display t)))
;;;###autoload
(add-to-list 'auto-mode-alist '("timelog\\'" . timeclock-mode))
(use-package url-handlers
:ensure nil
:demand
:config (url-handler-mode))
(use-package winner
:demand
:config (winner-mode))
(use-package which-func
:demand
:config (which-function-mode))
Show whitespace
(use-package whitespace
:hook (prog-mode . whitespace-mode))
Each use-package
declaration corresponds to major modes
in Emacs lingo.
Each language will at least one of these major modes as well as associated
packages (for completion, syntax checking, etc.)
Eglot is builtin to Emacs and can work with any language with an LSP backend.
(use-package eglot
:config
:custom (eglot-ignored-server-capabilities '(:codeLensProvider :documentFormattingProvider :documentRangeFormattingProvider :documentLinkProvider :colorProvider))
:custom (eglot-report-progress nil)
:custom (eglot-sync-connect nil)
)
(use-package csv-mode
:hook ((csv-mode . (lambda () (visual-line-mode -1)))
(csv-mode . (lambda () (auto-fill-mode -1)))
(csv-mode . (lambda () (toggle-truncate-lines 1)))))
(use-package elf-mode
:magic ("�ELF" . elf-mode))
(use-package ess-site
:ensure ess
:no-require
:interpreter (("Rscript" . r-mode)
("r" . r-mode))
:mode (("\\.sp\\'" . S-mode)
("/R/.*\\.q\\'" . R-mode)
("\\.[qsS]\\'" . S-mode)
("\\.ssc\\'" . S-mode)
("\\.SSC\\'" . S-mode)
("\\.[rR]\\'" . R-mode)
("\\.[rR]nw\\'" . Rnw-mode)
("\\.[sS]nw\\'" . Snw-mode)
("\\.[rR]profile\\'" . R-mode)
("NAMESPACE\\'" . R-mode)
("CITATION\\'" . R-mode)
("\\.omg\\'" . omegahat-mode)
("\\.hat\\'" . omegahat-mode)
("\\.lsp\\'" . XLS-mode)
("\\.do\\'" . STA-mode)
("\\.ado\\'" . STA-mode)
("\\.[Ss][Aa][Ss]\\'" . SAS-mode)
("\\.[Ss]t\\'" . S-transcript-mode)
("\\.Sout" . S-transcript-mode)
("\\.[Rr]out" . R-transcript-mode)
("\\.Rd\\'" . Rd-mode)
("\\.[Bb][Uu][Gg]\\'" . ess-bugs-mode)
("\\.[Bb][Oo][Gg]\\'" . ess-bugs-mode)
("\\.[Bb][Mm][Dd]\\'" . ess-bugs-mode)
("\\.[Jj][Aa][Gg]\\'" . ess-jags-mode)
("\\.[Jj][Oo][Gg]\\'" . ess-jags-mode)
("\\.[Jj][Mm][Dd]\\'" . ess-jags-mode)
))
(use-package git-modes)
(use-package haskell-mode
:custom (haskell-compile-cabal-build-command "cabal new-build")
:custom (haskell-hoogle-url "https://hoogle.haskell.org/?hoogle=%s")
:mode (("\\.cabal\\'" . haskell-cabal-mode))
:hook ((haskell-mode . subword-mode)
;; (haskell-mode . flyspell-prog-mode)
;; (haskell-mode . haskell-indentation-mode)
;; (haskell-mode . haskell-auto-insert-module-template)
;; (haskell-mode . haskell-decl-scan-mode)
;; (haskell-mode . turn-on-haskell-indent)
;; (haskell-mode . imenu-add-menubar-index)
;; (haskell-mode .
;; (lambda ()
;; (autoload 'haskell-doc-current-info
;; "haskell-doc")
;; (setq-local eldoc-documentation-function
;; 'haskell-doc-current-info)))
(haskell-mode . (lambda () (whitespace-mode -1))))
:functions xref-push-marker-stack
:commands (haskell-session-maybe haskell-mode-find-def haskell-ident-at-point
haskell-mode-handle-generic-loc)
:bind (:map haskell-mode-map
("C-c h" . haskell-hoogle)
("C-c C-." . haskell-navigate-imports)
("C-`" . haskell-interactive-bring)
("C-c `" . haskell-interactive-bring)
("C-c C-t" . haskell-process-do-type)
("C-c C-i" . haskell-process-do-info)
("C-c C-k" . haskell-interactive-mode-clear)
("C-c c" . haskell-process-cabal)
;; ("M-." . haskell-mode-jump-to-def)
:map haskell-cabal-mode-map
("C-`" . haskell-interactive-bring)
("C-c C-k" . haskell-interactive-mode-clear)
("C-c c" . haskell-process-cabal))
:init
(add-to-list 'completion-ignored-extensions ".hi")
;; (require 'haskell-compile)
)
(use-package typescript-ts-mode
:mode (("\\.ts\\'" . typescript-ts-mode)
("\\.tsx\\'" . typescript-ts-mode)))
(use-package js-mode
:ensure nil
:mode (("\\.es6\\'" . js-mode)
("\\.ejs\\'" . js-mode)
("\\.jshintrc$" . js-json-mode)
("\\.json_schema$" . js-json-mode))
:interpreter "node")
Auctex provides some helpful tools for working with LaTeX.
(use-package tex-mode
:custom (TeX-auto-save t)
:custom (TeX-auto-untabify t)
:custom (TeX-electric-escape t)
:custom (TeX-parse-self t)
:hook (TeX-mode . latex-electric-env-pair-mode))
(use-package elisp-mode
:ensure nil
:interpreter (("emacs" . emacs-lisp-mode)))
(use-package ielm
:bind ("C-c :" . ielm))
View macho binaries read-only. To view in Hexl-mode raw binaries, run M-x macho-mode to toggle then M-x hexl-mode.
(use-package macho-mode
:ensure nil
:magic (("\xFE\xED\xFA\xCE" . macho-mode)
("\xFE\xED\xFA\xCF" . macho-mode)
("\xCE\xFA\xED\xFE" . macho-mode)
("\xCF\xFA\xED\xFE" . macho-mode)))
(use-package markdown-mode
:mode (("README\\.md\\'" . gfm-mode)))
(use-package nix-mode)
(use-package nix-shell
:ensure nil)
(use-package nix-drv-mode
:ensure nil)
(use-package nix-update
:disabled
:bind (("C-. u" . nix-update-fetch)))
(use-package tuareg
:disabled
:config
;; Use Merlin if available
(when (require 'merlin nil t)
(defvar merlin-command)
(setq merlin-command 'opam)
(declare-function merlin-mode "merlin.el")
(when (functionp 'merlin-document)
(define-key tuareg-mode-map (kbd "\C-c\C-h") 'merlin-document))
;; Run Merlin if a .merlin file in the parent dirs is detected
(add-hook 'tuareg-mode-hook
(lambda()
(let ((fn (buffer-file-name)))
(if (and fn (locate-dominating-file fn ".merlin"))
(merlin-mode)))))))
(use-package proof-site
:ensure proofgeneral
:disabled
:custom (proof-auto-action-when-deactivating-scripting 'retract)
:custom (proof-autosend-enable nil)
:custom (proof-electric-terminator-enable t)
:custom (proof-fast-process-buffer nil)
:custom (proof-script-fly-past-comments t)
:custom (proof-shell-fiddle-frames nil)
:custom (proof-splash-enable nil)
:custom (proof-sticky-errors t)
:custom (proof-tidy-response t)
:demand
:if (not needs-package-init))
(use-package scala-mode
:interpreter ("scala" . scala-mode))
(use-package sh-script
:mode (("\\.*shellrc$" . sh-mode)
("\\.*shell_profile" . sh-mode)
("\\.zsh\\'" . sh-mode)))
(use-package texinfo)
(use-package terraform-mode)
(use-package yaml-mode)
These are all available in ./site-lisp. Eventually they should go into separate repositories.
(use-package nethack
:ensure nil)
(use-package pcomplete-extras
:ensure nil)
These should correspond to minor modes or helper functions. Some of them are more helpful than others but none are essential.
Most of these are available in MELPA.
(use-package browse-at-remote
:bind ("C-c g g" . browse-at-remote))
(use-package browse-kill-ring)
(use-package buffer-move
:bind
(("<M-S-up>" . buf-move-up)
("<M-S-down>" . buf-move-down)
("<M-S-left>" . buf-move-left)
("<M-S-right>" . buf-move-right)))
(use-package copy-as-format
:bind (("C-c w s" . copy-as-format-slack)
("C-c w g" . copy-as-format-github)))
(use-package daemons)
(use-package delight)
(use-package dired-rsync
:bind (:map dired-mode-map ("C-c C-r" . dired-rsync)))
(use-package gif-screencast
:bind ("<f9>". gif-screencast-start-or-stop))
(use-package ethan-wspace
:config
(global-ethan-wspace-mode 1))
(use-package git-attr-linguist
:ensure git-attr
:hook (find-file . git-attr-linguist))
(use-package htmlize :no-require)
(use-package ibuffer-vc
:commands (ibuffer-vc-set-filter-groups-by-vc-root)
:hook ((ibuffer . (lambda ()
(ibuffer-vc-set-filter-groups-by-vc-root)
(unless (eq ibuffer-sorting-mode 'alphabetic)
(ibuffer-do-sort-by-alphabetic))))))
(use-package keycast)
(use-package ledger-mode
:mode "\\.journal\\'")
(use-package mwim
:bind (([remap move-beginning-of-line]
. mwim-beginning-of-code-or-line)
([remap move-end-of-line]
. mwim-end-of-code-or-line)))
(use-package org-static-blog)
(use-package page-break-lines
:delight
:hook ((doc-mode
emacs-lisp-mode
outline-mode
prog-mode
haskell-mode
help-mode
magit-mode) . page-break-lines-mode))
(use-package pandoc-mode
:hook ((markdown-mode . pandoc-mode)
(pandoc-mode . pandoc-load-default-settings)))
(use-package rainbow-delimiters
:hook ((emacs-lisp-mode
inferior-emacs-lisp-mode
ielm-mode
lisp-mode
inferior-lisp-mode
lisp-interaction-mode
slime-repl-mode) . rainbow-delimiters-mode))
(use-package bats-mode)
(use-package rainbow-mode
:hook ((emacs-lisp-mode
inferior-emacs-lisp-mode
ielm-mode
lisp-mode
inferior-lisp-mode
lisp-interaction-mode
slime-repl-mode
less-css-mode
html-mode
css-mode) . rainbow-mode))
(use-package shrink-whitespace
:bind ("H-SPC" . shrink-whitespace))
(use-package string-inflection
:bind (("C-c r r" . string-inflection-all-cycle)
("C-c r c" . string-inflection-camelcase)
("C-c r l" . string-inflection-lower-camelcase)
("C-c r u" . string-inflection-underscore)
("C-c r k" . string-inflection-kebab-case)
("C-c r J" . string-inflection-java-style-cycle)))
(use-package systemd)
(use-package undo-tree
:demand
:custom (undo-tree-mode-lighter "")
:custom (undo-tree-history-directory-alist `((".*" . ,(expand-file-name "undo-tree/" user-emacs-directory))))
:config (global-undo-tree-mode))
(use-package unfill
:bind ([remap fill-paragraph] . unfill-toggle))
(use-package url-util
:ensure nil
:functions (url-hexify-string url-unhex-string region-active-p)
:preface
(defun func-region (start end func)
"Run a function over the region between START and END in current buffer."
(unless (region-active-p) (error "No active region."))
(save-excursion
(let ((text (delete-and-extract-region start end)))
(insert (funcall func text)))))
(defun url-encode (start end)
"URL encode the region between START and END in current buffer."
(interactive "r")
(func-region start end 'url-hexify-string))
(defalias 'url-escape 'url-encode)
(defun url-decode (start end)
"URL decode the region between START and END in current buffer."
(interactive "r")
(func-region start end 'url-unhex-string))
(defalias 'url-unescape 'url-decode))
(use-package xterm-color
:init
(defun advice-compilation-filter (f proc string)
(funcall f proc (xterm-color-filter string)))
(advice-add 'compilation-filter :around #'advice-compilation-filter)
)
Miscellaneous games
(use-package chess)
(use-package mines)
(use-package gnugo)
(use-package 2048-game)
(use-package dumb-jump
:init
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate))
(use-package visual-regexp)
(use-package persistent-scratch
:disabled
:demand
:config (persistent-scratch-setup-default))
(provide 'default)
All of these files live outside of Emacs but are necessary for a usable developer environment. They are basic shell profile and some git configuration scripts as well.
To use this, you must create a short ~/.profile file. Here is an example,
bootstrap="$HOME/.nix-profile/etc/profile"
[ -f $bootstrap ] && . $bootstrap
Set some Nix variables that never seem to get set correctly. There are a few bug
that break how this works. Also need to set some common exports that are needed.
Lots of common variables need to know about the .nix-profile
directory. We
leave the first MANPATH separator empty so it uses the default PATH lookup as
well. CVS_RSH is needed if your ever need to do cvs. HISTFILE sets where to
store the bash or zsh history. We set the history size to 10000 for good
measure. VISUAL should point to this editor.
Here we setup .profile
. First, setup exports. We leave the first part of
MANPATH empty so it resolves using the PATH-based method. Fallbacks are provided
just in case.
export PATH="$HOME/bin":"$HOME/.nix-profile/bin":$PREFIX/bin:@PATH@${PATH:+:$PATH} \
INFOPATH="$HOME/.nix-profile/share/info":@INFOPATH@${INFOPATH:+:$INFOPATH} \
MANPATH=:"$HOME/.nix-profile/share/man":@MANPATH@${MANPATH:+:$MANPATH} \
XDG_DATA_DIRS="$HOME/.nix-profile/share":@XDG_DATA_DIRS@${XDG_DATA_DIRS:+:$XDG_DATA_DIRS} \
TERMINFO_DIRS=@TERMINFO_DIRS@${TERMINFO_DIRS:+:$TERMINFO_DIRS}
if [ -z "${SSH_CONNECTION-}" ] && [ -z "${SSH_AUTH_SOCK-}" ] && command -v ssh-agent > /dev/null; then
eval $(ssh-agent) > /dev/null
fi
# taken from https://github.com/unixorn/zsh-quickstart-kit/blob/main/zsh/.zshrc
if [ "$(uname -s)" = Darwin ] && [ $(/usr/bin/ssh-add -l | grep -c "The agent has no identities.") -eq 1 ]; then
# check if Monterey or higher
# https://scriptingosx.com/2020/09/macos-version-big-sur-update/
if [[ $(sw_vers -buildVersion) > 21 ]]; then
# Load all ssh keys that have pass phrases stored in macOS keychain using new flags
/usr/bin/ssh-add --apple-load-keychain
else
/usr/bin/ssh-add -qA
fi
fi
if [ "$(uname -s)" = Darwin ]; then
export XDG_RUNTIME_DIR=$(getconf DARWIN_USER_TEMP_DIR)
fi
HISTSIZE=50000
SAVEHIST=10000
if [ -z "${TERM-}" ] || [ "$TERM" = dumb ] || [ "$TERM" = dumb-emacs-ansi ] || [ "$TERM" = eterm-color ]; then
if [ -n "${DISPLAY-}" ]; then
export VISUAL='emacsclient -a emacs -c'
else
export VISUAL='emacsclient -a emacs'
fi
elif [ -n "${PS1-}" ]; then
export VISUAL='emacsclient -a "emacs -nw" -t'
else
export VISUAL='emacsclient -a emacs'
fi
export EDITOR="$VISUAL"
if [ -n "${SSH_CONNECTION-}" ]; then
export PINENTRY_USER_DATA=USE_CURSES=1
fi
export LESS=FRSMi \
INPUTRC=@INPUTRC@ \
GPG_TTY=$TTY \
CVS_RSH=ssh
export SYSTEMD_LESS=$LESS
HISTCONTROL=ignoredups:erasedups:ignorespace
PROMPT_DIRTRIM=4
if [ -n "${BASH_VERSION-}" ]; then
if [[ $- == *i* ]]; then
stty -ixon
fi
shopt -s cdable_vars \
cdspell \
checkwinsize \
histappend \
lithist
shopt -s autocd \
checkjobs \
direxpand \
dirspell \
histverify \
histreedit \
no_empty_cmd_completion \
globstar \
2>/dev/null || true
if [ -n "${PS1-}" ] && [ "${TERM-}" != dumb ] && shopt -q progcomp 2>/dev/null && [ "${BASH_VERSINFO-0}" -gt 3 ]; then
. @bash_completion@/share/bash-completion/bash_completion
fi
if { [ "${TERM-}" = dumb-emacs-ansi ] || [ -n "${INSIDE_EMACS-}" ]; } && [ -n "${SSH_CONNECTION-}" ]; then
PROMPT_DIRTRIM=0
PS1='\[\e[34m\]/ssh:\u@\H:$PWD\[\e[0m\] \[\e[33m\]$\[\e[0m\] '
elif [ -n "${SSH_CONNECTION-}" ]; then
PROMPT_DIRTRIM=0
PS1='\[\e[34m\]ssh://\u@\H\[\e[36m\]$PWD\[\e[0m\] \[\e[33m\]$\[\e[0m\] '
else
PS1='\[\e[34m\]\u@\h\[\e[0m\]:\[\e[36m\]\w \[\e[33m\]\$\[\e[0m\] '
fi
if { [ "${TERM_PROGRAM-}" = iTerm.app ] || [ -n "${SSH_CONNECTION-}" ]; } && [ "$TERM" != dumb-emacs-ansi ] && [ "$TERM" != eterm-color ] && [ "$TERM" != dumb ]; then
PS1='\[\e]133;D;\$?\a\e]133;A\a\]'"$PS1"'\[\e]133;B\a\]'
fi
alias ...='../..' \
....='../../..' \
.....='../../../..' \
......='../../../../..'
# if command -v direnv > /dev/null; then
# eval "$(direnv hook bash)"
# fi
fi
Dircolors
if [ "$TERM" != dumb ] && [ -n "${SHELL-}" ]; then
if [ "$TERM" = dumb-emacs-ansi ]; then
eval "$(TERM=ansi dircolors)"
else
eval "$(dircolors)"
fi
fi
Then setup aliases.
alias rm='rm -Iv' \
mv='mv -iv' \
cp='cp -iv' \
ln='ln -iv' \
rmdir='rmdir -v' \
mkdir='mkdir -v' \
chown='chown --preserve-root -c' \
chmod='chmod --preserve-root -c' \
chgrp='chgrp --preserve-root -c' \
curl='curl --fail --globoff --location --proto-default https --retry 10 --retry-max-time 10 --no-clobber --parallel' \
wget='curl --remote-name-all --remote-time --fail --globoff --location --proto-default https --retry 10 --retry-max-time 10 --no-clobber --parallel' \
upgrade_bauer='git -C "$HOME/.local/share/bauer" pull && nix-env -if "$HOME/.local/share/bauer"' \
upgrade=upgrade_bauer \
df='df -HT' \
du='du -ch' \
gdb='gdb --quiet --args'
open() {
if command -v open > /dev/null; then
command open "$@"
elif command -v xdg-open > /dev/null; then
xdg-open "$@"
else
echo Can’t find open.
exit 1
fi
}
take() { mkdir -p $@ && cd ${@:$#} ; }
set -o noclobber -o notify
if [ "$TERM" != "dumb" ]; then
if [ "$TERM" != eterm-color ] && [ "${TERM_PROGRAM-}" != iTerm.app ]; then
alias l='ls -lh --hyperlink=auto --color=auto' \
ls='ls -Fh --hyperlink=auto --color=auto'
else
alias l='ls -lh --color=auto' \
ls='ls -Fh --color=auto'
fi
export CLICOLOR=1
alias grep='grep --color=auto' \
egrep='grep -E --color=auto' \
diff='diff --color=auto' \
rsync='rsync -v --progress --human-readable' \
dd='dd status=progress'
else
alias l='ls -lh' \
ls='ls -Fh' \
egrep='grep -E'
fi
Configure INSIDE_EMACS.
if [ "${TERM-}" = dumb ] && [ -n "${INSIDE_EMACS-}" ]; then
export TERM=dumb-emacs-ansi
if [ -z "${COLORTERM-}" ]; then
export COLORTERM=1
fi
fi
Last, we source the HOME profile to make sure it is used by zsh.
if [ -f "$HOME/.profile" ] && [ -z "${__BAUER_SOURCED_PROFILE-}" ]; then
__BAUER_SOURCED_PROFILE=1
# shellcheck source=/dev/null
. "$HOME/.profile"
fi
This is a profile for use with Zsh. It is closely based off of oh-my-zsh.
Setup ZSH profile. First, we just source the global profile.
# shellcheck source=/dev/null
. @out@/etc/profile
Handle dumb options.
case "$TERM" in
dumb)
setopt no_zle \
no_prompt_cr \
no_prompt_subst \
no_rcs
PS1='$ '
PROMPT='$ '
whence -w precmd >/dev/null && unfunction precmd
whence -w preexec >/dev/null && unfunction preexec
return
;;
eterm*)
setopt nopromptsp \
single_line_zle
;;
dumb-emacs-ansi)
setopt no_beep
;;
esac
Turn on ZSH-specific options.
setopt always_to_end \
auto_cd \
auto_name_dirs \
auto_pushd \
cdable_vars \
complete_in_word \
correct \
extended_history \
hist_ignore_space \
hist_reduce_blanks \
hist_save_no_dups \
hist_verify \
inc_append_history_time \
interactive_comments \
long_list_jobs \
multios \
no_list_beep \
no_flow_control \
prompt_subst \
pushd_minus \
transient_rprompt
alias -g ...='../..' \
....='../../..' \
.....='../../../..' \
......='../../../../..'
Load up site-functions in ZSH. Use XDG_DATA_DIRS variable. Then we use this to initialize completions.
if [ "$TERM" != dumb-emacs-ansi ] && [[ -o interactive ]]; then
# shellcheck disable=SC2154
for dir in "@completions@/share" ${(s.:.)XDG_DATA_DIRS-}; do
if [ -d "$dir/zsh/site-functions" ]; then
fpath+="$dir/zsh/site-functions"
fi
if [ -d "$dir/zsh/vendor-completions" ]; then
fpath+="$dir/zsh/vendor-completions"
fi
done
autoload -U compinit
if ! [ -f "$HOME/.zcompdump" ] || [ "$(@coreutils@/bin/date +%j)" != "$(@coreutils@/bin/date +%j -r "$HOME/.zcompdump")" ]; then
compinit -i -d "$HOME/.zcompdump"
else
compinit -i -d "$HOME/.zcompdump" -C
fi
fi
Bind to emacs…
bindkey -e
autoload -U tetris
zle -N tetris
autoload -U tetriscurses
zle -N tetriscurses
autoload -U bracketed-paste-magic
zle -N bracketed-paste bracketed-paste-magic
bindkey ' ' magic-space
bindkey ';5D' backward-word
bindkey ';5C' forward-word
bindkey '^[[1;9D' backward-word
bindkey '^[[1;9C' forward-word
autoload -U zmv
autoload -U zargs
autoload -U url-quote-magic
zle -N self-insert url-quote-magic
autoload -Uz up-line-or-beginning-search down-line-or-beginning-search
zle -N up-line-or-beginning-search
zle -N down-line-or-beginning-search
bindkey '^[[A' up-line-or-beginning-search
bindkey '^[OA' up-line-or-beginning-search
bindkey '^[[B' down-line-or-beginning-search
bindkey '^[OB' down-line-or-beginning-search
autoload -U edit-command-line
zle -N edit-command-line
bindkey "^X^E" edit-command-line
bindkey "^[m" copy-prev-shell-word
bindkey '^[w' kill-region
bindkey '^[[Z' reverse-menu-complete
bindkey "^S" history-incremental-search-forward
# unalias run-help
autoload -U run-help
alias help=run-help
bindkey "^[h" run-help
Zstyle completions.
zstyle ':completion:*' ignored-patterns '*~' '_*' '*\\\#*\\\#'
zstyle ':completion:*:*:*:users' ignored-patterns 'nixbld*' '_*' 'systemd-*' \
apache avahi daemon dbus messagebus nobody nginx nscd polkituser rpc sshd
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' insert-tab pending
zstyle ':completion:*' matcher-list 'm:{a-zA-Z-_}={A-Za-z_-}' 'r:|=*' 'l:|=* r:|=*'
zstyle ':completion:*' special-dirs yes
zstyle ':completion:*' squeeze-slashes yes
zstyle ':completion:*' expand yes
zstyle ':completion:*:match:*' original only
zstyle ':completion:*:approximate:*' max-errors 1 numeric
zstyle ':completion:*:*:*:*:*' menu select
zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;34=0=01'
zstyle ':completion:*:history-words' list no
zstyle ':completion:*:history-words' remove-all-dups yes
zstyle ':completion:*:history-words' stop yes
zstyle ':completion:*:rm:*' file-patterns '*:all-files'
zstyle ':completion::complete:*' cache-path "$HOME/.zcompcache"
zstyle ':completion::complete:*' use-cache yes
zstyle '*' single-ignored show
zstyle -e ':completion:*:(ssh|scp|sftp|rsh|rsync):hosts' hosts 'reply=(${=${${(f)"$(cat {/etc/ssh_,~/.ssh/known_}hosts(|2)(N) /dev/null)"}%%[# ]*}//,/ })'
Turn on prompt with colors.
PROMPT=
if [ -n "${SSH_CONNECTION-}" ]; then
PROMPT+='%F{blue}'
if [ "$TERM" = dumb-emacs-ansi ]; then
PROMPT+='/ssh:'
else
PROMPT+='ssh://'
fi
PROMPT+='%n@'
echo "$SSH_CONNECTION" | read client_ip client_port server_ip server_port
if [[ "$server_ip" =~ fe80::* ]]; then
server_ip="$(echo "$server_ip" | sed 's,%[a-z0-9]*$,,')"
fi
if [[ "$client_ip" =~ fe80::* ]]; then
client_ip="$(echo "$client_ip" | sed 's,%[a-z0-9]*$,,')"
fi
# localhost
if [ "$server_ip" = 127.0.0.1 ] || [ "$server_ip" = ::1 ] || [ "$server_ip" = "$client_ip" ]; then
PROMPT+=localhost
elif [ "$TERM" = dumb-emacs-ansi ]; then
PROMPT+="${HOST-}"
else
PROMPT+=%M
fi
if [ "$server_port" -ne 22 ]; then
if [ "$TERM" = dumb-emacs-ansi ]; then
PROMPT+="#"
else
PROMPT+=":"
fi
PROMPT+="$server_port"
fi
unset client_ip client_port server_ip server_port hostname
if [ "$TERM" = dumb-emacs-ansi ]; then
PROMPT+=':%/%f'
else
PROMPT+='%F{cyan}%/%f'
fi
else
PROMPT+='%(!.%F{red}%B.%F{blue})%n%b%F{blue}@%m%f:%F{cyan}%4~%f'
fi
PROMPT+=' %(?..%F{red}[%?]%f )%(1j.%F{green}[%j]%f .)%F{yellow}%(!.#.$)%f '
unset RPS1
HISTFILE="$HOME/.zsh_history"
REPORTTIME=4
Vterm
vterm_cmd() {
local vterm_elisp=""
while [ $# -gt 0 ]; do
vterm_elisp+="$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")"
shift
done
printf '\e]51;E%s\e\\' "$vterm_elisp"
}
update_vterm_cwd () {
printf '\e]51;A%s\e\\' "$(whoami)@$(hostname):$(pwd)"
}
if [[ "$INSIDE_EMACS" = vterm ]]; then
alias clear='printf "\e]51;Evterm-clear-scrollback\e\\"; tput clear'
chpwd_functions+=(update_vterm_cwd)
update_vterm_cwd
fi
Update eterm cwds.
update_eterm_cwd () {
printf '\eAnSiTc %s\n' "$PWD"
printf '\eAnSiTh %s\n' "${HOST-}"
printf '\eAnSiTu %s\n' "${USER-$LOGNAME}"
}
if [ "$TERM" = "eterm-color" ]; then
chpwd_functions+=(update_eterm_cwd)
update_eterm_cwd
fi
Setup Apple Terminal so that CWD is shown.
update_apple_terminal_cwd () {
printf '\e]7;%s\a' "file://${HOST-}${PWD// /%20}"
}
if [ "${TERM_PROGRAM-}" = Apple_Terminal ] \
&& [ -z "${INSIDE_EMACS-}" ] \
&& [ -n "${TERM_SESSION_ID-}" ]; then
chpwd_functions+=(update_apple_terminal_cwd)
update_apple_terminal_cwd
fi
iTerm2
if { [ "${TERM_PROGRAM-}" = iTerm.app ] || [ -n "${SSH_CONNECTION-}" ]; } && [ "$TERM" != dumb-emacs-ansi ] && [ "$TERM" != eterm-color ] && [ "$TERM" != dumb ] && [[ -o interactive ]]; then
iterm2_before_cmd_executes() {
printf '\e]133;C;\r\a'
}
iterm2_print_state_data() {
printf '\e]1337;RemoteHost=%s@%s\a' "${USER-$LOGNAME}" "${HOST-}"
printf '\e]1337;CurrentDir=%s\a' "$PWD"
}
iterm2_after_cmd_executes() {
printf '\e]133;D;%s\a' "$?"
iterm2_print_state_data
}
PROMPT=$'%{\e]133;A\a%}'"$PROMPT"$'%{\e]133;B\a%}'
precmd_functions+=(iterm2_after_cmd_executes)
preexec_functions+=(iterm2_before_cmd_executes)
iterm2_print_state_data
printf '\e]1337;ShellIntegrationVersion=14;shell=zsh\a'
fi
direnv
# if command -v direnv > /dev/null; then
# eval "$(direnv hook zsh)"
# fi
This just sources everything in the /etc/profile.d
dir. PREFIX
can be
used to reference the Nix output dir.
PREFIX=@out@
This will source everything in /etc/profile.d
. We skip nix-daemon.sh
because
it is put in the global profile, and sets values incorrectly when we are in
single user mode. We need @out@ to come after $HOME so that it gets the last
word in these settings.
if [ -n "${BASH_VERSION-}" ]; then
shopt -s nullglob
fi
for i in "$HOME"/.nix-profile/etc/profile.d/*.sh @out@/etc/profile.d/*.sh; do
if [ -r "$i" ] && [ "$(@coreutils@/bin/basename "$i")" != nix-daemon.sh ]; then
# shellcheck source=/dev/null
. "$i"
fi
done
This file provides site-specific paths. However, it must be substituted in Nix
before we can actually run it in Emacs. To prevent Emacs from trying to run
this, I’ve set the syntax to text
.
(require 'set-defaults)
(require 'subr-x)
output-directory
points to the nix-profile directory created by Nix.
Ideally, this could point to a Nix store path, but the order of building
means that we don’t know this until too late.
(defvar output-directory
(expand-file-name ".nix-profile" (getenv "HOME")))
(defvar zsh-command
(expand-file-name "bin/zsh" output-directory))
Setup exec-path
.
(setq exec-path
(append `(,(expand-file-name "bin" output-directory)
"@bins@/bin")
(split-string "@PATH@" ":")
exec-path))
Setup man-path
.
(defvar man-path (append
`(""
,(expand-file-name "share/man" output-directory)
"@manpages@/share/man")
(split-string "@MANPATH@" ":")))
Setup history and stuff. This gets messy, but we need to make sure these are set otherwise zsh will default to 500 and not save at all!
(defun bauer-set-history-file (sym val)
"Setter for history-file."
(setenv "HISTFILE" (eval val))
(custom-set-default sym val))
(defcustom history-file (expand-file-name ".zsh_history" (getenv "HOME"))
"The history file to use."
:type 'string
:group 'bauer
:set 'bauer-set-history-file)
(defvar eshell-history-size)
(defvar comint-input-ring-size)
(defun bauer-set-history-size (sym val)
"Setter for history-size."
(setenv "HISTSIZE" (format "%i" val))
(setenv "SAVEHIST" (format "%i" val))
(setq eshell-history-size val
comint-input-ring-size val)
(custom-set-default sym val))
(defcustom history-size 50000
"The history size to use."
:type 'integer
:group 'bauer
:set 'bauer-set-history-size)
Set some more misc. vars.
(append-envs ":"
`("MANPATH" ,man-path))
(prepend-envs ":"
`("PATH" ,exec-path))
(set-envs
`("XDG_DATA_DIRS" ,(string-join `(,(expand-file-name "share"
output-directory)
"@XDG_DATA_DIRS@")
":"))
`("TERMINFO_DIRS" "@TERMINFO_DIRS@")
`("INFOPATH" ,(string-join `(,(expand-file-name "share/info"
output-directory)
"@INFOPATH@")
":")))
Set paths provided by Nix,
(defvar gnutls-program "gnutls")
(defvar pdf2dsc-command "pdf2dsc")
(defvar dvips-command "dvips")
(defvar dvipng-command "dvipng")
(defvar xetex-command "xetex")
(defvar xelatex-command "xelatex")
(defvar luatex-command "luatex")
(defvar makeinfo-command "makeinfo")
(defvar LaTeX-command "LaTeX")
(defvar pdftex-command "pdftex")
(defvar context-command "context")
(defvar bibtex-command "bibtex")
(defvar makeindex-command "makeindex")
(defvar dvipdfmx-command "dvipdfmx")
(defvar ag-executable "ag")
(defvar ripgrep-executable "ripgrep")
(defvar lacheck-command "lacheck")
(defvar chktex-command "chktex")
(defvar ps2pdf-command "ps2pdf")
(set-paths
'(company-cmake-executable "@cmake@/bin/cmake")
'(doc-view-dvipdf-program "@ghostscript@/bin/dvipdf")
'(calc-gnuplot-name "@gnuplot@/bin/gnuplot")
'(gnuplot-program "@gnuplot@/bin/gnuplot")
'(doc-view-ps2pdf-program "@ghostscript@/bin/ps2pdf")
'(epg-gpg-program "@gpg@/bin/gpg")
'(epg-gpgconf-program "@gpg@/bin/gpgconf")
'(epg-gpgsm-program "@gpg@/bin/gpgsm")
'(haskell-check-command "@hlint@/bin/hlint")
'(haskell-hoogle-command "@hoogle@/bin/hoogle")
'(haskell-hasktags-path "@hasktags@/bin/hasktags")
'(irony-cmake-executable "@cmake@/bin/cmake")
'(jka-compr-info-compress-program "@ncompress@/bin/compress")
'(jka-compr-info-uncompress-program "@ncompress@/bin/uncompress")
'(markdown-command "@markdown2@/bin/markdown2")
'(nethack-executable "@nethack@/bin/nethack")
'(org-pandoc-command "@pandoc@/bin/pandoc")
'(pandoc-binary "@pandoc@/bin/pandoc")
'(ripgrep-executable "@ripgrep@/bin/rg")
'(rtags-path "@rtags@/bin")
'(sql-ingres-program "@parallel@/bin/sql")
'(sql-interbase-program "@unixODBC@/bin/isql")
'(sql-ms-program "@freetds@/bin/osql")
'(sql-postgres-program "@freetds@/bin/osql")
'(sql-sqlite-program "@sqliteInteractive@/bin/sqlite3")
'(gnutls-program "@gnutls@/bin/gnutls-cli")
'(pdf2dsc-command "@ghostscript@/bin/pdf2dsc")
'(preview-gs-command "@texlive@/bin/rungs")
'(TeX-command "@texlive@/bin/tex")
'(LaTeX-command "@texlive@/bin/latex")
'(latex-run-command "@texlive@/bin/latex")
'(tex-run-command "@texlive@/bin/tex")
'(luatex-command "@texlive@/bin/luatex")
'(xetex-command "@texlive@/bin/xetex")
'(xelatex-command "@texlive@/bin/xelatex")
'(makeinfo-command "@texinfoInteractive@/bin/makeinfo")
'(pdftex-command "@texlive@/bin/pdftex")
'(context-command "@texlive@/bin/context")
'(bibtex-command "@texlive@/bin/bibtex")
'(dvipdfmx-command "@texlive@/bin/dvipdfmx")
'(makeindex-command "@texlive@/bin/makeindex")
'(chktex-command "@texlive@/bin/chktex")
'(lacheck-command "@texlive@/bin/lacheck")
'(dvipdfmx-command "@texlive@/bin/dvipdfmx")
'(dvips-command "@texlive@/bin/dvips")
'(dvipng-command "@texlive@/bin/dvipng")
'(ps2pdf-command "@ghostscript@/bin/ps2pdf")
'(ag-executable "@silver-searcher@/bin/ag")
'(gud-gdb-command-name "@gdb@/bin/gdb")
'(coq-prog-name "@coq@/bin/coqtop")
'(coq-dependency-analyzer "@coq@/bin/coqdep")
'(coq-compiler "@coq@/bin/coqc")
)
Set some defaults that depend on the path variables below,
(set-defaults
'(imap-ssl-program `(,(concat gnutls-program " --tofu -p %p %s")))
'(tls-program (concat gnutls-program " --tofu -p %p %h"))
'(preview-pdf2dsc-command
(concat pdf2dsc-command " %s.pdf %m/preview.dsc"))
'(preview-dvips-command
(concat dvips-command " -Pwww %d -o %m/preview.ps"))
'(preview-fast-dvips-command
(concat dvips-command " -Pwww %d -o %m/preview.ps"))
'(preview-dvipng-command
(concat dvipng-command
" -picky -noghostscript %d -o \"%m/prev%%03d.png\""))
'(TeX-engine-alist
`((xetex "XeTeX"
xetex-command
xelatex-command
xetex-command)
(luatex "LuaTeX" luatex-command
,(concat luatex-command " --jobname=%s")
luatex-command)))
'(TeX-command-list
`(("TeX"
,(concat "%(PDF)%(tex) %(file-line-error) "
"%(extraopts) %`%S%(PDFout)%(mode)%' %t")
TeX-run-TeX nil
(plain-tex-mode ams-tex-mode texinfo-mode)
:help "Run plain TeX")
("LaTeX" "%`%l%(mode)%' %t" TeX-run-TeX nil
(latex-mode doctex-mode)
:help "Run LaTeX")
("Makeinfo" ,(concat makeinfo-command
" %(extraopts) %t")
TeX-run-compile nil
(texinfo-mode)
:help "Run Makeinfo with Info output")
("Makeinfo HTML"
,(concat makeinfo-command
" %(extraopts) --html %t")
TeX-run-compile nil
(texinfo-mode)
:help "Run Makeinfo with HTML output")
("AmSTeX"
,(concat pdftex-command
" %(PDFout) %(extraopts) %`%S%(mode)%' %t")
TeX-run-TeX nil
(ams-tex-mode)
:help "Run AMSTeX")
("ConTeXt"
,(concat context-command
" --once --texutil %(extraopts) %(execopts)%t")
TeX-run-TeX nil
(context-mode)
:help "Run ConTeXt once")
("ConTeXt Full"
,(concat context-command
" %(extraopts) %(execopts)%t")
TeX-run-TeX nil
(context-mode)
:help "Run ConTeXt until completion")
("BibTeX" ,(concat bibtex-command " %s")
TeX-run-BibTeX nil t :help "Run BibTeX")
("Biber" "biber %s" TeX-run-Biber nil t
:help "Run Biber")
("View" "%V" TeX-run-discard-or-function t t
:help "Run Viewer")
("Print" "%p" TeX-run-command t t
:help "Print the file")
("Queue" "%q" TeX-run-background nil t
:help "View the printer queue"
:visible TeX-queue-command)
("File" ,(concat dvips-command " %d -o %f ")
TeX-run-dvips t t :help "Generate PostScript file")
("Dvips" ,(concat dvips-command " %d -o %f ")
TeX-run-dvips nil t
:help "Convert DVI file to PostScript")
("Dvipdfmx" ,(concat dvipdfmx-command " %d")
TeX-run-dvipdfmx nil t
:help "Convert DVI file to PDF with dvipdfmx")
("Ps2pdf" ,(concat ps2pdf-command " %f")
TeX-run-ps2pdf nil t
:help "Convert PostScript file to PDF")
("Index" ,(concat makeindex-command " %s")
TeX-run-index nil t
:help "Run makeindex to create index file")
("upMendex" "upmendex %s"
TeX-run-index t t
:help "Run mendex to create index file")
("Xindy" "xindy %s"
TeX-run-command nil t
:help "Run xindy to create index file")
("Check" ,(concat lacheck-command " %s")
TeX-run-compile nil
(latex-mode)
:help "Check LaTeX file for correctness")
("ChkTeX" ,(concat chktex-command " -v6 %s")
TeX-run-compile nil
(latex-mode)
:help "Check LaTeX file for common mistakes")
("Spell" "(TeX-ispell-document \"\")"
TeX-run-function nil t
:help "Spell-check the document")
("Clean" "TeX-clean"
TeX-run-function nil t
:help "Delete generated intermediate files")
("Clean All" "(TeX-clean t)" TeX-run-function nil t
:help
"Delete generated intermediate and output files")
("Other" "" TeX-run-command t t
:help "Run an arbitrary command")))
'(counsel-grep-base-command
(concat ripgrep-executable
" -i -M 120 --no-heading --line-number --color never '%s' %s"))
'(counsel-rg-base-command
(concat ripgrep-executable
" -i --no-heading --line-number %s ."))
'(counsel-ag-base-command
(concat ag-executable " --nocolor --nogroup %s"))
'(Info-directory-list '("@infopages@/share/info"))
'(tramp-remote-path
`(tramp-own-remote-path
"/run/current-system/sw/bin"
tramp-default-remote-path
"/bin"
"/usr/bin"
"/sbin"
"/usr/sbin"
"/usr/local/bin"
"/usr/local/sbin"
"/opt/bin"
"/opt/sbin"
,(expand-file-name "bin" output-directory)))
'(woman-manpath man-path)
'(Man-header-file-path
`("@sysheaders@/include"
"/usr/include"
"/usr/local/include"))
'(ffap-c-path
'("@sysheaders@/include"
"/usr/include"
"/usr/local/include"))
'(rng-schema-locating-files
`("schemas.xml"
"@schemas@"
,(expand-file-name
"schema/schemas.xml" data-directory)))
)
This is the bootstrap script that is mentioned above. We use it to install the IDE. It ensures Nix is installed as well as that the Git repo is available & up-to-date. Note, this script will automatically install Nix and Git for you if they are not already installed! Use it at your own risk.
Install Nix if it isn’t already.
{
set -e
echo This script will install Nix and Git
echo if they are not already installed.
if ! command -v nix-env >/dev/null 2>&1; then
case "$(uname)" in
Darwin) curl -L -s https://nixos.org/nix/install | sh -s - --darwin-use-unencrypted-nix-store-volume --daemon ;;
Linux) curl -L -s https://nixos.org/nix/install | sh -s - --daemon ;;
*) curl -L -s https://nixos.org/nix/install | sh -s ;;
esac
[ -f $HOME/.profile ] && . $HOME/.profile
PATH="$HOME/.nix-profile/bin${PATH-+:$PATH}" # in case above didn't work
fi
Check for Git & SSH. Install if it’s not there.
if ! command -v git >/dev/null 2>&1 || \
{ [ "$(uname)" = Darwin ] && \
[ "$(command -v git)" = /usr/bin/git ] &&
! xcode-select -p >/dev/null 2>&1; }; then
nix-env -iA nixpkgs.git 2>/dev/null || nix-env -iA nixos.git || nix --experimental-features 'nix-command flakes' profile install nixpkgs#git || nix-env -iA git -f https://nixos.org/channels/nixos-unstable/nixexprs.tar.xz
fi
if ! command -v ssh >/dev/null 2>&1; then
nix-env -iA nixpkgs.openssh 2>/dev/null || nix-env -iA nixos.openssh || nix --experimental-features 'nix-command flakes' profile install nixpkgs#openssh || nix-env -iA openssh -f https://nixos.org/channels/nixos-unstable/nixexprs.tar.xz
fi
If we are in a Git repo already, we’ll pull to get latest updates.
if [ -d .git ]; then
git pull origin master || true
fi
If we can’t find default.nix then we’ll clone from GitHub. This will be stored in
~/.local/share/bauer
.
if ! [ -f default.nix ]; then
repo_dir=$HOME/.local/share/bauer
mkdir -p $(dirname $repo_dir)
if ! [ -d $repo_dir/.git ]; then
git clone https://github.com/matthewbauer/bauer $repo_dir
else
git -C $repo_dir pull
fi
cd $repo_dir
fi
Pull private data, which may contain some binary caches we can use.
if [ -z "${GIST_ID-}" ] && [ -n "$1" ] && (echo "$1" | grep -q "^[0-9a-f]\{5,40\}$"); then
GIST_ID="$1"
fi
if [ -n "${GIST_ID-}" ]; then
URL=ssh://[email protected]/"$GIST_ID".git
fi
if [ -z "${URL-}" ]; then
case "${USER-}" in
mbauer|matthewbauer) URL=ssh://[email protected]/matthewbauer/dotfiles.git ;;
esac
fi
if [ -n "${URL-}" ]; then
echo Found Gist commit "$1", cloning now.
./gist-unpack.sh "$URL"
fi
Install our new derivation.
nix-env -if . || nix --experimental-features 'nix-command flakes' profile install
Source the profile.
if ! [ -f "$HOME/.profile" ] || ! grep -q '\(source\|\.\) "\?$HOME/.nix-profile/etc/profile"\?' "$HOME/.profile"; then
echo '[ -f "$HOME/.nix-profile/etc/profile" ] && . "$HOME/.nix-profile/etc/profile"' >> "$HOME/.profile"
fi
if ! [ -f "$HOME/.zshenv" ] || ! grep -q '\(source\|\.\) "\?$HOME/.nix-profile/etc/zshrc"\?' "$HOME/.zshenv"; then
echo '[ -f "$HOME/.nix-profile/etc/zshrc" ] && source "$HOME/.nix-profile/etc/zshrc"' >> "$HOME/.zshenv"
fi
if ! [ -f "$HOME/.bashrc" ] || ! grep -q '\(source\|\.\) "\?$HOME/.nix-profile/etc/profile"\?' "$HOME/.bashrc"; then
echo '[ -f "$HOME/.nix-profile/etc/profile" ] && source "$HOME/.nix-profile/etc/profile"' >> "$HOME/.bashrc"
fi
if ! [ -f "$HOME/.bash_profile" ] || ! grep -q '\(source\|\.\) "\?$HOME/.nix-profile/etc/profile"\?' "$HOME/.bash_profile"; then
echo '[ -f "$HOME/.nix-profile/etc/profile" ] && source "$HOME/.nix-profile/etc/profile"' >> "$HOME/.bash_profile"
fi
echo To use bauer correctly, you must first source the profile.
echo
echo To do this, just run:
echo $ . $HOME/.nix-profile/etc/profile
if [ -n "${ZSH_NAME-}" ]; then
echo $ . $HOME/.nix-profile/etc/zshrc
fi
echo From you command line
echo You can also run either emacs or zsh to launch the environment
}
Cross-platform script to execute the app. Uses open
on macOS to get the
graphical version of Emacs.
case $(uname) in
Darwin)
open @emacs@/Applications/Emacs.app
;;
*)
@emacs@/bin/emacs
;;
esac
No tests currently exists, but some things are important to keep in mind. Some high level goals that should be verified:
- Keep closure size under 1GB (currently 800MB)
- Don’t assume users’ language, locales, time zone, etc.
- Don’t hard code any personal information
- Everything should run on Linux, and macOS.
- Nix usage is optional (see Withou Nix above).
The editor and environment should be general purpose and portable
Here we being the building process. There are a couple of stages to this not normally seen in configurations. The first part is the ‘default.nix’ which will tangle the README.org file. This should be the only file that has to live outside of this ORG document. This is meant to be very minimal so that once we tangle README.org, we can get a working .nix file to import from using Nix’s import from derivation.
The function header defines some arguments you can pass. It will use the latest nixpkgs-unstable from nixos.org. If you want to change this, you can pass in your own pkgs variable.
The small argument is a bit of a hack. Nix will only recognize args that have been explicitly listed so we cannot rely on the ‘…’ syntax to pick up random args.
# -*- mode: nix; coding: utf-8; -*-
{ nixpkgs-url ?
"https://nixos.org/channels/nixpkgs-unstable/nixexprs.tar.xz"
, system ? builtins.currentSystem
, crossSystem ? null
, config ? {}
, overlays ? []
, pkgs ? import (builtins.fetchTarball nixpkgs-url) {
inherit crossSystem system config overlays;
}
, small ? true
, ...
} @ args:
Now let’s tangle README.org… This uses ORG’s babel functionality to generate a .nix file. The .nix file is renamed to default.nix to replace this script.
let
ensure = f: n: if builtins.pathExists f then f
else builtins.fetchurl
"https://matthewbauer.us/bauer/${n}";
in import (pkgs.runCommand "README" {
buildInputs = with pkgs; [ emacs git ];
} (''
install -D ${ensure ./README.org "README.org"} \
$out/README.org
cd $out
'' + pkgs.lib.optionalString (builtins.pathExists ./site-lisp) ''
cp -r ${./site-lisp} site-lisp
'' + ''
emacs --batch --quick \
-l ob-tangle \
--eval "(org-babel-tangle-file \"README.org\")" > /dev/null
cp bauer.nix default.nix
'')) { inherit ensure pkgs small system; }
Here we have the actual Nix file that will build our Emacs configuration. Again we have some options that can be provided…
# -*- mode: nix; coding: utf-8; -*-
{ system ? builtins.currentSystem
, crossSystem ? null
, config ? {}
, overlays ? []
, pkgs ? import <nixpkgs> { inherit crossSystem system config overlays; }
, evalPkgs ? pkgs
, ensure ? f: n: f
, small ? pkgs.config.bauer.small or true
, emacs-overlay ? { overlay = import (builtins.fetchTarball "https://github.com/nix-community/emacs-overlay/archive/master.tar.gz"); }
, ... }: let
inherit (pkgs) stdenv lib runCommand buildEnv config writeText;
inherit (stdenv) hostPlatform;
big = !small;
allowUnfree = config.allowUnfree or false;
By default, you will be building the “small” version which is about 500MB. The normal version has a huge closure totalling in at over 5GB. This is extremely useful for integrations with developer tools and manuals, but also inconvenient when wanting to start running for the first time. If you want to enable the big version, you can just do ‘config.bauer.small = false’ in your ~/.config/nixpkgs/config.nix file. Alternatively, if you want the small version, just do ‘nixpkgs.config.bauer.small = true’.
Next we start defining some packages. R is one of the simpler ones right now, so let’s start with that.
rEnv = pkgs.rWrapper.override {
packages = with pkgs.rPackages; [
RCurl
];
};
Here we define our package set. This will just give us access to all of the Emacs packages defined in Nixpkgs.
We also define our Emacs version to use. Mitsuharo’s Emacs package is much better for MacOS so we use that when we’re on Darwin systems. Otherwise, just default to ‘emacs’ which should be the latest (Nixpkgs-unstable has version 26.1 currently).
customEmacsPackages = let
pkgs' = import pkgs.path {
inherit crossSystem system config;
overlays = overlays ++ [ emacs-overlay.overlay ];
};
in pkgs'.emacsPackagesFor (if pkgs'.stdenv.hostPlatform.isDarwin then pkgs'.emacsMacport else pkgs'.emacs-git);
Tex live provides some LaTeX commads for us.
myTex = pkgs.texlive.combine {
inherit (pkgs.texlive) xetex setspace
fontspec chktex enumitem xifthen
ifmtarg filehook wrapfig inconsolata
upquote minted lastpage collection-basic
collection-binextra collection-context
collection-fontsrecommended collection-fontutils
collection-langenglish collection-latex
collection-latexrecommended collection-luatex
collection-metapost collection-texworks
collection-xetex capt-of ulem hyperref titlesec
beamer;
};
Here, we start building up the site-paths.el file. This does a simple substitution of all the attributes set.
site-paths = runCommand "site-paths.el" (with pkgs; ({
inherit ripgrep silver-searcher coreutils;
inherit manpages sysheaders sysframeworks
schemas bins infopages;
inherit MANPATH PATH XDG_DATA_DIRS INFOPATH TERMINFO_DIRS;
} // (lib.optionalAttrs big {
gpg = gnupg1compat;
inherit
pandoc ghostscript
sqliteInteractive freetds
parallel unixODBC ncompress
texinfoInteractive gnuplot
gdb coq rtags gnutls;
inherit (haskellPackages) hoogle hlint;
texlive = myTex;
markdown2 = pythonPackages.markdown2;
}))) ''
substituteAll ${./site-paths.el.in} $out
substituteInPlace $out \
--subst-var-by PATH ${PATH} \
--subst-var-by INFOPATH ${INFOPATH} \
--subst-var-by MANPATH ${MANPATH} \
--subst-var-by XDG_DATA_DIRS ${XDG_DATA_DIRS} \
--subst-var-by TERMINFO_DIRS ${TERMINFO_DIRS}
'';
Emacs building can be divided into phases. Each phase will run through the Elisp once.
myEmacsPackages gets a listing of all of the packages that are needed by the Emacs configuration. use-package-list generates this list automatically.
package-list =
evalPkgs.runCommand "package-list" {
buildInputs = [ evalPkgs.emacs ];
} ''
emacs --batch --quick \
-L ${evalPkgs.emacsPackages.use-package
}/share/emacs/site-lisp/elpa/use-package-* \
-L ${evalPkgs.emacsPackages.delight
}/share/emacs/site-lisp/elpa/delight-* \
-L ${evalPkgs.emacsPackages.bind-key
}/share/emacs/site-lisp/elpa/bind-key-* \
-l ${ensure ./site-lisp/set-defaults.el
"site-lisp/set-defaults.el"} \
-l ${ensure ./site-lisp/bauer.el
"site-lisp/bauer.el"} \
-l ${ensure ./site-lisp/use-package-list.el
"site-lisp/use-package-list.el"} \
--eval "(use-package-list \"${./README.el}\")" \
> $out 2>/dev/null
'';
myEmacsPackages' = builtins.fromJSON
(builtins.readFile package-list);
default = runCommand "bauer-emacs" {
buildInputs = [ pkgs.emacs pkgs.git ];
} ''
Install our lisp files. Many of these should be released into MELPA but don’t have the time to do it currently.
install -D ${site-paths} \
$out/share/emacs/site-lisp/site-paths.el
install -D ${./README.el} \
$out/share/emacs/site-lisp/default.el
install -D ${ensure ./site-lisp/em-dired.el
"site-lisp/em-dired.el"} \
$out/share/emacs/site-lisp/em-dired.el
install -D ${ensure ./site-lisp/dired-column.el
"site-lisp/dired-column.el"} \
$out/share/emacs/site-lisp/dired-column.el
install -D ${ensure ./site-lisp/macho-mode.el
"site-lisp/macho-mode.el"} \
$out/share/emacs/site-lisp/macho-mode.el
install -D ${ensure ./site-lisp/nethack.el
"site-lisp/nethack.el"} \
$out/share/emacs/site-lisp/nethack.el
install -D ${ensure ./site-lisp/set-defaults.el
"site-lisp/set-defaults.el"} \
$out/share/emacs/site-lisp/set-defaults.el
install -D ${ensure ./site-lisp/pcomplete-extra.el
"site-lisp/pcomplete-extra.el"} \
$out/share/emacs/site-lisp/pcomplete-extra.el
install -D ${ensure ./site-lisp/installer.el
"site-lisp/installer.el"} \
$out/share/emacs/site-lisp/installer.el
install -D ${ensure ./site-lisp/restart-emacs.el
"site-lisp/restart-emacs.el"} \
$out/share/emacs/site-lisp/restart-emacs.el
install -D ${ensure ./site-lisp/use-package-list.el
"site-lisp/use-package-list.el"} \
$out/share/emacs/site-lisp/use-package-list.el
install -D ${ensure ./site-lisp/bauer.el
"site-lisp/bauer.el"} \
$out/share/emacs/site-lisp/bauer.el
install -D ${ensure ./site-lisp/comint-hyperlink.el
"site-lisp/comint-hyperlink.el"} \
$out/share/emacs/site-lisp/comint-hyperlink.el
install -D ${ensure ./site-lisp/persistent-mode.el
"site-lisp/persistent-mode.el"} \
$out/share/emacs/site-lisp/persistent-mode.el
install -D ${ensure ./site-lisp/envrc.el
"site-lisp/envrc.el"} \
$out/share/emacs/site-lisp/envrc.el
install -D ${ensure ./site-lisp/yesod-mode.el
"site-lisp/yesod-mode.el"} \
$out/share/emacs/site-lisp/yesod-mode.el
'';
This phase wraps the byte compiled lisp into an Emacs binary. Each package
listed in use-package
above is pulled into the closure.
requiredPackages is a function that takes two arguments.
requiredPackages = epkgs: map (x:
if builtins.hasAttr x epkgs
then builtins.getAttr x epkgs
else if builtins.hasAttr x pkgs.emacsPackages
then builtins.getAttr x pkgs.emacsPackages
else abort "no attribute found for use-package ${x}");
Now we build our Emacs distribution.
TODO: use dump-emacs here to speed up config.
myEmacsPackages = buildEnv {
name = "emacs-packages-env";
paths = (requiredPackages customEmacsPackages myEmacsPackages')
++ [ customEmacsPackages.use-package customEmacsPackages.delight ];
};
myEmacs = customEmacsPackages.emacsWithPackages (epkgs:
(requiredPackages epkgs myEmacsPackages')
++ [default epkgs.use-package epkgs.delight epkgs.treesit-grammars.with-all-grammars]
);
Finally, we can actually build the environment. This just uses Nixpkgs
buildEnv
to generate Nix paths for the different packages. Some need special handling.
First, we build the info pages. This takes all of the packages listed in
userPackages
and runs install-info
on them. Info pages are usually found in
the $out/share/info
directory, but in Emacs packages can be found anywhere in
$out/share/emacs
.
infopages = buildEnv {
name = "info-pages";
buildInputs = [ pkgs.texinfoInteractive ];
paths = userPackages ++ [customEmacsPackages.emacs]
++ lib.optional hostPlatform.isLinux pkgs.glibcInfo;
extraOutputsToInstall = [ "info" "doc" "devdoc" ];
pathsToLink = [ "/share/info" ];
postBuild = ''
shopt -s nullglob
find -L ${myEmacsPackages}/share/emacs/ -name '*.info*' -exec ln -s {} $out/share/info \;
for i in $out/share/info/*.info*; do
install-info $i $out/share/info/dir
done
'';
};
Next, we build the man pages. Again, these come from userPackages
and are in
the /share/man
directory. In addition, some extra man pages are added like the
POSIX man pages, and the C++ stdlib man pages. Other OS-specific ones are also
included where appropriate Linux man pages (man-pages
), and the ones from the
Apple SDK.
manpages = buildEnv {
name = "man-pages";
ignoreCollisions = (!(config.bauer.small or false));
paths = userPackages ++ [pkgs.man-pages-posix
pkgs.stdman
# pkgs.llvmPackages.clang-manpages
# pkgs.llvmPackages.llvm-manpages
]
++ lib.optional (hostPlatform.isDarwin && big && allowUnfree)
"${apple_sdk}/usr"
++ lib.optional hostPlatform.isLinux pkgs.man-pages;
extraOutputsToInstall = [ "man" "doc" "devdoc" "devman" ];
pathsToLink = [ "/share/man" ];
};
Next, build the XDG / FreeDesktop directory paths. These come from
userPackages
and include relevnt information for Desktop files, MIME info,
menus, and icons. This updates the caches where appropriate as well. The
location of these directories is defined in the FreeDesktop specification.
xdg-data = buildEnv {
name = "xdg-data-dirs";
buildInputs = [ pkgs.desktop-file-utils pkgs.shared-mime-info ];
paths = userPackages ++ [ customEmacsPackages.emacs pkgs.zsh ];
pathsToLink = [
"/share/applications"
"/share/mime"
"/share/menus"
"/share/icons"
];
postBuild = ''
export XDG_DATA_DIRS=$out/share
if [ -w $out/share/applications ]; then
update-desktop-database $out/share/applications
fi
if [ -w $out/share/mime ] \
&& [ -w $out/share/mime/packages ]; then
update-mime-database -V $out/share/mime
fi
'';
};
Next, ZSH completions are built. These all reside in the
$out/share/zsh/site-functions
directory.
zsh-completions = buildEnv {
name = "zsh-completions";
paths = [ pkgs.zsh-completions ] ++ userPackages; # pkgs.nix-zsh-completions
pathsToLink = [ "/share/zsh" ];
};
Next, setup the binary directory. This will be put in the user’s PATH
. We also
remove binaries starting with .
, as they are used in Nixpkgs for the “wrapped”
version of executables.
bins = buildEnv {
name = "bins";
paths = userPackages;
extraOutputsToInstall = [ "bin" ];
pathsToLink = [ "/bin" ];
postBuild = ''
find $out/bin -maxdepth 1 -name ".*" -type l -delete
'';
};
Setup the system headers path. This is delibirately light to avoid huge
closures. Right now, only libc
and libcxx
are included by default.
sysheaders = buildEnv {
name = "headers";
pathsToLink = [ "/include" ];
extraOutputsToInstall = [ "dev" ];
paths = [ stdenv.cc.libc ]
++ lib.optional hostPlatform.isDarwin pkgs.libcxx
++ lib.optional (hostPlatform.isDarwin && big && allowUnfree)
"${apple_sdk}/usr";
};
Also setup the Apple framework paths. These are a mix between lib directory and include directory. This is oinly useful on macOS/Darwin machines.
apple_sdk = "${pkgs.darwin.xcode}/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk";
sysframeworks = buildEnv {
name = "frameworks";
paths = lib.optionals (hostPlatform.isDarwin && big && allowUnfree)
[ "${apple_sdk}/System/Library/Frameworks"
"${apple_sdk}/System/Library/PrivateFrameworks" ];
};
Setup XML schema needed for editing some docbooks in Emacs. Not entirely sure why this is necessary, but nXML complains otherwise.
schemas = writeText "schemas.xml" ''
<locatingRules
xmlns="http://thaiopensource.com/ns/locating-rules/1.0"
>
<documentElement localName="section" typeId="DocBook"/>
<documentElement localName="chapter" typeId="DocBook"/>
<documentElement localName="article" typeId="DocBook"/>
<documentElement localName="book" typeId="DocBook"/>
<typeId id="DocBook"
uri="${pkgs.docbook5}/xml/rng/docbook/docbookxi.rnc"
/>
</locatingRules>
'';
Full listing of packages that will be made available.
userPackages = (with pkgs; [
# From common-path.nix
coreutils-full findutils diffutils gnused gnugrep gawk gnutar
gzip bzip2 gnumake bashInteractive patch xz
# Helpful core tools
curl zsh cacert file lsof pstree which rsync
unzip man less silver-searcher ripgrep
tree gnutls
direnv
] ++ lib.optionals big ([
git
# Useful tools
isync notmuch graphviz indent
graphviz imagemagick
bazaar mercurial
# Programming interpreters/compilers
myTex rEnv perl python lua coq ocaml
openjdk nodejs gcc gdb
travis v8
] ++ (lib.optional stdenv.hostPlatform.isDarwin openssh) # use /usr/bin/ ssh
++ (with netbsd; [ getent getconf ])
++ (with nodePackages; [ tern heroku node2nix ])
++ (with gitAndTools; [ hub ])
++ (with haskellPackages; [ ghc jq nix-diff cabal2nix cabal-install ])
++ (with unixtools; [ utillinux nettools procps ])
));
Setup global environment variables & directories. Most of the global directories will not exist, but the way search paths work, means that is okay. We aim to support all directories in a common system, prioritizing ones that the user has the most direct access to. Global directories should only contain system paths.
global-dirs = [ "/nix/var/nix/profiles/default"
"/run/wrappers"
"/run/current-system/sw"
"/usr/local"
"/usr"
"" ];
PATH = lib.concatStringsSep ":" [
(lib.makeBinPath ([ bins customEmacsPackages.emacs pkgs.zsh ]
++ global-dirs))
(lib.makeSearchPath "sbin" [ "/usr" "" ])
];
MANPATH = ":" + lib.makeSearchPathOutput "man" "share/man"
([ manpages customEmacsPackages.emacs pkgs.zsh ] ++ global-dirs);
INFOPATH = "${infopages}/share/info";
XDG_DATA_DIRS = lib.makeSearchPathOutput "xdg" "share"
([ xdg-data ] ++ global-dirs);
TERMINFO_DIRS = lib.makeSearchPathOutput "terminfo" "share/terminfo"
([ pkgs.ncurses ]);
INPUTRC = builtins.toFile "inputrc" ''
$include /etc/inputrc
$if mode=emacs
"\C-p": history-search-backward
"\C-n": history-search-forward
$endif
set keyseq-timeout 1200
set colored-stats on
set colored-completion-prefix on
set completion-ignore-case on
set completion-prefix-display-length 3
set enable-bracketed-paste on
set expand-tilde on
set menu-complete-display-prefix on
set mark-symlinked-directories on
set show-all-if-ambiguous on
set show-all-if-unmodified on
set visible-stats on
$if Bash
Space: magic-space
$endif
'';
Finally, build the final environment. This only contains Emacs and ZSH, which have been configured to use the paths above.
in buildEnv {
name = "bauer-2.0.1";
buildInputs = [ pkgs.makeWrapper pkgs.bash ];
postBuild = ''
mkdir -p $out/etc
substituteAll ${./zshrc.sh} $out/etc/.zshrc
substituteInPlace $out/etc/.zshrc \
--subst-var-by completions ${zsh-completions} \
--subst-var-by coreutils ${pkgs.coreutils}
ln -s $out/etc/.zshrc $out/etc/zshrc
makeWrapper ${pkgs.zsh}/bin/zsh $out/bin/zsh --set ZDOTDIR $out/etc
substituteAll ${./etc-profile.sh} $out/etc/profile
substituteInPlace $out/etc/profile \
--subst-var-by coreutils ${pkgs.coreutils}
substitute ${./runemacs.sh} $out/bin/run \
--subst-var-by emacs ${myEmacs}
chmod +x $out/bin/run
patchShebangs $out/bin/run
ln -s $out/bin/run $out/bin/bauer
'';
pathsToLink = [
"/bin"
"/etc/profile.d"
"/share/applications"
] ++ lib.optional hostPlatform.isDarwin "/Applications";
meta = with lib; {
description = "Bauer's automated unified Emacs realm";
maintainers = with maintainers; [ matthewbauer ];
platforms = platforms.all;
priority = 4;
};
passthru = with lib; {
shellPath = "/bin/zsh";
run = "/bin/run";
sourceFile = "/etc/profile.d/profile";
PATH = makeSearchPath "bin" [bins];
MANPATH = makeSearchPath "share/man" [manpages];
INFOPATH = makeSearchPath "share/info" [infopages];
XDG_DATA_DIRS = makeSearchPath "share" [xdg-data];
TERMINFO_DIRS = makeSearchPath "share/terminfo" [terminfo];
emacs = myEmacs;
emacsConfig = default;
inherit myTex rEnv;
};
paths = [
myEmacs
(runCommand "my-profile" {
inherit PATH MANPATH XDG_DATA_DIRS INFOPATH TERMINFO_DIRS;
bash_completion = pkgs.bash-completion;
} ''
mkdir -p $out/etc/profile.d
substituteAll ${./profile.sh} $out/etc/profile.d/my-profile.sh
substituteInPlace $out/etc/profile.d/my-profile.sh \
--subst-var-by PATH ${PATH} \
--subst-var-by INFOPATH ${INFOPATH} \
--subst-var-by MANPATH ${MANPATH} \
--subst-var-by XDG_DATA_DIRS ${XDG_DATA_DIRS} \
--subst-var-by TERMINFO_DIRS ${TERMINFO_DIRS} \
--subst-var-by INPUTRC ${INPUTRC}
'')
];
}
We can build it with nix-build
.
nix-build
./result/bin/run
Continuous integration is now through GitHub Actions. See .github/workflows/main.yml for more info.
These are some extra files that are checked in. They mostly deal with maintainence and advanced usage of BAUER.
This is a simple script that I use to make sure I’ve updated the generated files. It runs ORG mode tangler and then exports README.org to html.
emacs --batch \
-l ob-tangle \
--eval "(org-babel-tangle-file \"README.org\")"
emacs README.org --batch \
--eval "(setq org-html-htmlize-output-type 'css)" \
-l nix-mode \
-f org-html-export-to-html
If you end up with generated files, they’re easy to remove with Git. Just run
git clean -xdf
& it will remove all of the files that match the .gitignore
rules (which should never be added to the git tree).
These set up some paths for .gitignore
that we don’t want getting put in
the repo. Start with Emacs/org-mode/LaTeX stuff.
flycheck_*.el
*.elc
*.pdf
*.html
*.tex
*.log
*.aux
*.out
*.toc
Nix-related stuff. These are generate by nix-build
.
result
result-*
These are all tangled by README.org.
README.el
bauer.nix
zshrc.sh
etc-profile.sh
runemacs.sh
gitconfig
gitignore
default.el
profile.sh
site-paths.el.in
org-init.el
org-src-*
configuration.nix
install
auto/
*~
Mark generated files.
.gitattributes linguist-generated=true
.gitignore linguist-generated=true
.travis.yml linguist-generated=true
LICENSE linguist-generated=true
bootstrap.sh linguist-generated=true
config.nix linguist-generated=true
default.nix linguist-generated=true
deploy.sh linguist-generated=true
gist-unpack.sh linguist-generated=true
init.el linguist-generated=true
install linguist-generated=true
module.nix linguist-generated=true
update.sh linguist-generated=true
flake.nix linguist-generated=true
You can use this as part of your Emacs init file:
(load
(expand-file-name "settings.el" user-emacs-directory) t)
(package-initialize)
(defvar bauer-dir user-emacs-directory)
(defvar bauer-org
(expand-file-name "README.org" bauer-dir))
(add-to-list 'load-path
(expand-file-name "site-lisp" bauer-dir))
(add-to-list 'native-comp-eln-load-path
(expand-file-name "native-lisp" bauer-dir))
(unless (file-exists-p
(expand-file-name "README.el" bauer-dir))
(let ((default-directory bauer-dir))
(autoload 'org-babel-tangle-file "ob-tangle")
(org-babel-tangle-file bauer-org
"README.el"
"emacs-lisp")))
(load (expand-file-name "README.el" bauer-dir) t)
This will boot the IDE. Note that this is done for you in the Nix-based installation process. The above script should be used in places where Nix is unavailable.
I’ve provided a module suitable for use with a NixOS configuration. To use it, just add something like the following to your configuration.nix file.
# -*- mode: nix; coding: utf-8; -*-
{ ... }:
{
imports = [
(builtins.fetchurl "https://matthewbauer.us/bauer/module.nix")
];
programs.bauer.enable = true;
}
The actual module implementation follow here. It will pull in some files needed for all of this to work. It should provide ‘bauer’ as a runnable command.
# -*- mode: nix; coding: utf-8; -*-
{ config, lib, pkgs, ... }: with lib;
let
ensure = f: n: if builtins.pathExists f then f
else builtins.fetchurl
"https://matthewbauer.us/bauer/${n}";
bauer = import (ensure ./default.nix "default.nix") {
inherit pkgs;
};
in {
options = {
programs.bauer = {
enable = mkOption {
default = false;
type = types.bool;
};
};
};
config = mkIf config.programs.bauer.enable {
environment = {
systemPackages = [ bauer ];
variables = with lib; {
PATH = [bauer.PATH];
XDG_DATA_DIRS = [bauer.XDG_DATA_DIRS];
TERMINFO_DIRS = [bauer.TERMINFO_DIRS];
MANPATH = [bauer.MANPATH];
INFOPATH = [bauer.INFOPATH];
};
};
};
}
Use this to build a .ova file for demonstration purposes. This file can be booted by VirtualBox. Useful for Windows users especially.
# -*- mode: nix; coding: utf-8; -*-
{ system ? builtins.currentSystem }:
let
ensure = f: n: if builtins.pathExists f then f
else builtins.fetchurl
"https://matthewbauer.us/bauer/${n}";
in {
ova = (import <nixpkgs/nixos/lib/eval-config.nix> {
inherit system;
modules = [
<nixpkgs/nixos/modules/installer/virtualbox-demo.nix>
(ensure ./module.nix "module.nix")
];
}).config.system.build.virtualBoxOVA;
iso = (import <nixpkgs/nixos/lib/eval-config.nix> {
inherit system;
modules = [
<nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix>
(ensure ./module.nix "module.nix")
];
}).config.system.build.isoImage;
}
{
description = "an Emacs+Nix IDE";
inputs.nixpkgs.url = "github:nixos/nixpkgs";
inputs.emacs-overlay = {
url = "github:nix-community/emacs-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, emacs-overlay }: let
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
in {
packages = forAllSystems (system: {
bauer = let
pkgs = nixpkgsFor.${system};
evalPkgs = nixpkgsFor.${system};
in import (evalPkgs.runCommand "README" {
buildInputs = with evalPkgs; [ pkgs.emacs git ];
} (''
install -D ${./README.org} $out/README.org
cd $out
cp -r ${./site-lisp} site-lisp
emacs --batch --quick \
-l ob-tangle \
--eval "(org-babel-tangle-file \"README.org\")" > /dev/null
cp bauer.nix default.nix
'')) { inherit pkgs emacs-overlay evalPkgs system; };
inherit (self.packages.${system}.bauer) myTex emacs rEnv;
default = self.packages.${system}.bauer;
});
apps = forAllSystems (system: {
bauer = {
type = "app";
program = "${self.packages.${system}.bauer}/bin/run";
};
default = self.apps.${system}.bauer;
});
devShells = forAllSystems (system: {
bauer = with nixpkgsFor.${system}; stdenv.mkDerivation {
name = "bauer";
nativeBuildInputs = [ pkgs.emacs git ];
shellHook = ''
echo Run ./update.sh to generate files.
'';
};
default = self.devShells.${system}.bauer;
});
nixosModules.bauer = {
imports = [ ./module.nix ];
};
};
nixConfig.allow-import-from-derivation = true;
}
I use this script to deploy to the gh-pages branch. It merges master into gh-pages and then runs ORG mode tangle and export. Unfortunately, merge conflicts are not handled, and must be dealt with manually.
setup() {
git stash push
git checkout gh-pages
}
cleanup() {
git checkout master
git stash pop
}
setup
trap cleanup EXIT
git fetch origin
git reset --hard origin/gh-pages
git merge --no-edit master
./update.sh
git add .
git commit -m "Regen"
git push origin gh-pages
Private file handling for new environments. Unpack private files from a Gist into your environment. Must have already setup SSH GitHub authentication. To set up GitHub SSH run:
$ ssh-keygen
Then, add $HOME/.ssh/id_rsa
.pub through GitHub’s Web UI. Invoke the script
with:
$ ./gist-unpack.sh 4a7372cf458f2b322c0b7a6e7837592f
Files that can be included in the gist are:
- .authinfo: stores your passwords and credentials. Domains like smtp.gmail.com, api.github.com, and irc.freenode.net require credentials.
- .gitconfig: stores your personal Git configuration and settings. Things like user.email, user.name, github.user should go here. Unfortunately, it also may have credentials for smtpmail. Ideally these would be put in .authinfo to avoid mixing secrets. See this thread for more info.
- .sshconfig: stores your SSH configuration corresponding to .ssh/config. Put global host names and relates settings here.
- settings.el: store your personal Emacs configurations. Settings like
erc-nick
,message-send-mail-function
,send-mail-function
,smtpmail-smtp-server
,smtpmail-smtp-service
,user-full-name
, anduser-mail-address
should go here.
The actual script follows. The main idea is just to download all of the files
from the Gist and copy them to the $HOME
directory. --force
can be passed to
allow overriding your current $HOME
files.
if [ $# -eq 0 ]; then
echo Usage: "$0" [ GIST_ID | URL ] [ -u USER ] [ -t TOKEN ] >&2
exit 1
fi
FORCE=
GIST_ID=
USER=
TOKEN=
URL=
while [ $# -gt 0 ]; do
case "$1" in
-f|--force)
echo Forcing install... >&2
FORCE=1
shift
;;
-u|--user)
if [ -n "$USER" ]; then
echo Multiple users passed! >&2
exit 1
fi
shift
USER="$1"
shift
;;
-t|--token)
if [ -n "$TOKEN" ]; then
echo Multiple tokens passed! >&2
exit 1
fi
shift
TOKEN="$1"
shift
;;
-g|--gist-id)
if [ -n "$GIST_ID" ] || [ -n "$URL" ]; then
echo Multiple URLs or Gist ids passed! >&2
exit 1
fi
shift
GIST_ID="$1"
shift
;;
-u|--url)
if [ -n "$GIST_ID" ] || [ -n "$URL" ]; then
echo Multiple URLs or Gist ids passed! >&2
exit 1
fi
shift
URL="$1"
shift
;;
https://*|ssh://*|git://*)
if [ -n "$GIST_ID" ] || [ -n "$URL" ]; then
echo Multiple URLs or Gist ids passed! >&2
exit 1
fi
URL="$1"
shift
;;
*)
if [ -n "$GIST_ID" ] || [ -n "$URL" ]; then
echo Multiple URLs or Gist ids passed! >&2
exit 1
fi
GIST_ID="$1"
echo Using gist "$GIST_ID" >&2
shift
;;
esac
done
if [ -n "$GIST_ID" ]; then
URL=ssh://[email protected]/"$GIST_ID".git
fi
if [ -z "$URL" ]; then
echo No gist id or url provided. >&2
exit 1
fi
mkdir -p $HOME/.ssh
echo "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" >> $HOME/.ssh/known_hosts
ssh -T [email protected] 2> /dev/null
if [ $? -eq 255 ]; then
if ! [ -f "$HOME/.ssh/id_rsa" ]; then
echo No ssh key is available. Generating it and adding it to GitHub to continue.
ssh-keygen -t rsa -N "" -f "$HOME/.ssh/id_rsa"
fi
if [ -z "$USER" ] && [ -t 1 ]; then
echo -n "GitHub Username: "; read USER
fi
if [ -z "$USER" ]; then
echo Username cannot be empty. Pass -u with your username to continue.
exit 1
fi
auth="$USER"
if [ -n "$TOKEN" ]; then
auth="$auth:$TOKEN"
fi
curl -u "$auth" -d "$(printf '{"title": "%s", "key": "%s"}' "${HOST-$(hostname)}" "$(cat $HOME/.ssh/id_rsa.pub)")" https://api.github.com/user/keys > /dev/null
fi
gistdir="$(mktemp -d)"
setup() {
if ! git clone "$URL" "$gistdir"; then
echo Failed to clone Gist. Verify "$URL" exists >&2
echo and you have permission to access it. >&2
exit 1
fi
pushd "$gistdir" >/dev/null
}
cleanup() {
popd >/dev/null
rm -rf "$gistdir"
}
setup
trap cleanup EXIT
if [ -n "${BASH_VERSION-}" ]; then
shopt -s dotglob
fi
shopt -s nullglob
for f in *; do
if [ "$f" = ".git" ]; then
continue
fi
if ! [ -f "$f" ]; then
echo Skipping "$f", not a file >&2
continue
fi
DEST=
case "$f" in
settings.el) DEST="$HOME/.config/emacs/settings.el" ;;
.sshconfig) DEST="$HOME/.ssh/config" ;;
.ssh_authorized_keys) DEST="$HOME/.ssh/authorized_keys" ;;
nix.conf) DEST="$HOME/.config/nix/nix.conf" ;;
machines) DEST="$HOME/.config/nix/machines" ;;
.gitignore) DEST="$HOME/.config/git/ignore" ;;
.gitconfig) DEST="$HOME/.config/git/config" ;;
.gitattributes) DEST="$HOME/.config/git/attributes" ;;
credentials|.aws_credentials) DEST="$HOME/.aws/credentials" ;;
*) DEST="$HOME/$f" ;;
esac
CONCAT=
case "$f" in
.authinfo) CONCAT=1 ;;
.sshconfig) CONCAT=1 ;;
.ssh_authorized_keys) CONCAT=1 ;;
.gitignore) CONCAT=1 ;;
nix.conf) CONCAT=1 ;;
machines) CONCAT=1 ;;
esac
if [ -z "$DEST" ]; then
echo Skipping "$f", no destination found >&2
continue
fi
if [ -f "$DEST" ] && [ -z "$FORCE" ] && [ -z "$CONCAT" ]; then
echo Skipping "$f", destination already exists >&2
continue
fi
mkdir -p "$(dirname "$DEST")"
if [ -n "$CONCAT" ]; then
cat "$f" >> "$DEST"
else
cp "$f" "$DEST"
fi
case "$f" of
.authinfo) chmod 600 "$DEST" ;;
"Library/LaunchAgents/"*) launchctl load -w "$HOME/$f" ;;
esac
done
Copyright © 2018-2020 Matthew Bauer
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.