Nix and Emacs configuration
Contents of this file get tangled to $HOME via
(org-babel-tangle)
This exports configuration files to $HOME/.config/nixpkgs/. The Nix user environment gets populated by running
nix-env -iAr nixpkgs.tom
Setting up a new box
This is a checklist for reinstalling my laptop.
- Install Ubuntu.
- Install tools outside of Nix: e.g., dropbox, docker, nix.
- Symlink $HOME/.gnupg and $HOME/.profile.
- Tangle this file using a temporary nix-shell environment.
Nix configuration
Most of my software gets installed via Nix. First, proprietary software is enabled in config.nix:
{ allowUnfree = true; }
Next, I define a meta-package depending on all the necessary packages:
self: super: {
tom = super.tom or {} // {
emacsGit = self.lowPrio self.emacsGit; # emacs also writes .desktop => set lower priority
emacs-custom-desktop = self.emacs-custom-desktop;
nix = self.nix;
conda = self.conda;
slack = self.slack;
gnupg = self.gnupg;
pass = self.pass;
ripgrep = self.ripgrep;
htop = self.htop;
gmsh = self.gmsh;
suitesparse = self.suitesparse;
gnumake = self.gnumake;
xclip = self.xclip;
graphviz = self.graphviz;
git = self.git;
vim = self.vim;
tmux = self.tmux;
pandoc = self.pandoc;
};
}
Emacs overlays
I’m using the bleeding edge Emacs overlay from https://github.com/nix-community/emacs-overlay, but fixing it to a specific commit so package versions are equivalent on all computers.
import (builtins.fetchTarball {
url = "https://github.com/nix-community/emacs-overlay/archive/86707a04d9679a92b7454e073a13e0c676e59e6d.tar.gz";
sha256 = "1x3l0iypc1zchm364axp66m70jjik4pl413lrwj37w320gv23fh5";
})
The following overlay enables a custom Emacs .desktop file (below) which fixes
dead keys (^, `, etc.) in Emacs 27/Ubuntu 19.10. It also uses the function
emacsWithPackagesFromUsePackage
from the above Emacs overlay to install a set
of (M)ELPA packages.
self: super: {
emacs-custom-desktop = with self; stdenv.mkDerivation {
name = "emacs-custom-desktop";
src = ./emacs;
buildInputs = [];
installPhase = ''
mkdir -p $out/share/applications
cp emacs.desktop $out/share/applications
'';
};
emacsGit = (self.emacsWithPackagesFromUsePackage {
config = builtins.readFile ~/.config/emacs/init.el;
package = super.emacsGit;
});
}
This is the custom .desktop file. It simply unsets XMODIFIERS which fixes the dead keys.
[Desktop Entry]
Name=Emacs
GenericName=Text Editor
Comment=Edit text
MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
Exec=env XMODIFIERS="" emacs %F
Icon=emacs
Type=Application
Terminal=false
Categories=Development;TextEditor;
StartupWMClass=Emacs
Keywords=Text;Editor;
Emacs configuration
The Emacs configuration comprises the rest of the snippets.
(require 'package)
(package-initialize 'noactivate)
(eval-when-compile
(require 'use-package))
Org-mode
(use-package org
:commands org-babel-do-load-languages
:config
(unbind-key "C-," org-mode-map)
(unbind-key "C-." org-mode-map)
:init
(add-hook 'org-mode-hook (lambda ()
(fset 'tex-font-lock-suscript 'ignore)
(org-babel-do-load-languages
'org-babel-load-
'((python . t)
(shell . t)))))
(add-hook 'org-agenda-finalize-hook
(lambda ()
(save-excursion
(set-face-attribute 'org-agenda-structure nil :height (car moe-theme-resize-org-title))
(set-face-attribute 'org-agenda-date nil :height (cadr moe-theme-resize-org-title))
(set-face-attribute 'org-agenda-date-today nil :height (cadr moe-theme-resize-org-title))
(set-face-attribute 'org-agenda-date-weekend nil :height (cadr moe-theme-resize-org-title))
(color-org-header "inbox:" "#DDDDFF" "black")
(color-org-header "work:" "#FFDDDD" "red")
(color-org-header "research:" "#DDFFDD" "DarkGreen"))))
(defun color-org-header (tag backcolor forecolor)
""
(interactive)
(goto-char (point-min))
(while (re-search-forward tag nil t)
(add-text-properties
(match-beginning 0) (+ (match-beginning 0) 10)
`(face (:background, backcolor, :foreground, forecolor)))))
(fset 'tex-font-lock-suscript 'ignore)
(defun capture-report-date-file ()
(interactive)
(let ((name (read-string "Name: ")))
(expand-file-name (format "%s-%s.org"
(format-time-string "%Y-%m-%d")
name)
"~/Dropbox/Notes/")))
(setq org-default-notes-file "~/Dropbox/Notes/gtd/inbox.org"
org-agenda-files '("~/Dropbox/Notes/gtd/inbox.org"
"~/Dropbox/Notes/gtd/tickler.org"
"~/Dropbox/Notes/gtd/research.org"
"~/Dropbox/Notes/gtd/work.org")
org-refile-targets '(("~/Dropbox/Notes/gtd/inbox.org" . (:maxlevel . 1))
("~/Dropbox/Notes/gtd/tickler.org" . (:maxlevel . 1))
("~/Dropbox/Notes/gtd/research.org" . (:maxlevel . 1))
("~/Dropbox/Notes/gtd/work.org" . (:maxlevel . 1)))
org-log-done 'time
org-tags-column 0
org-export-babel-evaluate nil
org-startup-folded nil
org-adapt-indentation nil
org-refile-use-outline-path 'file
org-structure-template-alist '(("l" . "latex latex")
("s" . "src"))
org-outline-path-complete-in-steps nil
org-duration-format '(("d" . nil) ("h" . t) (special . 2))
org-format-latex-options '(:foreground default
:background default
:scale 1.5
:html-foreground "Black"
:html-background "Transparent"
:html-scale 1.0
:matchers
("begin" "$1" "$" "$$" "\\(" "\\["))
org-src-preserve-indentation t
org-confirm-babel-evaluate nil
org-html-validation-link nil
python-shell-completion-native-disabled-interpreters '("python")
org-babel-default-header-args:sh '((:prologue . "exec 2>&1")
(:epilogue . ":"))
org-capture-templates '(("t" "Todo" entry
(file "~/Dropbox/Notes/gtd/inbox.org")
"* TODO %?\n SCHEDULED: %t\n%i\n%a")
("k" "Entry" entry
(file "~/Dropbox/Notes/gtd/inbox.org")
"* %?\n%t")
("n" "Note" entry
(file capture-report-date-file))))
:bind (("C-c c" . org-capture)
("C-c a" . org-agenda)))
Find, select and modify: ivy/counsel/swiper and wgrep
(use-package ivy
:commands
ivy-mode
:init
(ivy-mode 1)
(setq ivy-height 15
ivy-fixed-height-minibuffer t
ivy-use-virtual-buffers t)
:bind (("C-x b" . ivy-switch-buffer)
("C-c r" . ivy-resume)
("C-x C-b" . ibuffer)))
(use-package counsel
:init
(setq counsel-find-file-ignore-regexp "\\archive\\'")
(defun counsel-org-rg ()
"Search org notes using ripgrep."
(interactive)
(counsel-rg "-g*org -g!*archive* -- " "~/Dropbox/Notes" nil nil))
(defun counsel-nixpkgs-rg ()
"Search nixpkgs using ripgrep."
(interactive)
(counsel-rg "" "~/.nix-defexpr/channels/nixpkgs" nil nil))
(defun counsel-nixpkgs-file ()
"Search nixpkgs using ripgrep."
(interactive)
(counsel-file-jump "" "~/.nix-defexpr/channels/nixpkgs"))
:bind (("M-x" . counsel-M-x)
("C-x C-f" . counsel-find-file)
("C-c g" . counsel-rg)
("C-c G" . counsel-git)
("C-c o" . counsel-org-rg)
("C-c l" . counsel-nixpkgs-rg)
("C-c L" . counsel-nixpkgs-file)
("C-x b" . counsel-switch-buffer)
("C-c h" . counsel-minibuffer-history)
("M-y" . counsel-yank-pop)))
(use-package swiper
:bind ("C-c s" . swiper))
(use-package wgrep)
Magit
(use-package magit
:init
(setq magit-repository-directories '(("~/src" . 1)))
:bind (("C-x g" . magit-status)
("C-c M-g" . magit-file-dispatch)))
Multiline editing
This triplet of packages allows doing magical things.
(use-package expand-region
:after (org)
:bind ("C-." . er/expand-region)
:init
(require 'expand-region)
(require 'cl)
(defun mark-around* (search-forward-char)
(let* ((expand-region-fast-keys-enabled nil)
(char (or search-forward-char
(char-to-string
(read-char "Mark inner, starting with:"))))
(q-char (regexp-quote char))
(starting-point (point)))
(when search-forward-char
(search-forward char (point-at-eol)))
(cl-flet ((message (&rest args) nil))
(er--expand-region-1)
(er--expand-region-1)
(while (and (not (= (point) (point-min)))
(not (looking-at q-char)))
(er--expand-region-1))
(er/expand-region -1))))
(defun mark-around ()
(interactive)
(mark-around* nil))
(define-key global-map (kbd "M-i") 'mark-around))
(use-package multiple-cursors
:init
(define-key global-map (kbd "C-'") 'mc-hide-unmatched-lines-mode)
(define-key global-map (kbd "C-,") 'mc/mark-next-like-this)
(define-key global-map (kbd "C-;") 'mc/mark-all-dwim))
(use-package phi-search
:after multiple-cursors
:init (require 'phi-replace)
:bind ("C-:" . phi-replace)
:bind (:map mc/keymap
("C-s" . phi-search)
("C-r" . phi-search-backward)))
Customizations to dired
(defalias 'use-internal-package 'use-package)
(use-internal-package term)
(use-internal-package dired-x)
(use-internal-package dired
:after (term dired-x)
:init
(setq dired-dwim-target t)
(setq dired-omit-files "^\\...+$")
(defun run-gnome-terminal-here ()
(interactive)
(shell-command "gnome-terminal"))
(setq dired-guess-shell-alist-user
'(("\\.pdf\\'" "evince")
("\\.eps\\'" "evince")
("\\.jpe?g\\'" "eog")
("\\.png\\'" "eog")
("\\.gif\\'" "eog")
("\\.xpm\\'" "eog")))
:bind (("C-x C-j" . dired-jump))
:bind (:map dired-mode-map
("'" . run-gnome-terminal-here)
("j" . swiper)
("s" . swiper)))
(use-package dired-k
:after (dired)
:bind (:map dired-mode-map
("g" . dired-k)))
(use-package diredfl
:commands diredfl-global-mode
:init (diredfl-global-mode))
Additional syntax highlighting
(use-package json-mode)
(use-package julia-mode)
(use-package highlight-indentation
:init (add-hook 'prog-mode-hook 'highlight-indentation-mode))
(use-package yaml-mode)
(use-package csv-mode
:mode "\\.csv$"
:init (setq csv-separators '(";")))
(use-package markdown-mode
:commands (markdown-mode)
:mode (("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode)))
(use-package nix-mode)
Emacs theme
(use-package moe-theme
:commands moe-light
:init
(require 'org)
(setq moe-theme-resize-markdown-title '(2.0 1.7 1.5 1.3 1.0 1.0))
(setq moe-theme-resize-org-title '(2.2 1.8 1.6 1.4 1.2 1.0 1.0 1.0 1.0))
(setq moe-theme-resize-rst-title '(2.0 1.7 1.5 1.3 1.1 1.0))
(put 'diredp-tagged-autofile-name 'face-alias 'diredfl-tagged-autofile-name)
(put 'diredp-autofile-name 'face-alias 'diredfl-autofile-name)
(put 'diredp-ignored-file-name 'face-alias 'diredfl-ignored-file-name)
(put 'diredp-symlink 'face-alias 'diredfl-symlink)
(put 'diredp-compressed-file-name 'face-alias 'diredfl-compressed-file-name)
(put 'diredp-file-suffix 'face-alias 'diredfl-file-suffix)
(put 'diredp-compressed-extensions 'face-alias 'diredfl-compressed-extensions)
(put 'diredp-deletion 'face-alias 'diredfl-deletion)
(put 'diredp-deletion-file-name 'face-alias 'diredfl-deletion-file-name)
(put 'diredp-flag-mark-line 'face-alias 'diredfl-flag-mark-line)
(put 'diredp-rare-priv 'face-alias 'diredfl-rare-priv)
(put 'diredp-number 'face-alias 'diredfl-number)
(put 'diredp-exec-priv 'face-alias 'diredfl-exec-priv)
(put 'diredp-file-name 'face-alias 'diredfl-file-name)
(put 'diredp-dir-heading 'face-alias 'diredfl-dir-heading)
(put 'diredp-compressed-file-suffix 'face-alias 'diredfl-compressed-file-suffix)
(put 'diredp-flag-mark 'face-alias 'diredfl-flag-mark)
(put 'diredp-mode-set-explicitly 'face-alias 'diredfl-mode-set-explicitly)
(put 'diredp-executable-tag 'face-alias 'diredfl-executable-tag)
(put 'diredp-global-mode-hook 'face-alias 'diredfl-global-mode-hook)
(put 'diredp-ignore-compressed-flag 'face-alias 'diredfl-ignore-compressed-flag)
(put 'diredp-dir-priv 'face-alias 'diredfl-dir-priv)
(put 'diredp-date-time 'face-alias 'diredfl-date-time)
(put 'diredp-other-priv 'face-alias 'diredfl-other-priv)
(put 'diredp-no-priv 'face-alias 'diredfl-no-priv)
(put 'diredp-link-priv 'face-alias 'diredfl-link-priv)
(put 'diredp-write-priv 'face-alias 'diredfl-write-priv)
(put 'diredp-global-mode-buffers 'face-alias 'diredfl-global-mode-buffers)
(put 'dired-directory 'face-alias 'diredfl-dir-name)
(put 'diredp-read-priv 'face-alias 'diredfl-read-priv)
(global-hl-line-mode)
(moe-light)
(set-face-attribute 'font-lock-type-face nil :box 1)
(set-face-attribute 'font-lock-function-name-face nil :box 1))
Python development
My current Python workflow is fairly old school and it’s complemented by
counsel-rg
and counsel-git
.
(use-package dumb-jump
:bind (("M-." . dumb-jump-go)
("M-," . dumb-jump-back))
:config (setq dumb-jump-selector 'ivy))
(use-package virtualenvwrapper
:init (setq venv-location "~/.conda/envs"))
(use-package python-pytest
:bind ("C-c t" . python-pytest-popup))
(use-package hydra)
;; from move-lines package, https://github.com/targzeta/move-lines
(defun move-lines--internal (n)
"Moves the current line or, if region is actives, the lines surrounding
region, of N lines. Down if N is positive, up if is negative"
(let* (text-start
text-end
(region-start (point))
(region-end region-start)
swap-point-mark
delete-latest-newline)
(when (region-active-p)
(if (> (point) (mark))
(setq region-start (mark))
(exchange-point-and-mark)
(setq swap-point-mark t
region-end (point))))
(end-of-line)
(if (< (point) (point-max))
(forward-char 1)
(setq delete-latest-newline t)
(insert-char ?\n))
(setq text-end (point)
region-end (- region-end text-end))
(goto-char region-start)
(beginning-of-line)
(setq text-start (point)
region-start (- region-start text-end))
(let ((text (delete-and-extract-region text-start text-end)))
(forward-line n)
(when (not (= (current-column) 0))
(insert-char ?\n)
(setq delete-latest-newline t))
(insert text))
(forward-char region-end)
(when delete-latest-newline
(save-excursion
(goto-char (point-max))
(delete-char -1)))
(when (region-active-p)
(setq deactivate-mark nil)
(set-mark (+ (point) (- region-start region-end)))
(if swap-point-mark
(exchange-point-and-mark)))))
(defun move-lines-up (n)
"Moves the current line or, if region is actives, the lines surrounding
region, up by N lines, or 1 line if N is nil."
(interactive "p")
(if (eq n nil)
(setq n 1))
(move-lines--internal (- n)))
(defun move-lines-down (n)
"Moves the current line or, if region is actives, the lines surrounding
region, down by N lines, or 1 line if N is nil."
(interactive "p")
(if (eq n nil)
(setq n 1))
(move-lines--internal n))
(defun tom/shift-left (start end &optional count)
"Shift region left and activate hydra."
(interactive
(if mark-active
(list (region-beginning) (region-end) current-prefix-arg)
(list (line-beginning-position) (line-end-position) current-prefix-arg)))
(python-indent-shift-left start end count)
(tom/hydra-move-lines/body))
(defun tom/shift-right (start end &optional count)
"Shift region right and activate hydra."
(interactive
(if mark-active
(list (region-beginning) (region-end) current-prefix-arg)
(list (line-beginning-position) (line-end-position) current-prefix-arg)))
(python-indent-shift-right start end count)
(tom/hydra-move-lines/body))
(defun tom/move-lines-p ()
"Move lines up once and activate hydra."
(interactive)
(move-lines-up 1)
(tom/hydra-move-lines/body))
(defun tom/move-lines-n ()
"Move lines down once and activate hydra."
(interactive)
(move-lines-down 1)
(tom/hydra-move-lines/body))
(defhydra tom/hydra-move-lines ()
"Move one or multiple lines"
("n" move-lines-down "down")
("p" move-lines-up "up")
("<" python-indent-shift-left "left")
(">" python-indent-shift-right "right"))
(define-key global-map (kbd "C-c n") 'tom/move-lines-n)
(define-key global-map (kbd "C-c p") 'tom/move-lines-p)
(define-key global-map (kbd "C-c <") 'tom/shift-left)
(define-key global-map (kbd "C-c >") 'tom/shift-right)
Miscellanous stuff
Including things that I yet haven’t categorized.
(use-package exec-path-from-shell
:commands exec-path-from-shell-initialize
:init (exec-path-from-shell-initialize))
(use-package transient)
(use-package docker
:bind ("C-c d" . docker))
(use-package restclient)
(use-package ob-restclient
:after (org restclient)
:init (org-babel-do-load-languages
'org-babel-load-languages '((restclient . t))))
(use-package htmlize)
(use-package org-ref)
(use-package which-key
:commands which-key-mode
:init (which-key-mode))
(use-package ivy-pass
:commands ivy-pass
:init
(defun pass ()
"Call ivy-pass."
(interactive)
(ivy-pass)))
;; useful functions
(defun tom/unfill-paragraph (&optional region)
"Take REGION and turn it into a single line of text."
(interactive (progn (barf-if-buffer-read-only) '(t)))
(let ((fill-column (point-max))
(emacs-lisp-docstring-fill-column t))
(fill-paragraph nil region)))
(define-key global-map "\M-Q" 'tom/unfill-paragraph)
(defun tom/increment-number-decimal (&optional arg)
"Increment the number forward from point by 'arg'."
(interactive "p*")
(save-excursion
(save-match-data
(let (inc-by field-width answer)
(setq inc-by (if arg arg 1))
(skip-chars-backward "0123456789")
(when (re-search-forward "[0-9]+" nil t)
(setq field-width (- (match-end 0) (match-beginning 0)))
(setq answer (+ (string-to-number (match-string 0) 10) inc-by))
(when (< answer 0)
(setq answer (+ (expt 10 field-width) answer)))
(replace-match (format (concat "%0" (int-to-string field-width) "d")
answer)))))))
(global-set-key (kbd "C-c x") 'tom/increment-number-decimal)
;; other global configurations
;; show current function in modeline
(which-function-mode)
;; scroll screen
(define-key global-map "\M-n" 'scroll-up-line)
(define-key global-map "\M-p" 'scroll-down-line)
;; change yes/no to y/n
(defalias 'yes-or-no-p 'y-or-n-p)
(setq confirm-kill-emacs 'yes-or-no-p)
;; enable winner-mode, previous window config with C-left
(winner-mode 1)
;; windmove
(windmove-default-keybindings)
;; fonts
(set-face-attribute 'mode-line-inactive nil :font "Ubuntu Mono-12")
(set-face-attribute 'default nil :font "Ubuntu Mono-16")
(set-face-attribute 'mode-line nil :font "Ubuntu Mono-12")
;; disable tool and menu bars
(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
(blink-cursor-mode -1)
;; change gc behavior
(setq gc-cons-threshold 50000000)
;; warn when opening large file
(setq large-file-warning-threshold 100000000)
;; disable startup screen
(setq inhibit-startup-screen t)
;; useful frame title format
(setq frame-title-format
'((:eval (if (buffer-file-name)
(abbreviate-file-name (buffer-file-name))
"%b"))))
;; automatic revert
(global-auto-revert-mode t)
;; highlight parenthesis, easier jumping with C-M-n/p
(show-paren-mode 1)
(setq show-paren-style 'expression)
(setq show-paren-delay 0)
;; control indentation
(setq-default indent-tabs-mode nil)
(setq tab-width 4)
(setq c-basic-offset 4)
;; modify scroll settings
(setq scroll-preserve-screen-position t)
;; set default fill width (e.g. M-q)
(setq-default fill-column 80)
;; window dividers
(fringe-mode 0)
(setq window-divider-default-places t
window-divider-default-bottom-width 1
window-divider-default-right-width 1)
(window-divider-mode 1)
;; display time in modeline
(display-time-mode 1)
;; put all backups to same directory to not clutter directories
(setq backup-directory-alist '(("." . "~/.emacs.d/backups")))
;; display line numbers
(global-display-line-numbers-mode)
;; browse in chrome
(setq browse-url-browser-function 'browse-url-chrome)
;; don't fontify latex
(setq font-latex-fontify-script nil)
;; set default encodings to utf-8
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-language-environment 'utf-8)
(set-selection-coding-system 'utf-8)
;; make Customize to not modify this file
(setq custom-file (make-temp-file "emacs-custom"))
;; enable all disabled commands
(setq disabled-command-function nil)
;; ediff setup
(setq ediff-window-setup-function 'ediff-setup-windows-plain)
;; unbind keys
(unbind-key "C-z" global-map)
;; change emacs frame by number
(defun tom/select-frame (n)
"Select frame identified by the number N."
(interactive)
(let ((frame (nth n (reverse (frame-list)))))
(if frame
(select-frame-set-input-focus frame)
(select-frame-set-input-focus (make-frame))
(toggle-frame-fullscreen))))
(define-key global-map
(kbd "M-1")
(lambda () (interactive)
(tom/select-frame 0)))
(define-key global-map
(kbd "M-2")
(lambda () (interactive)
(tom/select-frame 1)))
(define-key global-map
(kbd "M-3")
(lambda () (interactive)
(tom/select-frame 2)))
(define-key global-map
(kbd "M-4")
(lambda () (interactive)
(tom/select-frame 3)))
;; bind find config
(define-key global-map (kbd "<f10>")
(lambda () (interactive)
(find-file "~/Dropbox/Config/nixpkgs/README.org")))
;; bind compile
(define-key global-map (kbd "<f12>") 'compile)
;; load private configurations
(load "~/Dropbox/Config/emacs/private.el" t)