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))
elpy
requires certain Python packages to be installed in the virtualenv. These packages may not be included in therequirements.txt
file 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
pyvenv
to 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)