John's Emacs Config
Table of Contents
- Introduction
- System paths and files
- Constants
- Package Archives and Management
- Utility Functions and Macros
- OS Specific
- Install Packages
- Configure
Introduction
See init.el on how Org loads and interprets this file for initializing Emacs.
The latest raw version of this file can be found at https://github.com/john2x/emacs.d.
This file was last exported: 2019-10-04 16:36
System paths and files
Tell Emacs where to put packages installed from Melpa, where custom themes
can be loaded and where customizations done with customize should be stored.
(push (expand-file-name "lib" "~/.emacs.d") load-path) (push (expand-file-name "themes" "~/.emacs.d") custom-theme-load-path) (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (when (file-exists-p custom-file) (load custom-file))
Constants
(defconst *is-a-mac* (eq system-type 'darwin)) (defconst *is-linux* (eq system-type 'gnu/linux))
Package Archives and Management
MELPA
Add MELPA and MELPA Stable as package archives.
(require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t)
Pin these packages to use fetch from MELPA stable.
(setq package-pinned-packages '((cider . "melpa-stable")))
Initialize the package library.
(package-initialize)
Utility Functions and Macros
Define utility functions and macros used throughout this config file.
add-auto-mode
(defun add-auto-mode (mode &rest patterns) "Add entries to `auto-mode-alist' to use `MODE' for all given file `PATTERNS'." (dolist (pattern patterns) (add-to-list 'auto-mode-alist (cons pattern mode))))
Persistent scratch
Persist the *scratch* buffer every 5 minutes, so we don't lose any possibly important data if/when Emacs crashes.1
(defun save-persistent-scratch () "Write the contents of *scratch* to the file name `persistent-scratch-file-name'." (with-current-buffer (get-buffer-create "*scratch*") (write-region (point-min) (point-max) "~/.emacs-persistent-scratch"))) (defun load-persistent-scratch () "Load the contents of `persistent-scratch-file-name' into the scratch buffer, clearing its contents first." (if (file-exists-p "~/.emacs-persistent-scratch") (with-current-buffer (get-buffer "*scratch*") (delete-region (point-min) (point-max)) (insert-file-contents "~/.emacs-persistent-scratch")))) (push #'load-persistent-scratch after-init-hook) (push #'save-persistent-scratch kill-emacs-hook) (if (not (boundp 'save-persistent-scratch-timer)) (setq save-persistent-scratch-timer (run-with-idle-timer 300 t 'save-persistent-scratch)))
OS Specific
OS X
Conditionally set the following when on OS X (see Constants):
- Reveal file in current buffer in Finder.
- Use Command ⌘ for Meta and don't use Option ⌥.
- Fix mouse wheel/trackpad scrolling to be less "jerky".
- Bind ⌘+` to switch between frames.
(when *is-a-mac* ;; 1. ;; 2. (setq mac-command-modifier 'meta) (setq mac-option-modifier 'none) ;; 3. (setq mouse-wheel-scroll-amount '(1 ((shift) . 5) ((control)))) ;; 4. (global-set-key "\M-`" 'other-frame) (global-set-key "\M-~" (lambda () (interactive) (other-frame -1))))
The following adds packages installed by Homebrew to our load-path.
(if *is-a-mac* (let ((default-directory "/Applications/Emacs.app/Contents/Resources/site-lisp/")) (normal-top-level-add-subdirs-to-load-path)))
Linux
The following adds packages installed by Pacman to our load-path.
(if *is-linux* (let ((default-directory "/usr/share/emacs/site-lisp/")) (normal-top-level-add-subdirs-to-load-path)))
Load keychain environment variables, so we don't have to keep on typing our SSH passphrase.
(when *is-linux* (keychain-refresh-environment))
Make the kill ring work with X selections.
(setq select-enable-clipboard t select-enable-primary t)
TODO Windows
Try using this config on a Windows (7+) VM.
Install Packages
Install all packages here using install-package.
(defvar my-packages '(;;;; Misc exec-path-from-shell undo-tree bind-key avy link-hint swiper keychain-environment ;;;; Mode-line diminish smart-mode-line rich-minority ;;;; UI indent-guide yascroll highlight-symbol smooth-scroll ;;;; ido, ~M-x~ flx-ido ido-completing-read+ smex idomenu ido-vertical-mode ;;;; Window and frame management buffer-move ;window-number fullframe perspective nameframe edwina ;;;; Interactive Search anzu ;;;; Completion company company-emoji company-lsp company-terraform ;;;; Language Server Protocol lsp-mode lsp-ui ; lsp-python ;;;; Linting flycheck ;;;; Dired ;; dired+ ;;;; Ack & Ag ag ;;;; Git magit git-blamed gitignore-mode gitconfig-mode git-messenger git-gutter browse-at-remote ;;;; Projectile projectile flx project-explorer ;;;; frame-purpose frame-purpose nameframe nameframe-projectile nameframe-perspective ;;;; Evil (Vim) evil evil-anzu evil-surround evil-leader evil-matchit evil-nerd-commenter evil-search-highlight-persist evil-vimish-fold ;;;; Ledger ledger-mode flycheck-ledger ;;;; Language specific ;;;;;; Python pyvenv nose elpy ;; ein ;;;;;; YAML yaml-mode ;;;;;; HTML, CSS web-mode ;;;;;; Markdown markdown-mode ;;;;;; Javascript json-mode js2-mode ;;;;;; Lisp paredit rainbow-delimiters highlight-parentheses paren-face ;;;;;; Clojure cider ;;;;;; Misc haskell-mode ghc flycheck-haskell purescript-mode elm-mode mu4e-alert restclient company-restclient origami ;;;;;; Org htmlize org-journal ;; ob-ipython toc-org) "My packages!") ;; loop over my-packages and install them (defun install-my-packages () (interactive) (mapc 'package-install my-packages)) (install-my-packages)
Configure
Now that everything is installed and ready, we can begin configuring packages, modes, key bindings, etc.
Misc
For a majority of programming modes, we want to indent immediately after a newline.
(add-hook 'prog-mode-hook (lambda () (local-set-key (kbd "RET") 'newline-and-indent)))
For a majority of programming languages, an underscore is part of a word or symbol.
(modify-syntax-entry ?_ "w" (standard-syntax-table))
Set some generic variables.
(setq-default tab-width 4 make-backup-files nil indent-tabs-mode nil show-trailing-whitespace t visible-bell nil)
We don't want to have to type "yes" or "no" at prompts.
(fset 'yes-or-no-p 'y-or-n-p)
Remember where we were when we last visited a file.
(setq-default save-place t) (setq save-place-file "~/.emacs.d/tmp/saved-places")
Automatically creating missing parent directories when visiting a new file.
(defun my-create-non-existent-directory () (let ((parent-directory (file-name-directory buffer-file-name))) (when (and (not (file-exists-p parent-directory)) (y-or-n-p (format "Directory `%s' does not exist! Create it?" parent-directory))) (make-directory parent-directory t)))) (add-to-list 'find-file-not-found-functions #'my-create-non-existent-directory)
When visiting buffers with the same name, uniqify them instead of the default of appending a number.
(setq uniquify-buffer-name-style 'forward uniquify-separator " • " uniquify-after-kill-buffer-p t ;; don't uniquify internal buffers (those that start with '*') uniquify-ignore-buffers-re "^\\*")
Bind undo/redo to sane bindings.
(require 'undo-tree) (global-set-key (kbd "M-z") 'undo) (global-set-key (kbd "M-Z") 'undo-tree-redo)
Interactive functions to encode/decode region for URLs.
(defun url-encode-region (beg end) "URL encode the region between BEG and END." (interactive "r") (if (use-region-p) (let* ((selected-text (buffer-substring beg end)) (encoded-text (url-hexify-string selected-text))) (kill-region beg end) (insert encoded-text)))) (defun url-decode-region (beg end) "URL decode the region between BEG and END." (interactive "r") (if (use-region-p) (let* ((selected-text (buffer-substring beg end)) (decoded-text (url-unhex-string selected-text))) (kill-region beg end) (insert decoded-text))))
Shell
;; make these environment variables available in Emacs (with-eval-after-load 'exec-path-from-shell (dolist (var '("SSH_AUTH_SOCK" "SSH_AGENT_PID" "GPG_AGENT_INFO" "LANG" "LC_CTYPE" "LEDGER_FILE" "WORKON_HOME")) (add-to-list 'exec-path-from-shell-variables var))) (when (memq window-system '(mac ns)) (exec-path-from-shell-initialize))
UI
Configure UI stuff like:
- hide toolbars
- hide GUI scrollbars, use in-buffer scrollbars instead with
yascroll - show indentation guide (useful for Python and HTML)
(require 'yascroll) (require 'indent-guide) ;; don't show toolbar (tool-bar-mode -1) ;; don't show menubar (menu-bar-mode -1) ;; highlight matching parentheses (show-paren-mode 1) ;; show line numbers (global-display-line-numbers-mode) (setq display-line-numbers-width 4) ;; workaround for annoying issue of shifting line number width ;; we use yascroll for the scrollbar instead (scroll-bar-mode -1) (global-yascroll-bar-mode 1) (setq yascroll:delay-to-hide nil) ;; show column number in mode-line (column-number-mode) (setq inhibit-splash-screen nil) (setq-default indicate-empty-lines t) ;; enable indent-guide for the following modes only (setq indent-guide-recursive nil) ;; (add-hook 'python-mode-hook 'indent-guide-mode) (add-hook 'web-mode-hook 'indent-guide-mode)
Enable highlight-symbol in select modes. Also patch how symbols are (not)
highlighted when holding down movement keys.
(dolist (hook '(prog-mode-hook html-mode-hook)) (add-hook hook 'highlight-symbol-mode) (add-hook hook 'highlight-symbol-nav-mode) (add-hook hook 'vimish-fold-mode)) ;(add-hook hook 'hs-minor-mode)) ;; http://emacs.stackexchange.com/questions/931 (defun highlight-symbol-mode-post-command () "After a command, change the temporary highlighting. Remove the temporary symbol highlighting and, unless a timeout is specified, create the new one." (if (eq this-command 'highlight-symbol-jump) (when highlight-symbol-on-navigation-p (highlight-symbol-temp-highlight)) (highlight-symbol-update-timer highlight-symbol-idle-delay))) (defun highlight-symbol-update-timer (value) (when highlight-symbol-timer (cancel-timer highlight-symbol-timer)) (setq highlight-symbol-timer (run-with-timer value nil 'highlight-symbol-temp-highlight))) (setq highlight-symbol-idle-delay .1)
Font
(defvar PragmataPro-font '(:family "PragmataPro" :size 13)) (defvar Go-font '(:family "Go Mono" :size 12)) (defvar Terminus-font '(:family "Terminus" :size 14)) (set-frame-font (apply 'font-spec PragmataPro-font) nil t) (when *is-a-mac* (set-fontset-font t 'symbol (font-spec :family "Apple Color Emoji") nil 'prepend))
Easily switch fonts.
(defun my-switch-font (font) (interactive "sSwitch font (1. PragmataPro 2. Go Mono 3. Terminus): ") (cond ((string= font "1") (set-frame-font (apply 'font-spec PragmataPro-font) nil t)) ((string= font "2") (set-frame-font (apply 'font-spec Go-font) nil t)) ((string= font "3") (set-frame-font (apply 'font-spec Terminus-font) nil t)) (t (message "Invalid option. Please choose 1 - 3."))))
Theme
Theme of the month.
(load-theme 'plan9 t)
Mode line
Show which function we are currently in in the mode line.
(which-function-mode)
Hide some minor modes from the mode line.
(rich-minority-mode) (setq rm-blacklist '(" hl-p" " hl-s" " $" " hs" " zf" " company" " GG" " FlyC" " Undo-Tree" " FlyC-" " Isearch" " Anaconda" " Anzu"))
Other
Enable pixelwise resizing of frames, so they can be properly aligned by our window manager.
(setq frame-resize-pixelwise t)
Ag
Highlight search results in the ag buffer.
(setq ag-highlight-search t)
ido, M-x
(ido-mode t) (ido-everywhere t) (flx-ido-mode t) (setq ido-enable-flex-matching t ido-use-filename-at-point nil ido-auto-merge-work-directories-length 0 ;; Allow the same buffer to be open in different frames ido-default-buffer-method 'selected-window)
Render ido candidates vertically.
(ido-vertical-mode t) (setq ido-vertical-define-keys 'C-n-and-C-p-only ido-vertical-show-count t)
Ignore dired buffers when using ido-switch-buffer, as we're only interested
in actual file buffers (and some internal buffers).
(defun ido-ignore-dired-buffers (name) "Ignore dired buffers" (with-current-buffer name (derived-mode-p 'dired-mode))) (add-to-list 'ido-ignore-buffers 'ido-ignore-dired-buffers)
Use ido in all interactions with M-x (i.e. provides ido-completion when doing M-x ledger-report, etc.)
(ido-ubiquitous-mode t)
Override M-x to use smex. Smex basically sorts commands by most-recently used.
(global-set-key (kbd "M-x") 'smex) (global-set-key (kbd "M-X") 'smex-major-mode-commands)
Swiper, Ivy
(setq ivy-use-virtual-buffers t) (global-set-key "\C-s" 'swiper) (global-set-key (kbd "C-c C-r") 'ivy-resume) (global-set-key (kbd "<f6>") 'ivy-resume)
Emulate Evil's * command with Swiper.
(global-set-key (kbd "C-M-s") (lambda () (interactive) (swiper (word-at-point))))
Window and frame management
Use M-g [h|j|k|l] to swap buffers between windows.
Also allow using numbers to switch window focus.
(require 'buffer-move) (require 'window-number) (dolist (fn '(buf-move-up buf-move-down buf-move-left buf-move-right)) (let ((file "buffer-move")) (autoload fn file "Swap buffers between windows" t))) (global-set-key (kbd "M-g h") 'buf-move-left) (global-set-key (kbd "M-g l") 'buf-move-right) (global-set-key (kbd "M-g k") 'buf-move-up) (global-set-key (kbd "M-g j") 'buf-move-down) (window-number-meta-mode t) (window-number-mode t) ;; need to refresh the mode line format for each new frame (add-hook 'window-configuration-change-hook (lambda () (window-number--update-mode-line-format)))
Cycle through a window's buffer history using C-M-,~ (backward) and ~C-M-. (forward).
(global-set-key (kbd "C-M-,") 'switch-to-prev-buffer) (global-set-key (kbd "C-M-.") 'switch-to-next-buffer)
Enable Edwina for dwm-like window management. Need to enable it at the end of init to avoid a bug where the init file stops loading when trying to enable edwina.
(add-hook 'after-init-hook #'edwina-mode)
Interactive searching
(global-anzu-mode t) (global-set-key [remap query-replace-regexp] 'anzu-query-replace-regexp) (global-set-key [remap query-replace] 'anzu-query-replace) ;; Activate occur easily inside isearch (define-key isearch-mode-map (kbd "C-o") 'isearch-occur)
Completion
company
Enable company-mode globally.
(require 'company) (add-hook 'after-init-hook #'global-company-mode)
Add miscellaneous backends.
(add-to-list 'company-backends 'company-restclient)
Flycheck
(setq flycheck-check-syntax-automatically '(save idle-change mode-enabled) flycheck-idle-change-delay 0.8) (add-hook 'after-init-hook #'global-flycheck-mode)
Language Specific
Python
Use Django style docstring format when filling docstrings.
(setq python-fill-docstring-style 'django)
- LSP
Emacs 27 has JSON improvements which make LSP perform LSP better. Use lsp-mode only when we are using Emacs 27+.
Enable LSP for Python.
(when (eq emacs-major-version 27) (require 'lsp-mode) (add-hook 'python-mode-hook #'lsp) (setq lsp-enable-snippet nil))
LSP requires a language server to be installed in the virtual environment. Calling this function will do that.
(defun lsp-python-install-packages () "Install required Python packages for lsp." (interactive) (if 'pyvenv-virtual-env (async-shell-command "pip install -U python-language-server") (message "No active virtualenv.")))
- elpy
Use elpy when we are using Emacs 26 or lower.
(unless (eq emacs-major-version 27) (elpy-enable))
elpyrequires certain Python packages to be installed in the virtualenv. These packages may not be included in therequirements.txtfile for some Python projects. This command would install these required packages.(defun elpy-install-packages () "Install required Python packages for elpy." (interactive) (if pyvenv-virtual-env (async-shell-command "pip install jedi flake8 autopep8 yapf") (message "No active virtualenv.")))
- Virtual Environments
Use
pyvenvto manage virtual environments in Emacs.Define a command that would prompt the user to choose or create a virtualenv for the current project, and set the dir-locals file for that project.
(defun my-python-project-dwim-virtualenv () (interactive) ;; check if .dir-locals.el file already exists and if project-venv-name is in it ;; prompt user to choose existing venv or create a new one ;; update .dir-locals.el file )
When switching focus to another frame, re-activate the proper virtualenv for that frame's project.
(add-hook 'focus-in-hook (lambda () (hack-local-variables) (if (boundp 'project-venv-name) (progn (message "Activating %s" project-venv-name) (pyvenv-workon project-venv-name)) (progn (message "Deactivating") (pyvenv-deactivate)))))
Show active virtualenv in mode line.
; (setq-default mode-line-format (cons '(:exec (concat "venv:" venv-current-name)) mode-line-format))
- EIN
The Emacs IPython Notebook package.
Interactive function to create a frame dedicated for EIN usage. Make sure to have a Jupyter server up and running first.
;;(defun my-ein-frame () ;; "Open a frame dedicated for EIN." ;; (interactive) ;; ;; TODO: check if jupyter server is up and running before launching the frame ;; (nameframe-with-frame "EIN" ;; (persp-switch "ein") ;; (call-interactively 'ein:notebooklist-login)))
YAML
(add-auto-mode 'yaml-mode "\\.ya?ml\\'")
HTML/CSS (web-mode)
We use web-mode for working with templates and enable it for the following
filetypes.
(add-to-list 'auto-mode-alist '("\\.jinja2?\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.css?\\'" . web-mode)) (setq web-mode-markup-indent-offset 4 web-mode-css-indent-offset 4 web-mode-code-indent-offset 4 web-mode-enable-auto-quoting nil web-mode-enable-block-face t web-mode-enable-current-element-highlight t)
Use the appropriate web-mode engine when visiting a particular filetype.
At the moment we default to the django engine for .html files.
If you are in a project that uses jinja2 for templates, and the file extensions
are in .html (a safe bet), then you'll need to define a .dir-locals.el file
for that project, telling it to use the appropriate engine.
(setq web-mode-engines-alist '(("jinja2" . "\\.jinja2\\'") ("django" . "\\.html\\'")))
Markdown
(add-to-list 'auto-mode-alist '("\\.\\(md\\|markdown\\)\\'" . markdown-mode))
Javascript
We use js2-mode instead of the built-in js-mode.
(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode)) (setq js2-use-font-lock-faces t js2-mode-must-byte-compile nil js2-basic-offset 2 js2-indent-on-enter-key t js2-auto-indent-p t js2-bounce-indent-p nil) (with-eval-after-load 'js2-mode (js2-imenu-extras-setup) (toggle-truncate-lines))
Lisp
Use pp-eval-expression. The same as eval-expression, but pretty-prints output.
(global-set-key (kbd "M-:") 'pp-eval-expression)
Define a list of "lispy" modes, so we can activate/deactivate stuff for all of them in a loop.
(require 'derived) ;; elisp only (defconst elispy-modes '(emacs-lisp-mode ielm-mode)) ;; all lisps (defconst lispy-modes (append elispy-modes '(lisp-mode inferior-lisp-mode lisp-interaction-mode clojure-mode)) "All lispy major modes.") (defun my-lisp-setup () "Enable features useful in any Lisp mode." ;; (rainbow-delimiters-mode t) ;; (hl-sexp-mode) (enable-paredit-mode) (turn-on-eldoc-mode) (highlight-parentheses-mode)) (dolist (hook (mapcar #'derived-mode-hook-name lispy-modes)) (add-hook hook 'my-lisp-setup))
Check parentheses on save.
(defun maybe-check-parens () "Run `check-parens' if this is a lispy mode." (when (memq major-mode lispy-modes) (check-parens))) (add-hook 'after-save-hook 'maybe-check-parens)
Dim parentheses for Lisps.
(global-paren-face-mode)
Clojure
Hide *nrepl-connection* and *nrepl-server* buffers.
(setq nrepl-hide-special-buffers t)
Set some variables in CIDER REPL and some hooks.
(setq cider-repl-use-clojure-font-lock t) (add-hook 'cider-repl-mode-hook 'subword-mode) (add-hook 'cider-repl-mode-hook 'paredit-mode) (add-hook 'cider-repl-mode-hook (lambda () (setq show-trailing-whitespace nil)))
Use clojure-mode for Clojurescript.
(add-auto-mode 'clojure-mode "\\.cljs\\'")
Elm
(add-hook 'elm-mode-hook #'elm-oracle-setup-completion)
Haskell
(add-hook 'haskell-mode-hook 'haskell-indentation-mode) (eval-after-load 'flycheck '(add-hook 'flycheck-mode-hook #'flycheck-haskell-setup))
Terraform
Use LSP for Terraform when using Emacs 27.
(when (eq emacs-major-version 27) (add-to-list 'lsp-language-id-configuration '(terraform-mode . "terraform")) (lsp-register-client (make-lsp-client :new-connection (lsp-stdio-connection '("terraform-lsp" "-enable-log-file")) :major-modes '(terraform-mode) :server-id 'terraform-ls)) (add-hook 'terraform-mode-hook #'lsp))
Use company-terraform when using Emacs 26 or below.
(unless (eq emacs-major-version 27) (add-to-list 'company-backends 'company-terraform))
Code Folding (HideShow)
(setq origami-fold-replacement "...")
Show the contents of the first 40 characters of the folded text and the number of lines folded.
(setq hs-set-up-overlay (defun my-hs-overlay (ov) (when (eq 'code (overlay-get ov 'hs)) (overlay-put ov 'display (propertize (format " ... %s <%d> ... " (replace-regexp-in-string "\n" "" (replace-regexp-in-string "^[ \t]*" "" (replace-regexp-in-string "[ \t]*$" "" (buffer-substring (overlay-start ov) (+ (overlay-start ov) 40))))) (count-lines (overlay-start ov) (overlay-end ov))) 'face 'diff-removed)))))
Dired
Don't hide details in dired.
(setq diredp-hide-details-initially-flag nil)
Define some keybindings for dired for quick navigation.
(defun bind-dired-utils-keys () (bind-keys :map dired-mode-map ("." . dired-up-directory) ("M-o" . dired-subtree-insert) ("M-c" . dired-subtree-remove) ("M-u" . dired-subtree-up) ("M-d" . dired-subtree-down) ("M-p" . dired-subtree-previous-sibling) ("M-n" . dired-subtree-next-sibling) ("M->" . dired-subtree-end) ("M-<" . dired-subtree-beginning) ("C-c d" . dired-filter-by-directory) ("C-c f" . dired-filter-by-file)))
Setup dired+.
(with-eval-after-load 'dired (require 'dired+) (require 'dired-subtree) (require 'dired-filter) (when (fboundp 'global-dired-hide-details-mode) (global-dired-hide-details-mode -1)) (setq dired-recursive-deletes 'top) (bind-dired-utils-keys) (define-key dired-mode-map [mouse-2] 'dired-find-file))
Open dired for the current directory when pressing C-x C-d.
(global-set-key (kbd "C-x C-d") '(lambda () (interactive) (dired ".")))
Omit uninteresting files in dired.
(add-hook 'dired-mode-hook (lambda () (dired-omit-mode)))
Org
Tell Org where our orgfiles are.
(setq org-directory "~/orgfiles")
Set custom TODO keywords.
(setq org-todo-keywords '((sequence "TODO" "DOING" "WAITING" "LATER" "|" "DONE" "DELEGATED" "CANCELED")))
Default notes file for org-capture.
(setq org-default-notes-file (concat org-directory "/notes.org"))
Set custom org-capture templates.
(setq org-capture-templates '(("t" "Todo" entry (file+headline (concat org-directory "/todo.org") "Other") "* TODO %?\n %i\n %a") ("n" "Note" entry (file+datetree (concat org-directory "/notes.org")) "* %?\nEntered on %U\n %i\n %a"))) (global-set-key (kbd "C-c o c") 'org-capture)
Add custom org-agenda command. We'd like to see at a glance:
- Our agenda for the week
- What we are currently working on
- List of remaining TODO items
(setq org-agenda-custom-commands '(("z" "Agenda and Tasks" ((agenda "") (todo "DOING") (todo "TODO")))))
Enable font-locking for org source blocks.
(setq org-src-fontify-natively t)
Don't evaluate source blocks when exporting.
(setq org-export-babel-evaluate nil)
Allow quotes to be verbatim2, 3.
(add-hook 'org-mode-hook (lambda () (setcar (nthcdr 2 org-emphasis-regexp-components) " \t\n,'") (org-set-emph-re 'org-emphasis-regexp-components org-emphasis-regexp-components) (org-element--set-regexps) (custom-set-variables `(org-emphasis-alist ',org-emphasis-alist))))
Enable the toc-org package, for generating and inserting table of
contents directly in the Org document itself (e.g. useful for Github
README.org files)
(add-hook 'org-mode-hook 'toc-org-enable)
Publishing
Allow exporting and publishing to ODT.
(require 'ox-odt) ;;;###autoload (defun org-odt-publish-to-odt (plist filename pub-dir) "Publish an org file to ODT. FILENAME is the filename of the Org file to be published. PLIST is the property list of the given project. PUB-DIR is the publishing directory. Return output file name." (unless (or (not pub-dir) (file-exists-p pub-dir)) (make-directory pub-dir t)) ;; Check if a buffer visiting FILENAME is already open. (let* ((org-inhibit-startup t) (visiting (find-buffer-visiting filename)) (work-buffer (or visiting (find-file-noselect filename)))) (unwind-protect (with-current-buffer work-buffer (let ((outfile (org-export-output-file-name ".odt" nil pub-dir))) (org-odt--export-wrap outfile (let* ((org-odt-embedded-images-count 0) (org-odt-embedded-formulas-count 0) (org-odt-object-counters nil) (hfy-user-sheet-assoc nil)) (let ((output (org-export-as 'odt nil nil nil (org-combine-plists plist `(:crossrefs ,(org-publish-cache-get-file-property (expand-file-name filename) :crossrefs nil t) :filter-final-output (org-publish--store-crossrefs org-publish-collect-index ,@(plist-get plist :filter-final-output)))))) (out-buf (progn (require 'nxml-mode) (let ((nxml-auto-insert-xml-declaration-flag nil)) (find-file-noselect (concat org-odt-zip-dir "content.xml") t))))) (with-current-buffer out-buf (erase-buffer) (insert output)))))))) (unless visiting (kill-buffer work-buffer))))
Fix the path to the soffice program on macOS.
(setq org-odt-convert-processes '(("LibreOffice" "/Applications/LibreOffice.app/Contents/MacOS/soffice --headless --convert-to %f%x --outdir %d %i")))
Configure publishing of our orgfiles.
(setq org-export-date-timestamp-format "%Y-%m-%d") (defun my-website-sitemap-function (project &optional sitemap-filename) "Custom sitemap generator that inserts additional options." (let ((sitemap (org-publish-sitemap-default project sitemap-filename))) (concat sitemap "\n\n#+OPTIONS: html-preamble:nil" "\n#+SUBTITLE: a.k.a. john2x" "\n#+HTML_HEAD_EXTRA: <style>body { font-family: CMU Serif, serif; margin: auto; max-width:768px; };</style>" (format "\n#+DATE:%s" (format-time-string "%Y-%m-%d"))))) (defun my-website-html-postamble (options) (concat "<hr>" (if (and (plist-get options ':keywords) (not (string= (plist-get options ':keywords) ""))) (format "<p>Keywords: %s</p>" (plist-get options ':keywords)) "") (format "<p class=\"date\">Modified: %s</p>" (format-time-string "%Y-%m-%d %H:%M:%S %Z")) (format "<p>Copyright (c) %s %s</p>" (format-time-string "%Y") ;; TODO: get from document options (car (plist-get options ':author))) (format "<p>%s</p>" (plist-get options ':creator)))) (setq org-publish-project-alist `(("orgfiles" :base-directory "~/Dropbox/orgfiles" :publishing-directory "~/Dropbox/orgfiles/published" :publishing-function org-html-publish-to-html :section-numbers nil :table-of-contents nil :recursive t :auto-sitemap t :sitemap-filename "sitemap.org" :sitemap-title "orgfiles") ("website-images" :base-directory "~/projects/misc/john2x.gitlab.io/images/" :base-extension "png\\|jpg\\|ico\\|gif" :publishing-directory "~/projects/misc/john2x.gitlab.io/public/images/" :publishing-function org-publish-attachment) ("website-static" :base-directory "~/projects/misc/john2x.gitlab.io/static/" :base-extension "css\\|js" :publishing-directory "~/projects/misc/john2x.gitlab.io/public/static/" :publishing-function org-publish-attachment) ("website-others" :base-directory "~/projects/misc/john2x.gitlab.io/" :base-extension "txt\\|xml\\|pdf\\|html" :publishing-directory "~/projects/misc/john2x.gitlab.io/public/" :publishing-function org-publish-attachment) ("website-content" :base-directory "~/projects/misc/john2x.gitlab.io/" :publishing-directory "~/projects/misc/john2x.gitlab.io/public/" :recursive t :exclude "level-.*\\|.*\.draft\.org\\|README\.org" :publishing-function org-html-publish-to-html :auto-sitemap t :sitemap-title "John Louis Del Rosario" :sitemap-filename "index.org" :sitemap-sort-files anti-chronologically :sitemap-function my-website-sitemap-function :html-link-up "/" :html-link-home "/" :html-preamble "<p class=\"date\">Published: %d</p>" :html-postamble my-website-html-postamble) ("website" :components ("website-content" "website-images" "website-others" "website-static"))))
Journal
Experiment with org-journal for a personal diary of sorts.
(setq org-journal-dir (concat org-directory "/journal/"))
Org Babel
Load additional languages.
; (org-babel-do-load-languages ; 'org-babel-load-languages ; '((ipython . t)))
Set additional templates.
(setq org-structure-template-alist (append org-structure-template-alist '(("sel" "#+BEGIN_SRC emacs-lisp?\n\n#+END_SRC") ("sip" "#+BEGIN_SRC ipython :session?\n\n#+END_SRC") ("ex" "#+BEGIN_EXAMPLE\n\n#+END_EXAMPLE"))))
Git
Show git status indicators in the fringe.
(global-git-gutter-mode 1) (git-gutter:linum-setup) (setq git-gutter:modified-sign "* " git-gutter:added-sign "+ " git-gutter:deleted-sign "- " git-gutter:lighter " GG") (global-set-key (kbd "M-g M-p") 'git-gutter:previous-hunk) (global-set-key (kbd "M-g M-n") 'git-gutter:next-hunk)
Package for yanking/killing links to Git repository files.
(require 'browse-at-remote)
Magit
;; skip warning introduced by 1.4.0 (setq magit-last-seen-setup-instructions "1.4.0") (setq-default magit-save-some-buffers nil magit-process-popup-time 10 magit-diff-refine-hunk t magit-restore-window-configuration t magit-completing-read-function 'magit-ido-completing-read magit-revert-buffers nil) (global-set-key (kbd "C-c m m") 'magit-status)
Make the Magit buffer take the entire frame.
(with-eval-after-load 'magit (fullframe magit-status magit-mode-quit-window))
Projectile and frame management
Configure Projectile for project management and navigation.
(projectile-global-mode) (diminish 'projectile-mode) (setq projectile-switch-project-action 'projectile-dired projectile-completion-system 'ido projectile-enable-caching t) (global-set-key (kbd "C-x p") 'projectile-find-file)
Configure perspective and nameframe to have dedicated
frames for each Projectile project.
(persp-mode) (nameframe-projectile-mode 1) (nameframe-perspective-mode 1) (global-set-key (kbd "M-P") 'nameframe-switch-frame)
Not Projectile, but still project management related.
(global-set-key (kbd "<f3>") 'project-explorer-toggle)
ERC
Set some default values. We don't want to auto-reconnect too much since it could flood the channel and get us temporarily banned.
(setq erc-nick "john2x" erc-nick-uniquifier "_" erc-server-auto-reconnect t erc-server-reconnect-timeout 15)
Change header-line face when disconnected.
(defface erc-header-line-disconnected '((t (:inherit magit-diff-removed))) "Face to use when ERC has been disconnected.") (defun erc-update-header-line-show-disconnected () "Use a different face in the header-line when disconnected." (erc-with-server-buffer (cond ((erc-server-process-alive) 'erc-header-line) (t 'erc-header-line-disconnected)))) (setq erc-header-line-face-method 'erc-update-header-line-show-disconnected)
Interactive function to create a frame dedicated to ERC and automatically connect to preset servers. (We don't join channels automatically as it could take too long.)
(defun my-erc-frame () "Switch or create to a frame called 'ERC' and connect to IRC" (interactive) (nameframe-with-frame "ERC" (erc-tls :server "znc.john2x.com" :port "5000" :nick "john2x")))
When reconnect attempts fail, have a convenient shortcut to reconnect manually.
(with-eval-after-load 'erc (define-key erc-mode-map (kbd "C-c C-r") (lambda () (interactive) (erc-server-reconnect))))
When reconnecting, don't bring any channels up into the current buffer.
(setq erc-join-buffer 'window-noselect)
When using a VPN, freenode.net (and probably other servers as well) requires us to authenticate with SASL. Unfortunately, SASL support isn't implemented yet in the default ERC package bundled with Emacs.
There's an erc-sasl library4 but it requires patching the erc-login function so it sends the appropriate
CAP request for SASL. Until erc-sasl gets merged into the main ERC package, we'll have to patch it here.
(require 'erc-sasl) (add-to-list 'erc-sasl-server-regexp-list "irc\\.freenode\\.net") (defun erc-login () "Perform user authentication at the IRC server. (PATCHED)" (erc-log (format "login: nick: %s, user: %s %s %s :%s" (erc-current-nick) (user-login-name) (or erc-system-name (system-name)) erc-session-server erc-session-user-full-name)) (if erc-session-password (erc-server-send (format "PASS %s" erc-session-password)) (message "Logging in without password")) (when (and (featurep 'erc-sasl) (erc-sasl-use-sasl-p)) (erc-server-send "CAP REQ :sasl")) (erc-server-send (format "NICK %s" (erc-current-nick))) (erc-server-send (format "USER %s %s %s :%s" ;; hacked - S.B. (if erc-anonymous-login erc-email-userid (user-login-name)) "0" "*" erc-session-user-full-name)) (erc-update-mode-line))
.ircauthinfo is where we store our NickServ passwords, so we don't have to type it in all the time
(and it breaks erc-login prompt when SASL is required).
(add-to-list 'auth-sources "~/.emacs.d/.ircauthinfo")
Set the prompt to use the channel name.
(setq erc-prompt (lambda () (concat (buffer-name) " > ")))
Add a /FLUSH command to flush the ERC buffer of contents.
(defun erc-cmd-FLUSH (&rest ignore) "Erase the current buffer." (let ((inhibit-read-only t)) (buffer-disable-undo) (erase-buffer) (buffer-enable-undo) (message "Flushed contents of channel") t))
Set the fill prefix to a constant value, instead of basing it off the username.
(setq erc-fill-prefix " ↳ ")
Channel tracking is for keeping track of activity in channels which are currently not visible on some frame/window. Ignore tracking the following types of messages.
(setq erc-track-exclude-types '("JOIN" "NICK" "PART" "QUIT"))
Disable line numbers for ERC buffers.
(add-hook 'erc-mode-hook (lambda () (display-line-numbers-mode -1)))
Send messages with C-RET instead of just RET to avoid accidentally pasting text into
an ERC buffer and pressing Enter.
(with-eval-after-load 'erc (define-key erc-mode-map (kbd "<C-return>") 'erc-send-current-line) (define-key erc-mode-map (kbd "RET") '(lambda () (interactive) (message "Send with C-return"))))
Modules
Highlight nicknames so they're easier to spot.
(require 'erc-highlight-nicknames) (add-to-list 'erc-modules 'highlight-nicknames)
Use the services module to automatically attempt to identify with NickServ when connection to a server.
(add-to-list 'erc-modules 'services)
Save logs when leaving a channel.
(add-to-list 'erc-modules 'log) (setq erc-save-buffer-on-part t) (setq erc-log-channels-directory "~/.erc/logs")
Render smiley icons, because why not :-)?
(add-to-list 'erc-modules 'smiley)
Finally, reload ERC's modules.
(erc-update-modules)
Ledger
(defconst *ledger-journal-path* "~/Dropbox/ledger/john.ledger") (defconst *ledger-docs-dir* "~/Dropbox/ledger/") (add-to-list 'auto-mode-alist '("\\.ledger$" . ledger-mode)) (add-hook 'ledger-mode-hook 'goto-address-prog-mode) ;; don't override the highlighting of each posted item ;; in a xact if it is cleared/pending (setq ledger-fontify-xact-state-overrides nil) ;; (defun my-ledger-frame () ;; "Easy way to open my ledger journal" ;; (interactive) ;; (nameframe-with-frame "ledger" ;; (persp-switch "ledger") ;; (find-file *ledger-journal-path*) ;; (split-window-right) ;; (find-file-other-window (concat *ledger-docs-dir* "Accounts.ledger")) ;; (split-window-below) ;; (window-number-select 1) ;; (ledger-report "bal" nil) ;; (toggle-frame-maximized))) (with-eval-after-load 'flycheck (require 'flycheck-ledger))
Evil
Evil is meant to be enabled globally.
(evil-mode 1)
But we only want Normal state for particular modes, and use Emacs state everywhere else.
So first, we set Emacs state as Evil's default state.
(setq-default evil-default-state 'emacs)
We then clear Evil's whitelists of modes that should start in a particular state, so they all start in Emacs state.
(setq-default evil-insert-state-modes '())
Then we specify which modes we want Normal state for.
(setq-default evil-normal-state-modes '(clojure-mode python-mode ruby-mode erlang-mode emacs-lisp-mode web-mode css-mode js2-mode js-mode json-mode html-mode ledger-mode yaml-mode elixir-mode org-mode sh-mode haskell-mode elm-mode purescript-mode markdown-mode terraform-mode))
Set the evil-leader.
(require 'evil-leader) (evil-leader/set-leader ",") (global-evil-leader-mode) (evil-leader/set-key "a g" 'ag)
Enable Evil plugins.
(global-evil-surround-mode 1) (global-evil-matchit-mode 1) (global-evil-search-highlight-persist t) (evilnc-default-hotkeys) (with-eval-after-load 'evil (require 'evil-anzu) (require 'evil-vimish-fold)) (evil-vimish-fold-mode 1)
Use SPACE for scrolling.
(define-key evil-normal-state-map (kbd "SPC") 'evil-scroll-down) (define-key evil-normal-state-map (kbd "S-SPC") 'evil-scroll-up)
Bind some keys on the leader.
(evil-leader/set-key "n" 'evil-search-highlight-persist-remove-all) (evil-leader/set-key "w" 'evil-write) (defun my-evil-reload-buffer () (interactive) (evil-edit nil t)) (evil-leader/set-key "e" 'my-evil-reload-buffer)
By default, C-u is bound to Emacs' universal-argument function, a rather important function used by various commands.
But in Vim, C-u is supposed to scroll up half a page, and that has been burned into muscle memory by now.
As a compromise, we bind universal-argument to M-u (which previously performs upcase-word, something we rarely, if ever, use),
and use Vim's version of C-u to scroll up half a page.
(global-set-key (kbd "M-u") 'universal-argument) (define-key universal-argument-map (kbd "M-u") 'universal-argument-more) (with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "C-u") 'evil-scroll-up))
Fix visual select bug on macOS.
(fset 'evil-visual-update-x-selection 'ignore)
evil-nerd-commenter defines a global key binding for C-c p, which
we do not use. Remove this binding and rebind the C-c p p binding
for projectile-swith-project.
(global-unset-key (kbd "C-c p")) (global-set-key (kbd "C-c p p") 'projectile-switch-project)