Skip to content

Latest commit

 

History

History
2381 lines (2174 loc) · 81.1 KB

emacs.org

File metadata and controls

2381 lines (2174 loc) · 81.1 KB

Configuration infrastructure

Bootstrap

Emacs does not source this file automatically, so I need to instruct it to. Check org-babel documentation for more info. The following snippet is an adaptation of that idea and goes to my .emacs.d/init.el.

;;; init.el --- The start of my configuration
;;; Commentary:
;;; Code:

(require 'package)
(setq package-enable-at-startup nil)
(package-initialize)

(defvar rasen/dotfiles-directory
  (file-name-as-directory
   (expand-file-name ".." (file-name-directory (file-truename user-init-file))))
  "The path to the dotfiles directory.")

(require 'org-install)
(require 'ob-tangle)

(require 'el-patch)

;; org-babel fixes to tangle ALL matching sections
(defun rasen/map-regex (regex fn)
  "Map the REGEX over the BUFFER executing FN.

FN is called with the match-data of the regex.

Returns the results of the FN as a list."
  (save-excursion
    (goto-char (point-min))
    (let (res)
      (save-match-data
        (while (re-search-forward regex nil t)
          (let ((f (match-data)))
            (setq res
                  (append res
                          (list
                           (save-match-data
                             (funcall fn f))))))))
      res)))

(el-patch-feature ob-core)
(el-patch-defun org-babel-expand-noweb-references (&optional info parent-buffer)
  "Expand Noweb references in the body of the current source code block.

For example the following reference would be replaced with the
body of the source-code block named `example-block'.

<<example-block>>

Note that any text preceding the <<foo>> construct on a line will
be interposed between the lines of the replacement text.  So for
example if <<foo>> is placed behind a comment, then the entire
replacement text will also be commented.

This function must be called from inside of the buffer containing
the source-code block which holds BODY.

In addition the following syntax can be used to insert the
results of evaluating the source-code block named `example-block'.

<<example-block()>>

Any optional arguments can be passed to example-block by placing
the arguments inside the parenthesis following the convention
defined by `org-babel-lob'.  For example

<<example-block(a=9)>>

would set the value of argument \"a\" equal to \"9\".  Note that
these arguments are not evaluated in the current source-code
block but are passed literally to the \"example-block\"."
  (let* ((parent-buffer (or parent-buffer (current-buffer)))
         (info (or info (org-babel-get-src-block-info 'light)))
         (lang (nth 0 info))
         (body (nth 1 info))
         (ob-nww-start org-babel-noweb-wrap-start)
         (ob-nww-end org-babel-noweb-wrap-end)
         (new-body "")
         (nb-add (lambda (text) (setq new-body (concat new-body text))))
         index source-name evaluate prefix)
    (with-temp-buffer
      (setq-local org-babel-noweb-wrap-start ob-nww-start)
      (setq-local org-babel-noweb-wrap-end ob-nww-end)
      (insert body) (goto-char (point-min))
      (setq index (point))
      (while (and (re-search-forward (org-babel-noweb-wrap) nil t))
        (save-match-data (setf source-name (match-string 1)))
        (save-match-data (setq evaluate (string-match "(.*)" source-name)))
        (save-match-data
          (setq prefix
                (buffer-substring (match-beginning 0)
                                  (save-excursion
                                    (beginning-of-line 1) (point)))))
        ;; add interval to new-body (removing noweb reference)
        (goto-char (match-beginning 0))
        (funcall nb-add (buffer-substring index (point)))
        (goto-char (match-end 0))
        (setq index (point))
        (funcall
         nb-add
         (with-current-buffer parent-buffer
           (save-restriction
             (widen)
             (mapconcat ;; Interpose PREFIX between every line.
              #'identity
              (split-string
               (if evaluate
                   (let ((raw (org-babel-ref-resolve source-name)))
                     (if (stringp raw) raw (format "%S" raw)))
                 (or
                  ;; Retrieve from the Library of Babel.
                  (nth 2 (assoc-string source-name org-babel-library-of-babel))
                  ;; Return the contents of headlines literally.
                  (save-excursion
                    (when (org-babel-ref-goto-headline-id source-name)
                      (org-babel-ref-headline-body)))
                  ;; Find the expansion of reference in this buffer.
                  (save-excursion
                    (goto-char (point-min))
                    (let* ((name-regexp
                            (org-babel-named-src-block-regexp-for-name
                             source-name))
                           (comment
                            (string= "noweb"
                                     (cdr (assq :comments (nth 2 info)))))
                           (c-wrap
                            (lambda (s)
                              ;; Comment, according to LANG mode,
                              ;; string S.  Return new string.
                              (with-temp-buffer
                                (funcall (intern (concat lang "-mode")))
                                (comment-region (point)
                                                (progn (insert s) (point)))
                                (org-trim (buffer-string)))))
                           (expand-body
                            (lambda (i)
                              ;; Expand body of code blocked
                              ;; represented by block info I.
                              (let ((b (if (org-babel-noweb-p (nth 2 i) :eval)
                                           (org-babel-expand-noweb-references i)
                                         (nth 1 i))))
                                (if (not comment) b
                                  (let ((cs (org-babel-tangle-comment-links i)))
                                    (concat (funcall c-wrap (car cs)) "\n"
                                            b "\n"
                                            (funcall c-wrap (cadr cs)))))))))
                      (if (re-search-forward name-regexp nil t)
                          (el-patch-swap
                            (funcall expand-body
                                     (org-babel-get-src-block-info 'light))
                            ;; Found a source block named SOURCE-NAME.
                            ;; Assume it is unique; do not look after
                            ;; `:noweb-ref' header argument.
                            (mapconcat
                             #'identity
                             (rasen/map-regex name-regexp
                                              (lambda (md)
                                                (funcall expand-body
                                                         (org-babel-get-src-block-info 'light))))
                             "\n"))
                        ;; Though luck.  We go into the long process
                        ;; of checking each source block and expand
                        ;; those with a matching Noweb reference.
                        (let ((expansion nil))
                          (org-babel-map-src-blocks nil
                            (let* ((info (org-babel-get-src-block-info 'light))
                                   (parameters (nth 2 info)))
                              (when (equal source-name
                                           (cdr (assq :noweb-ref parameters)))
                                (push (funcall expand-body info) expansion)
                                (push (or (cdr (assq :noweb-sep parameters))
                                          "\n")
                                      expansion))))
                          (when expansion
                            (mapconcat #'identity
                                       (nreverse (cdr expansion))
                                       ""))))))
                  ;; Possibly raise an error if named block doesn't exist.
                  (if (or org-babel-noweb-error-all-langs
                          (member lang org-babel-noweb-error-langs))
                      (error "%s could not be resolved (see \
`org-babel-noweb-error-langs')"
                             (org-babel-noweb-wrap source-name))
                    "")))
               "[\n\r]")
              (concat "\n" prefix))))))
      (funcall nb-add (buffer-substring index (point-max))))
    new-body))

(org-babel-load-file (expand-file-name "emacs.org" rasen/dotfiles-directory))
;;; init.el ends here

You might notice that I don’t change load-path—that’s because my setup relies on org-plus-contrib to be installed by NixOS.

package

All emacs packages are installed with Nix. Disable usage of emacs internal archives.

(require 'package)
(setq package-archives nil)
(setq package-enable-at-startup nil)
(package-initialize)

use-package

use-package is a cool emacs library that helps managing emacs configuration making it simpler and more structured. It is the core of my configuration infrastructure and is required

Initialize

; Do not ensure packages---they are installed with Nix
(setq use-package-always-ensure nil)
; (setq use-package-verbose t)
(eval-when-compile
  (require 'use-package))
(require 'bind-key)

Other

Quickly open configuration file.

(global-set-key
 (kbd "<f12>")
 (lambda ()
   (interactive)
   (find-file (expand-file-name "emacs.org" rasen/dotfiles-directory))))

Quickly open global system configuration file.

(global-set-key
 (kbd "<C-f12>")
 (lambda ()
   (interactive)
   (find-file (expand-file-name "README.org" rasen/dotfiles-directory))))

String interpolation

I use string interpolation in the main README.org.

This macro copied from here.

(defmacro rasen/interpolate-string (text)
  "Expand text like \"Hello <<name>>\" to (format \"Hello %s\" name)."
  (let ((pattern "<<\\(.*?\\)>>"))
    ;; The regexp matches anything between delimiters, non-greedily
    (with-temp-buffer
      (save-excursion (insert text))
      (let ((matches '()))
        (while (re-search-forward pattern nil t)
          (push (match-string 1) matches)
          (replace-match "%s" t t))
`(format ,(buffer-string) ,@(reverse (mapcar 'read matches)))))))

Evil

General

(use-package evil
  :init
  (setq evil-want-integration nil)
  (setq evil-want-keybinding nil)
  :config
  <<evil-config>>
  (evil-mode 1))

A couple of functions to make configuration a little bit easier.

(defun nmap (key action)
  (define-key evil-normal-state-map (kbd key) action))
(defun vmap (key action)
  (define-key evil-visual-state-map (kbd key) action))
(defun imap (key action)
  (define-key evil-insert-state-map (kbd key) action))
(defun mmap (key action)
  (define-key evil-motion-state-map (kbd key) action))
(defun omap (key action)
  (define-key evil-operator-state-map (kbd key) action))

Use SPC as one of leaders.

(nmap "SPC" nil)
(vmap "SPC" nil)
(mmap "SPC" nil)

Hard way: prohibit usage of keybinding I have more efficient binding for.

(defmacro rasen/hard-way (key)
  `(lambda () (interactive) (error "Don't use this key! Use %s instead" ,key)))

Prohibit usage of arrows.

(mmap "<left>"  (rasen/hard-way "h"))
(mmap "<up>"    (rasen/hard-way "j"))
(mmap "<down>"  (rasen/hard-way "k"))
(mmap "<right>" (rasen/hard-way "l"))

Swap . and ;.

(nmap ";"       'evil-repeat)
(nmap "C-;"     'evil-repeat-pop)
(mmap "."       'evil-repeat-find-char)
(nmap "."       nil)
(mmap "g."      'goto-last-change)
(mmap "SPC ;" 'eval-expression)

Close other window.

(defun rasen/quit-other ()
  (interactive)
  (other-window 1)
  (quit-window))

(mmap "SPC q"   'rasen/quit-other)

Move to beginning/end of line with H and L respectively.

(defun rasen/smart-move-beginning-of-line (arg)
  "Move point back to indentation of beginning of line.

Move point to the first non-whitespace character on this line.
If point is already there, move to the beginning of the line.
Effectively toggle between the first non-whitespace character and
the beginning of the line.

If ARG is not nil or 1, move forward ARG - 1 lines first.  If
point reaches the beginning or end of the buffer, stop there."
  (interactive "^p")
  (setq arg (or arg 1))

  ;; Move lines first
  (when (/= arg 1)
    (let ((line-move-visual nil))
      (forward-line (1- arg))))

  (let ((orig-point (point)))
    (back-to-indentation)
    (when (= orig-point (point))
      (move-beginning-of-line 1))))

(mmap "H" 'rasen/smart-move-beginning-of-line)
(mmap "L" 'evil-end-of-line)

Save buffer with SPC SPC.

(nmap "SPC SPC" 'save-buffer)

Swap k and j

With workman layout, j is located on qwerty y and k—on qwerty n; thus j is higher than k, and it is not convenient to press lower key for going up. Just swap them.

(mmap "k"       'evil-next-visual-line)
(mmap "j"       'evil-previous-visual-line)
(mmap "gk"      'evil-next-line)
(mmap "gj"      'evil-previous-line)

(omap "k"       'evil-next-line)
(omap "j"       'evil-previous-line)
(omap "gk"      'evil-next-visual-line)
(omap "gj"      'evil-previous-visual-line)

(mmap "C-h"     'windmove-left)
(mmap "C-k"     'windmove-down)
(mmap "C-j"     'windmove-up)
(mmap "C-l"     'windmove-right)

evil-numbers

I use Vim’s C-a and C-x (increment/decrement number at point) a lot. evil-numbers provides that functionality for evil.

(use-package evil-numbers
  :after evil
  :bind (:map evil-normal-state-map
         ("C-a" . evil-numbers/inc-at-pt)
         ("C-x" . evil-numbers/dec-at-pt)))

Now, remap C-x to RET. (Because C-x is used for decrementing numbers.)

(mmap "RET" (lookup-key (current-global-map) (kbd "C-x")))

swap-keys

(use-package evil-swap-keys
  :config
  (global-evil-swap-keys-mode)
  (add-hook 'prog-mode-hook #'evil-swap-keys-swap-number-row))

evil-collection

evil-collection is a collection of evil bindings for different modes.

(require 'warnings)
(add-to-list 'warning-suppress-types '(evil-collection))

(use-package evil-collection
  :after (evil evil-magit)
  :config
  (defun rasen/rotate-keys (_mode mode-keymaps &rest _rest)
    (evil-collection-translate-key 'normal mode-keymaps
     "k" "j"
     "j" "k"
     "gk" "gj"
     "gj" "gk"
     (kbd "M-j") (kbd "M-k")
     (kbd "M-k") (kbd "M-j")
     (kbd "C-j") nil ; used for window-management
     (kbd "C-k") nil ; used for window-management
     "." ";"
     ";" "."))
  (add-hook 'evil-collection-setup-hook #'rasen/rotate-keys)

  (setq evil-collection-mode-list
    '(compile
      flycheck
      help
      js2-mode
      magit
      ;; notmuch bindings aren't that cool and are less efficient than native
      ;; keymap
      ; notmuch
      python
      racer
      restclient
      tide
      typescript-mode
      which-key))

  (evil-collection-init))

Evilify compile mode

(use-package compile
  :config
  (setq compilation-scroll-output t))

And evil commands to go to navigate errors.

(mmap "SPC ,"   'previous-error)
(mmap "SPC ."   'next-error)
(mmap "M-,"     'previous-error)
(mmap "M-."     'next-error)

General

Common options

Use single-key y/n instead of a more verbose yes/no.

(fset 'yes-or-no-p 'y-or-n-p)

Do not use tabs for indentation.

(setq-default indent-tabs-mode nil)

Make ‘_’ a part of words, so commands like evil-forward-word-begin work properly.

(add-hook 'prog-mode-hook
          (lambda () (modify-syntax-entry ?_ "w")))

Don’t clutter system

Save custom configuration in the ~/.emacs.d/custom.el file so emacs does not clutter init.el.

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(load custom-file t)

Don’t clutter the current directory with backups. Save them in a separate directory.

(setq backup-directory-alist '(("." . "~/.emacs.d/backups")))

Don’t clutter the current directory with auto-save files.

(setq auto-save-file-name-transforms '((".*" "~/.emacs.d/backups/" t)))

Do not create lockfiles either. (I am the only user in the system and only use emacs through daemon, so that should be ok.)

(setq create-lockfiles nil)

emacs-server

(load "server")
(unless (server-running-p)
  (server-start))

ivy

(use-package ivy
  :demand
  :bind (:map evil-motion-state-map
         ("SPC b" . ivy-switch-buffer))
  :diminish ivy-mode
  :config

Do not start input with ^ and ignore the case.

(setq-default ivy-initial-inputs-alist nil)
(setq-default ivy-re-builders-alist '((t . ivy--regex-ignore-order)))

The normal C-j is not placed conveniently on Workman layout, so move its function to C-e (which is qwerty k).

(define-key ivy-minibuffer-map (kbd "C-e") 'ivy-alt-done)
(define-key ivy-minibuffer-map (kbd "C-M-e") 'ivy-immediate-done)

Remap k-j in ivy-occur, make it default to emacs state.

(dolist (map (list ivy-occur-mode-map ivy-occur-grep-mode-map))
  (evil-define-key 'normal map
    (kbd "k") 'ivy-occur-next-line
    (kbd "j") 'ivy-occur-previous-line
    (kbd "C-n") 'ivy-occur-next-line
    (kbd "C-p") 'ivy-occur-previous-line
    (kbd "RET") 'ivy-occur-press-and-switch
    (kbd "C-e") 'ivy-occur-press-and-switch
    (kbd "g r") 'ivy-occur-revert-buffer
    (kbd "d") 'ivy-occur-delete-candidate
    (kbd "r") 'read-only-mode
    (kbd "a") 'ivy-occur-read-action
    (kbd "c") 'ivy-occur-toggle-calling
    (kbd "f") 'ivy-occur-press
    (kbd "o") 'ivy-occur-dispatch
    (kbd "q") 'quit-window))
(evil-define-key 'normal ivy-occur-grep-mode-map
  (kbd "w") 'ivy-wgrep-change-to-wgrep-mode)
(ivy-mode 1))

smex

I use smex for improved counsel-M-x (show most frequently used commands first).

(use-package smex
  :config
  (smex-initialize))

counsel

(use-package counsel
  :demand
  :diminish counsel-mode
  :bind (:map evil-motion-state-map
         ("SPC x" . counsel-M-x)
         ("SPC f" . counsel-find-file)
         ("g r"   . counsel-git-grep)
         ("g /"   . counsel-rg)
         :map read-expression-map
         ("C-r" . counsel-expression-history))
  :config
  (counsel-mode 1))

avy

Jump anywhere with a few keystrokes in tree-like way.

(use-package avy
  :bind
  (:map evil-motion-state-map
   ("K" . avy-goto-char))
  :custom
  ;; easy workman keys (excluding pinky)
  (avy-keys '(?s ?h ?t ?n ?e ?o ?d ?r ?u ?p)))

wgrep

Edit grep buffers and apply changes to the files.

(use-package wgrep)

whitespace

A good mode to highlight whitespace issues (leading/trainiling spaces/newlines) and too long lines.

(use-package whitespace
  :diminish (global-whitespace-mode
             whitespace-mode
             whitespace-newline-mode)
  :config
  (setq-default whitespace-line-column 120
                whitespace-style '(face
                                   tab-mark
                                   empty
                                   trailing
                                   lines-tail))

Original face overrides foreground, so you don’t see syntax highlight. Use underlines to show characters past limit.

(set-face-attribute 'whitespace-line nil
                    :foreground nil
                    :background nil
                    :underline (list :color "yellow4" :style 'wave))

Activate the mode in all programming modes.

(add-hook 'prog-mode-hook 'whitespace-mode))

whitespace-cleanup

Fix whitespaces on file save.

(use-package whitespace-cleanup-mode
  :diminish whitespace-cleanup-mode
  :config
  (global-whitespace-cleanup-mode 1))

undo-tree

It’s enable by default. Just diminish it.

(use-package undo-tree
  :diminish (undo-tree-mode global-undo-tree-mode))

which-key

which-key is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup.

(use-package which-key
  :defer 2
  :diminish which-key-mode
  :config
  (which-key-mode))

nixos-sandbox

(use-package nix-sandbox
  :disabled
  :commands (nix-shell-command
             nix-shell
             nix-compile
             nix-find-sandbox
             nix-current-sandbox
             nix-executable-find))

projectile

(use-package projectile
  :bind (:map evil-motion-state-map
         ("SPC p p" . projectile-switch-project)
         ("SPC p &" . projectile-run-async-shell-command-in-root)
         ("SPC p !" . projectile-run-shell-command-in-root)
         ;; That works much better than the default
         ("g f"     . projectile-find-file-dwim)
         ("U"       . projectile-find-file)
         ("<f3>"    . projectile-test-project)
         ("<f4>"    . projectile-compile-project)
         ("<f5>"    . projectile-run-project))
  :commands (projectile-project-name)
  :diminish projectile-mode
  :config
  ;; Use the prefix arg if you want to change the compilation command
  (setq-default compilation-read-command nil)

  (setq-default projectile-use-git-grep t)

  (setq-default projectile-completion-system 'ivy)
  (projectile-mode))
(use-package counsel-projectile
  :after projectile
  :config
  (counsel-projectile-mode))

Install noccur to multi-occur project-wide.

(use-package noccur)

fix “was neither a function nor a string”

bbatsov/projectile#1269

(el-patch-feature projectile)
(with-eval-after-load 'projectile
  (el-patch-defun projectile-default-generic-command (project-type command-type)
    "Generic retrieval of COMMAND-TYPEs default cmd-value for PROJECT-TYPE.

If found, checks if value is symbol or string.  In case of symbol
resolves to function `funcall's.  Return value of function MUST
be string to be executed as command."
    (let ((command (plist-get (alist-get project-type projectile-project-types) command-type)))
      (cond
       ((stringp command) command)
       ((functionp command)
        (if (fboundp command)
            (funcall (symbol-function command))))
       ((and (not command) (eq command-type 'compilation-dir))
        ;; `compilation-dir' is special in that it is used as a fallback for the root
        nil)
       (el-patch-remove
         (t
          (user-error "The value for: %s in project-type: %s was neither a function nor a string." command-type project-type)))))))

magit

(use-package magit
  :bind (:map evil-motion-state-map
         ("g m" . magit-status))
  :diminish auto-revert-mode
  ; :defer 6
  :init
  (global-set-key (kbd "C-c m") (rasen/hard-way "g m"))
  :config
  <<magit-config>>
  )

Do not put files into trash can. Delete them for real.

(setq-default magit-delete-by-moving-to-trash nil)
(setq-default magit-completing-read-function 'ivy-completing-read)

Evil

Evilify magit-mode.

(use-package evil-magit
  :config
  <<evil-magit-config>>
  )
(setq evil-magit-use-y-for-yank t)

Evilify magit-blame.

(evil-define-key 'normal magit-blame-read-only-mode-map
  (kbd "k")  'evil-next-visual-line
  (kbd "j")  'evil-previous-visual-line
  (kbd "C-k") 'magit-blame-next-chunk
  (kbd "C-j") 'magit-blame-previous-chunk
  (kbd "gk") 'magit-blame-next-chunk-same-commit
  (kbd "gj") 'magit-blame-previous-chunk-same-commit)

(evil-define-key 'motion magit-blame-mode-map
  (kbd "SPC") (lookup-key evil-motion-state-map (kbd "SPC")))

(evil-define-key `(,evil-magit-state visual) magit-mode-map
  (kbd "j")   'evil-previous-visual-line
  (kbd "k")   'evil-next-visual-line
  (kbd "C-j") 'magit-section-backward
  (kbd "C-k") 'magit-section-forward
  (kbd "gj")  'magit-section-backward-sibling
  (kbd "gk")  'magit-section-forward-sibling)

Custom commands

git push HEAD …

Add a magit command to push HEAD into a specified ref.

(defun rasen/magit-push-head (target args)
  "Push HEAD to a branch read in the minibuffer."
  (interactive
   (list (magit-read-remote-branch "Push HEAD to"
                                   nil nil nil 'confirm)
         (magit-push-arguments)))
  (magit-git-push "HEAD" target args))

(if (fboundp 'transient-insert-suffix)
    (transient-insert-suffix 'magit-push 'magit-push-other
      '(1 "h" "HEAD" rasen/magit-push-head))
  (magit-define-popup-action 'magit-push-popup
                             ?h "HEAD" 'rasen/magit-push-head))

git fetch origin/master && git checkout origin/master

(evil-magit)

(defun rasen/magit-fco-master ()
  "Fetch origin/master and checkout it."
  (interactive)
  (magit-git-fetch "origin" "master")
  (magit-checkout "origin/master"))

(evil-magit-define-key evil-magit-state 'magit-mode-map
                       "g m" 'rasen/magit-fco-master)
Make it normal magit command and generalize to fetch-checkout anything

add a detach head command (git checkout HEAD)

GPG

Sign commits by default.

(setq magit-commit-arguments '("--gpg-sign=DCEF7BCCEB3066C3"))

Show commit signatures in log.

(setq magit-log-arguments '("--graph" "--decorate" "--show-signature" "-n256"))

diff-hl

diff-hl is an emacs package to highlight uncommitted changes.

(use-package diff-hl
  :after magit
  :config
  ; (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)
  ; (unless (display-graphic-p)
  ;   (diff-hl-margin-mode t))
  ; (diff-hl-flydiff-mode t)
  (global-diff-hl-mode t))

yasnippet

(use-package yasnippet
  :defer 5
  :diminish yas-minor-mode
  :config
  (yas-global-mode 1)

  (setq rasen/snippets-directory
        (file-name-as-directory
         (expand-file-name ".emacs.d/snippets" rasen/dotfiles-directory)))

  (make-directory rasen/snippets-directory t)
  (yas-load-directory rasen/snippets-directory)

  (add-hook 'term-mode-hook (lambda ()
                              (setq-local yas-dont-activate-functions t))))

company

Company mode provides autocomplete features.

(use-package company
  :defer 2
  :bind (:map evil-insert-state-map
         ("C-n" . company-complete-common-or-cycle)
         ("C-p" . company-select-previous)
        (:map company-active-map
         ("C-n" . company-complete-common-or-cycle)
         ("C-p" . company-select-previous-or-abort)
         ("C-e" . company-complete)
         ("TAB" . company-complete-common-or-cycle)))
  :diminish company-mode
  :config
  (setq-default company-dabbrev-downcase nil)
  (global-company-mode))

flycheck

(use-package flycheck
  :config
  ;; not sure I actually use nix-sandbox
  ; (setq flycheck-command-wrapper-function
  ;       (lambda (cmd) (apply 'nix-shell-command (nix-current-sandbox) cmd))

  ;       flycheck-executable-find
  ;       (lambda (cmd) (nix-executable-find (nix-current-sandbox) cmd)))

  ;; Do not check for elisp header/footer
  (setq-default flycheck-disabled-checkers
                (append flycheck-disabled-checkers
                        '(emacs-lisp-checkdoc)))

  (global-flycheck-mode))

electric-pair

Auto-close pairs.

(electric-pair-mode)

Hippie expand

(use-package hippie-exp
  :bind (:map evil-insert-state-map
         ("C-/" . hippie-expand))
  :config
  (setq hippie-expand-try-functions-list
        '(try-expand-dabbrev-visible
          try-expand-dabbrev
          try-expand-dabbrev-all-buffers
          try-complete-file-name-partially
          try-complete-file-name
          try-expand-line
          try-expand-list)))

Color identifiers

(use-package color-identifiers-mode
  :commands (color-identifiers-mode
             global-color-identifiers-mode)
  :diminish (color-identifiers-mode
             global-color-identifiers-mode))

Helpers

Shamelessly stealed from https://github.com/purcell/emacs.d.

(defun rename-this-file-and-buffer (new-name)
  "Renames both current buffer and file it's visiting to NEW-NAME."
  (interactive "FNew name: ")
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (unless filename
      (error "Buffer '%s' is not visiting file!" name))
    (if (get-buffer new-name)
        (message "A buffer named '%s' already exists!" new-name)
      (progn
        (when (file-exists-p filename)
          (rename-file filename new-name 1))
        (rename-buffer new-name)
        (set-visited-file-name new-name)))))

(defun delete-this-file-and-buffer ()
  "Delete the current file, and kill the buffer."
  (interactive)
  (or (buffer-file-name) (error "No file is currently being edited"))
  (when (yes-or-no-p (format "Really delete '%s'?"
                             (file-name-nondirectory buffer-file-name)))
    (delete-file (buffer-file-name))
    (kill-buffer)))
(defun add-to-path (str)
  "Add an STR to the PATH environment variable."
  (setenv "PATH" (concat str ":" (getenv "PATH"))))

Google translate

(use-package google-translate
  :bind (:map evil-motion-state-map
         ("g t" . rasen/google-translate-at-point)
         ("g T" . google-translate-smooth-translate))
  :config
  (defun rasen/google-translate-at-point (arg)
    "Translate word at point. If prefix is provided, do reverse translation"
    (interactive "P")
    (if arg
        (google-translate-at-point-reverse)
      (google-translate-at-point)))

  (require 'google-translate-default-ui)
  (require 'google-translate-smooth-ui)
  (setq google-translate-show-phonetic t)

  (setq google-translate-default-source-language "en"
        google-translate-default-target-language "ru")

  (setq google-translate-translation-directions-alist '(("en" . "ru") ("ru" . "en")))
  ; auto-toggle input method
  (setq google-translate-input-method-auto-toggling t
        google-translate-preferable-input-methods-alist '((nil . ("en"))
                                                          (russian-computer . ("ru")))))

Org-mode

General

(use-package org
  :mode ("\\.org$" . org-mode)
  :bind (("C-c l" . org-store-link)
         :map evil-motion-state-map
         ("SPC o" . org-clock-out)
         ("SPC l" . org-clock-in-last)
         ("SPC j" . org-clock-goto)
         ("SPC c" . org-capture)
         ("SPC a" . org-agenda)
         :map org-mode-map
         ("C-c ," . org-time-stamp-inactive))
  :ensure org-plus-contrib
  :init
  <<org-init>>
  :config
  <<org-config>>
  )

Do not indent inside tasks

(setq org-adapt-indentation nil)

Do not indent org-babel blocks.

(setq org-edit-src-content-indentation 0)

Do not indent tags.

(setq org-tags-column 0)

Set tags faces, so they are more distinguishable.

(set-face-attribute 'org-tag nil :inverse-video t)
(set-face-attribute 'org-archived nil :inverse-video nil)
(set-face-attribute 'org-ellipsis nil :inverse-video nil)
(setq org-tag-faces
      '(("ARCHIVE" . (:inverse-video nil))))

Open pdfs in external viewer:

(add-to-list 'org-file-apps '("\\.pdf\\'" . "zathura %s"))

Use whitespace-mode in Org (but don’t show too long lines).

(add-hook 'org-mode-hook (lambda ()
                           (setq-local whitespace-style '(face
                                                          tab-mark
                                                          empty
                                                          trailing))
                           (whitespace-mode t)))

My directory for org files.

(setq rasen/org-directory "~/org")

My helper to find all org files in a directory.

(defun rasen/org-files-in-dir (dir)
  (f-files dir
           (lambda (file) (f-ext? file "org"))
           nil))

Package for f-files and f-ext? functions.

(use-package f
  :commands (f-files f-ext?))

Drill

For some reason, org-drill fails to load with the following error:

Debugger entered–Lisp error: (void-function copy-list) copy-list((1 (quote org-drill-visible-cloze-face) nil)) org-drill–compute-cloze-keywords() (defvar org-drill-cloze-keywords (org-drill–compute-cloze-keywords) nil) require(org-drill)

I googled it up and copy-list seems to be defined in cl, so load it first:

(require 'cl)
(require 'org-drill)
(setq org-drill-scope (rasen/org-files-in-dir "~/org/drill"))
(add-to-list 'org-modules 'org-drill)

https://bitbucket.org/eeeickythump/org-drill/issues/62/org-drill-doesnt-work-with-org-mode-92

(el-patch-feature org-drill)
(el-patch-defun org-drill-hide-subheadings-if (test)
  "TEST is a function taking no arguments. TEST will be called for each
of the immediate subheadings of the current drill item, with the point
on the relevant subheading. TEST should return nil if the subheading is
to be revealed, non-nil if it is to be hidden.
Returns a list containing the position of each immediate subheading of
the current topic."
  (let ((drill-entry-level (org-current-level))
        (drill-sections nil))
    (org-show-subtree)
    (save-excursion
      (org-map-entries
       (lambda ()
         (when (and (not (org-invisible-p))
                    (> (org-current-level) drill-entry-level))
           (when (or (/= (org-current-level) (1+ drill-entry-level))
                        (funcall test))
             (hide-subtree))
           (push (point) drill-sections)))
       (el-patch-swap "" t) 'tree))
    (reverse drill-sections)))

Todo

Use the following states: TODO NEXT DONE CANCELED WAIT.

(setq-default org-todo-keywords
              '((sequence "TODO(t)" "NEXT(n!)" "|" "DONE(d!)")
                (sequence "|" "CANCELED(c@)")
                (sequence "WAIT(w@)" "|")))
(setq-default org-todo-keyword-faces
              '(("TODO"     . (:foreground "dodger blue" :weight bold))
                ("NEXT"     . (:box t :foreground "red" :weight bold))
                ("WAIT"     . (:box t :foreground "magenta" :weight bold))
                ("DONE"     . (:foreground "grey" :weight bold))
                ("CANCELED" . (:foreground "gray" :weight bold))))
(setq-default org-use-fast-todo-selection t)

Switch task state with SPC t.

(evil-define-key 'normal org-mode-map (kbd "SPC t") 'org-todo)

When repeated task is finished, go back to TODO state.

(setq-default org-todo-repeat-to-state "TODO")

Log state changes to “LOGBOOK” drawer.

(setq-default org-log-into-drawer 't)

Save CLOSED timestamp when task is done.

(setq org-log-done t)

Schedule task for today and mark it NEXT. I use this a lot during daily planning.

(defun rasen/org-do-today (arg)
  "Schedule task for today and mark it NEXT.

If prefix is supplied, select different scheduled time."
  (interactive "P")
  (org-schedule nil (unless arg "."))
  (org-todo "NEXT"))

(evil-define-key 'normal org-mode-map (kbd "SPC n") #'rasen/org-do-today)

(evil-define-key 'normal org-mode-map (kbd "SPC s") 'org-schedule)
(evil-define-key 'normal org-mode-map (kbd "C-c C-s") (rasen/hard-way "SPC s"))

Clocking

Remove clocks with 0 duration.

(setq-default org-clock-out-remove-zero-time-clocks t)

Save more last clocks.

(setq-default org-clock-history-length 10)

Capture

I use an extension that adds page url to the title (used for page tracking). Strip it down here

(defun rasen/strip-url-from-title (title)
  (message "stripping: %s" title)
  (replace-regexp-in-string
   " @ [^ ]*$"
   ""
   (replace-regexp-in-string " \\[[^]]*\\]\\[[^]]*\\]$" "" title)))

My capture templates.

(setq org-capture-templates
      `(("u"
         "Task: Read this URL"
         entry
         (file+headline "refile.org" "Articles To Read")
         ,(concat "* TODO %(rasen/strip-url-from-title \"%:description\")\n:PROPERTIES:\n:CREATED:  %U\n:END:\n%:link\n")
         :immediate-finish t)

        ("w"
         "Capture web snippet"
         entry
         (file+headline "refile.org" "Inbox")
         ,(concat "* Fact: '%(rasen/strip-url-from-title \"%:description\")'       :"
                  (format "%s" org-drill-question-tag)
                  ":\n:PROPERTIES:\n:CREATED:  %U\n:SOURCE_URL: %:link\n:END:\n%i\n%?\n")
         :immediate-finish t)

        ("j" "Journal entry" plain
         (file+datetree+prompt "~/org/journal.org")
         ,(concat
           ; %U does not here work as timestamp is hijacked by
           ; file+datetime+prompt
           "%(format-time-string (org-time-stamp-format t t))"
           "\n%?\n"))

        ("f"
         "Capture normal snippet"
         entry
         (file+headline "my-facts.org" "Inbox")
         ,(concat "* Fact: '%f'       :"
                  (format "%s" org-drill-question-tag)
                  ":\n:PROPERTIES:\n:CREATED:  %U\n:SOURCE_URL: [[%l][%f]]\n:END:\n%i\n%?\n")
         :immediate-finish t)

        ("t" "todo" entry (file "~/org/refile.org")
         "* TODO %?\n:PROPERTIES:\n:CREATED:  %U\n:END:\n" :clock-in t :clock-resume t)

        ("m" "meeting" entry (file "~/org/refile.org")
         "* %?   :Meeting:\n:PROPERTIES:\n:CREATED:  %U\n:END:\n" :clock-in t :clock-resume t)

        ("n" "note" entry (file "~/org/refile.org")
         "* %?\n:PROPERTIES:\n:CREATED:  %U\n:END:\n")))

Enable org-protocol.

(require 'org-protocol)

Instanly go into insert mode on capture.

(add-hook 'org-capture-mode-hook 'evil-insert-state)

%l in org-capture fails with multiline context, so use only the first line as a context.

(setq org-context-in-file-links 1)

Refile

(defun rasen/org-refile-files ()
  (rasen/org-files-in-dir rasen/org-directory))

;; non-nil values work bad with ivy
(setq-default org-refile-use-outline-path 'file)
(setq-default org-outline-path-complete-in-steps nil)

(setq org-refile-targets
      '(;(nil :maxlevel . 3)
        (org-agenda-files :tag . "honeypot")
        (org-agenda-files :tag . "PROJECT")
        (org-agenda-files :maxlevel . 2)
        (rasen/org-refile-files :maxlevel . 1)))

Better bindings.

(evil-define-key 'normal org-mode-map (kbd "SPC w") 'org-refile)
(evil-define-key 'normal org-mode-map (kbd "C-c C-w") (rasen/hard-way "SPC w"))

Refile last but before archive

I like my archive sibling be the last child. The default org-refile ignores that at refiles all entries after archive.

So here is a little patch to refile before archive sibling if it is present.

(defun rasen/org-goto-last-child ()
  "Goto the last child, even if it is invisible.
Return t when a child was found.  Otherwise don't move point and return nil."
  (when (org-goto-first-child)
    (while (org-goto-sibling))
    t))

(defun rasen/org-goto-last-archive ()
  (and (rasen/org-goto-last-child)
       (string= org-archive-sibling-heading (org-get-heading t t t t))
       (member org-archive-tag (org-get-tags))
       (point)))

(require 'org-archive) ; for org-archive-sibling-heading

(el-patch-feature org)
(el-patch-defun org-refile (&optional arg default-buffer rfloc msg)
  "Move the entry or entries at point to another heading.

The list of target headings is compiled using the information in
`org-refile-targets', which see.

At the target location, the entry is filed as a subitem of the
target heading.  Depending on `org-reverse-note-order', the new
subitem will either be the first or the last subitem.

If there is an active region, all entries in that region will be
refiled.  However, the region must fulfill the requirement that
the first heading sets the top-level of the moved text.

With a `\\[universal-argument]' ARG, the command will only visit the target \
location
and not actually move anything.

With a prefix `\\[universal-argument] \\[universal-argument]', go to the \
location where the last
refiling operation has put the subtree.

With a numeric prefix argument of `2', refile to the running clock.

With a numeric prefix argument of `3', emulate `org-refile-keep'
being set to t and copy to the target location, don't move it.
Beware that keeping refiled entries may result in duplicated ID
properties.

RFLOC can be a refile location obtained in a different way.

MSG is a string to replace \"Refile\" in the default prompt with
another verb.  E.g. `org-copy' sets this parameter to \"Copy\".

See also `org-refile-use-outline-path'.

If you are using target caching (see `org-refile-use-cache'), you
have to clear the target cache in order to find new targets.
This can be done with a `0' prefix (`C-0 C-c C-w') or a triple
prefix argument (`C-u C-u C-u C-c C-w')."
  (interactive "P")
  (if (member arg '(0 (64)))
      (org-refile-cache-clear)
    (let* ((actionmsg (cond (msg msg)
                            ((equal arg 3) "Refile (and keep)")
                            (t "Refile")))
           (regionp (org-region-active-p))
           (region-start (and regionp (region-beginning)))
           (region-end (and regionp (region-end)))
           (org-refile-keep (if (equal arg 3) t org-refile-keep))
           pos it nbuf file level reversed)
      (setq last-command nil)
      (when regionp
        (goto-char region-start)
        (or (bolp) (goto-char (point-at-bol)))
        (setq region-start (point))
        (unless (or (org-kill-is-subtree-p
                     (buffer-substring region-start region-end))
                    (prog1 org-refile-active-region-within-subtree
                      (let ((s (point-at-eol)))
                        (org-toggle-heading)
                        (setq region-end (+ (- (point-at-eol) s) region-end)))))
          (user-error "The region is not a (sequence of) subtree(s)")))
      (if (equal arg '(16))
          (org-refile-goto-last-stored)
        (when (or
               (and (equal arg 2)
                    org-clock-hd-marker (marker-buffer org-clock-hd-marker)
                    (prog1
                        (setq it (list (or org-clock-heading "running clock")
                                       (buffer-file-name
                                        (marker-buffer org-clock-hd-marker))
                                       ""
                                       (marker-position org-clock-hd-marker)))
                      (setq arg nil)))
               (setq it
                     (or rfloc
                         (let (heading-text)
                           (save-excursion
                             (unless (and arg (listp arg))
                               (org-back-to-heading t)
                               (setq heading-text
                                     (replace-regexp-in-string
                                      org-bracket-link-regexp
                                      "\\3"
                                      (or (nth 4 (org-heading-components))
                                          ""))))
                             (org-refile-get-location
                              (cond ((and arg (listp arg)) "Goto")
                                    (regionp (concat actionmsg " region to"))
                                    (t (concat actionmsg " subtree \""
                                               heading-text "\" to")))
                              default-buffer
                              (and (not (equal '(4) arg))
                                   org-refile-allow-creating-parent-nodes)))))))
          (setq file (nth 1 it)
                pos (nth 3 it))
          (when (and (not arg)
                     pos
                     (equal (buffer-file-name) file)
                     (if regionp
                         (and (>= pos region-start)
                              (<= pos region-end))
                       (and (>= pos (point))
                            (< pos (save-excursion
                                     (org-end-of-subtree t t))))))
            (error "Cannot refile to position inside the tree or region"))
          (setq nbuf (or (find-buffer-visiting file)
                         (find-file-noselect file)))
          (if (and arg (not (equal arg 3)))
              (progn
                (pop-to-buffer-same-window nbuf)
                (goto-char (cond (pos)
                                 ((org-notes-order-reversed-p) (point-min))
                                 (t (point-max))))
                (org-show-context 'org-goto))
            (if regionp
                (progn
                  (org-kill-new (buffer-substring region-start region-end))
                  (org-save-markers-in-region region-start region-end))
              (org-copy-subtree 1 nil t))
            (with-current-buffer (setq nbuf (or (find-buffer-visiting file)
                                                (find-file-noselect file)))
              (setq reversed (org-notes-order-reversed-p))
              (org-with-wide-buffer
               (if pos
                   (progn
                     (goto-char pos)
                     (setq level (org-get-valid-level (funcall outline-level) 1))
                     (goto-char
                      (if reversed
                          (or (outline-next-heading) (point-max))
                        (or (el-patch-add (save-excursion (rasen/org-goto-last-archive)))
                            (save-excursion (org-get-next-sibling))
                            (org-end-of-subtree t t)
                            (point-max)))))
                 (setq level 1)
                 (if (not reversed)
                     (goto-char (point-max))
                   (goto-char (point-min))
                   (or (outline-next-heading) (goto-char (point-max)))))
               (unless (bolp) (newline))
               (org-paste-subtree level nil nil t)
               (when org-log-refile
                 (org-add-log-setup 'refile nil nil org-log-refile)
                 (unless (eq org-log-refile 'note)
                   (save-excursion (org-add-log-note))))
               (and org-auto-align-tags
                    (let ((org-loop-over-headlines-in-active-region nil))
                      (org-align-tags)))
               (let ((bookmark-name (plist-get org-bookmark-names-plist
                                               :last-refile)))
                 (when bookmark-name
                   (with-demoted-errors
                       (bookmark-set bookmark-name))))
               ;; If we are refiling for capture, make sure that the
               ;; last-capture pointers point here
               (when (bound-and-true-p org-capture-is-refiling)
                 (let ((bookmark-name (plist-get org-bookmark-names-plist
                                                 :last-capture-marker)))
                   (when bookmark-name
                     (with-demoted-errors
                         (bookmark-set bookmark-name))))
                 (move-marker org-capture-last-stored-marker (point)))
               (when (fboundp 'deactivate-mark) (deactivate-mark))
               (run-hooks 'org-after-refile-insert-hook)))
            (unless org-refile-keep
              (if regionp
                  (delete-region (point) (+ (point) (- region-end region-start)))
                (org-preserve-local-variables
                 (delete-region
                  (and (org-back-to-heading t) (point))
                  (min (1+ (buffer-size)) (org-end-of-subtree t t) (point))))))
            (when (featurep 'org-inlinetask)
              (org-inlinetask-remove-END-maybe))
            (setq org-markers-to-move nil)
            (message (concat actionmsg " to \"%s\" in file %s: done") (car it) file)))))))

Archive

(setq-default org-archive-default-command 'org-archive-to-archive-sibling)

Bindings.

(evil-define-key 'normal org-mode-map (kbd "SPC r") 'org-archive-subtree-default)
(evil-define-key 'normal org-mode-map (kbd "C-c C-x C-a") (rasen/hard-way "SPC r"))

Agenda

Set my org files location.

(setq org-directory "~/org"
      org-default-notes-file "~/org/refile.org"
      org-agenda-files (rasen/org-files-in-dir "~/org"))

Configure my agenda view.

(setq org-agenda-span 6
      org-agenda-start-day "-1d")

(setq org-agenda-custom-commands
      '(("N" tags "+TODO=\"NEXT\"-PROJECT|+TODO=\"WAIT\"-PROJECT")
        ("n" todo-tree "NEXT")
        ("p" tags "+PROJECT/+NEXT") ; active projects
        ("P" tags "+PROJECT/-DONE-CANCELED") ; all projects
        ))

Configure stuck projects.

(setq org-tags-exclude-from-inheritance '("PROJECT"))
(setq org-use-tag-inheritance nil)
(setq org-stuck-projects
      '("+PROJECT/-TODO-DONE-CANCELED-WAIT" ("NEXT" "WAIT") nil ""))

Allow NEXT projects to stuck

org-agenda-list-stuck-projects marks project as unstuck if its header matches any of specified keywords. This makes all NEXT projects automatically unstuck.

Fix this by skipping the first line in org-agenda-skip-function.

(el-patch-feature org-agenda)
(el-patch-defun org-agenda-list-stuck-projects (&rest ignore)
  "Create agenda view for projects that are stuck.
Stuck projects are project that have no next actions.  For the definitions
of what a project is and how to check if it stuck, customize the variable
`org-stuck-projects'."
  (interactive)
  (let* ((org-agenda-overriding-header
          (or org-agenda-overriding-header "List of stuck projects: "))
         (matcher (nth 0 org-stuck-projects))
         (todo (nth 1 org-stuck-projects))
         (tags (nth 2 org-stuck-projects))
         (gen-re (org-string-nw-p (nth 3 org-stuck-projects)))
         (todo-wds
          (if (not (member "*" todo)) todo
            (org-agenda-prepare-buffers (org-agenda-files nil 'ifmode))
            (org-delete-all org-done-keywords-for-agenda
                            (copy-sequence org-todo-keywords-for-agenda))))
         (todo-re (and todo
                       (format "^\\*+[ \t]+\\(%s\\)\\>"
                               (mapconcat #'identity todo-wds "\\|"))))
         (tags-re (cond ((null tags) nil)
                        ((member "*" tags) org-tag-line-re)
                        (tags
                         (let ((other-tags (format "\\(?:%s:\\)*" org-tag-re)))
                           (concat org-outline-regexp-bol
                                   ".*?[ \t]:"
                                   other-tags
                                   (regexp-opt tags t)
                                   ":" other-tags "[ \t]*$")))
                        (t nil)))
         (re-list (delq nil (list todo-re tags-re gen-re)))
         (skip-re
          (if (null re-list)
              (error "Missing information to identify unstuck projects")
            (mapconcat #'identity re-list "\\|")))
         (org-agenda-skip-function
          ;; Skip entry if `org-agenda-skip-regexp' matches anywhere
          ;; in the subtree.
          `(lambda ()
             (and (save-excursion
                    (let ((case-fold-search nil)
                          (el-patch-add (subtree-end (save-excursion (org-end-of-subtree t)))))
                      (el-patch-add (forward-line))
                      (re-search-forward
                       ,skip-re
                       (el-patch-swap
                         (save-excursion (org-end-of-subtree t))
                         subtree-end)
                       t)))
                  (progn (outline-next-heading) (point))))))
    (org-tags-view nil matcher)
    (setq org-agenda-buffer-name (buffer-name))
    (with-current-buffer org-agenda-buffer-name
      (setq org-agenda-redo-command
            `(org-agenda-list-stuck-projects ,current-prefix-arg))
      (let ((inhibit-read-only t))
        (add-text-properties
         (point-min) (point-max)
         `(org-redo-cmd ,org-agenda-redo-command))))))

Babel

Code-hightlight (fontify) org-babel (#+begin_src) blocks.

(setq org-src-fontify-natively t)

Do not confirm evaluation for emacs-lisp.

(defun rasen/org-confirm-babel-evaluate (lang body)
  (not (member lang '("emacs-lisp"))))

(setq org-confirm-babel-evaluate 'rasen/org-confirm-babel-evaluate)

Export

Fix exporting for confluence.

ox-confluence has an issue with verbatim—it doesn’t redefine verbatim translation, so org-ascii-verbatim is used. The following makes org-ascii-verbatim produce proper confluence fixed-width block.

(add-to-list 'org-modules 'ox-confluence)
(setq org-ascii-verbatim-format "\{\{%s\}\}")

Crypt

Allow encrypted entries in org files.

(require 'org-crypt)
(org-crypt-use-before-save-magic)
(setq org-tags-exclude-from-inheritance '("crypt"))
(setq org-crypt-key "[email protected]")
(add-hook 'org-babel-pre-tangle-hook 'org-decrypt-entries t)

Pomodoro

(use-package org-pomodoro
  :commands (org-pomodoro)
  :config
  (setq org-pomodoro-keep-killed-pomodoro-time t)
)

Habits

(require 'org-habit)
(setq org-habit-show-habits-only-for-today t)
(setq org-habit-preceding-days 25)
(setq org-habit-following-days 3)
; (add-to-list 'org-modules 'org-habit)

Evilify

(use-package evil-org
  :after org
  :custom
  ; swap j/k
  (evil-org-movement-bindings '((up . "j")
                                (down . "k")
                                (left . "h")
                                (right . "l")))
  :config
  (add-hook 'org-mode-hook 'evil-org-mode)
  (add-hook 'evil-org-mode-hook
            (lambda ()
              (evil-org-set-key-theme)))
  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys)

  (evil-define-key 'normal org-mode-map
    "go" 'org-open-at-point)

  (evil-define-key 'motion org-agenda-mode-map
    "k" 'org-agenda-next-line
    "j" 'org-agenda-previous-line
    "gk" 'org-agenda-next-item
    "gj" 'org-agenda-previous-item
    (kbd "C-k") 'org-agenda-next-item
    (kbd "C-j") 'org-agenda-previous-item

    "K" 'org-agenda-priority-down
    "J" 'org-agenda-priority-up

    (kbd "M-k") 'org-agenda-drag-line-forward
    (kbd "M-j") 'org-agenda-drag-line-backward)

  (evil-define-key 'motion org-agenda-mode-map
    ; unset prefix
    (kbd "SPC") nil
    (kbd "SPC s") 'org-agenda-schedule
    (kbd "C-c C-s") (rasen/hard-way "SPC s")
    (kbd "SPC d") 'org-agenda-deadline
    (kbd "C-c C-d") (rasen/hard-way "SPC d")
    (kbd "SPC w") 'org-agenda-refile
    (kbd "C-c C-w") (rasen/hard-way "SPC w")

    "go" 'org-agenda-open-link))

Languages

Emacs lisp

(use-package elisp-mode
  :ensure nil ; built-in
  :config
  <<elisp-mode-config>>
  )

Eval last sexp Vim-style.

(evil-define-operator rasen/evil-eval (beg end type)
  "Evaluate region."
  (if (eq type 'block)
      (evil-apply-on-block 'eval-region beg end nil)
    (eval-region beg end)))

(evil-define-key 'motion emacs-lisp-mode-map (kbd "SPC e") 'eval-last-sexp)
(evil-define-key 'visual emacs-lisp-mode-map (kbd "SPC e") 'rasen/evil-eval)

Nix

Pretty self-explaining.

(use-package nix-mode
  :mode "\\.nix$")

Haskell

(Old un-reviewed stuff.)

(use-package haskell-mode
  :mode "\\.hs$"
  :init
  (setq company-ghc-show-info t)
  (setq flycheck-ghc-stack-use-nix t)
  :config
  (add-hook 'haskell-mode-hook 'interactive-haskell-mode)
  (add-hook 'haskell-mode-hook 'haskell-decl-scan-mode)

  (setq haskell-compile-cabal-build-command "cd %s && stack build")
  (setq haskell-compile-cabal-build-command-alt "cd %s && cabal build --ghc-options=-ferror-spans")

  ;; Use Nix for stack ghci
  (add-to-list 'haskell-process-args-stack-ghci "--nix")
  (add-to-list 'haskell-process-args-stack-ghci "--test")

  ;; Use Nix for default build/test command
  (projectile-register-project-type 'haskell-stack
                                    '("stack.yaml")
                                    :compile "stack build --nix"
                                    :test "stack build --nix --test")

  (define-key haskell-mode-map [f8] 'haskell-navigate-imports)
  (define-key haskell-mode-map (kbd "C-c C-b") 'haskell-compile)
  (define-key haskell-mode-map (kbd "C-c v c") 'haskell-cabal-visit-file)

  ; haskell-interactive-mode
  (define-key haskell-mode-map (kbd "C-x C-d") nil)
  (define-key haskell-mode-map (kbd "C-c C-z") 'haskell-interactive-switch)
  (define-key haskell-mode-map (kbd "C-c C-l") 'haskell-process-load-file)
  (define-key haskell-mode-map (kbd "C-c C-t") 'haskell-process-do-type)
  (define-key haskell-mode-map (kbd "C-c C-i") 'haskell-process-do-info)
  (define-key haskell-mode-map (kbd "C-c M-.") nil)
  (define-key haskell-mode-map (kbd "C-c C-d") nil)

  ;; Disable popups (i.e., report errors in the interactive shell).
  (setq haskell-interactive-popup-errors nil)

  (setq haskell-process-suggest-remove-import-lines t
        haskell-process-auto-import-loaded-modules t)

  (with-eval-after-load 'align
    (add-to-list 'align-rules-list
                 '(haskell-types
                   (regexp . "\\(\\s-+\\)\\(::\\|∷\\)\\s-+")
                   (modes . '(haskell-mode literate-haskell-mode))))
    (add-to-list 'align-rules-list
                 '(haskell-assignment
                   (regexp . "\\(\\s-+\\)=\\s-+")
                   (modes . '(haskell-mode literate-haskell-mode))))
    (add-to-list 'align-rules-list
                 '(haskell-arrows
                   (regexp . "\\(\\s-+\\)\\(->\\|→\\)\\s-+")
                   (modes . '(haskell-mode literate-haskell-mode))))
    (add-to-list 'align-rules-list
                 '(haskell-left-arrows
                   (regexp . "\\(\\s-+\\)\\(<-\\|←\\)\\s-+")
                   (modes . '(haskell-mode literate-haskell-mode))))))

Rust

(use-package eldoc
  :commands (eldoc-mode)
  :diminish eldoc-mode)

(use-package rust-mode
  :mode ("\\.rs$" . rust-mode))

(use-package racer
  :after rust-mode
  :commands racer-mode
  :diminish racer-mode
  :config
  (add-hook 'rust-mode-hook #'racer-mode)
  (add-hook 'racer-mode-hook #'eldoc-mode))

C/C++

Doxygen

This const is taken from doxymacs and is subject to GPLv2. I’ve copied it my dotfiles as I don’t need all doxymacs features and setup is non-trivial. (It requires compilation, there is no melpa package.)

(defconst doxymacs-doxygen-keywords
  (list
   (list
    ;; One shot keywords that take no arguments
    (concat "\\([@\\\\]\\(brief\\|li\\|\\(end\\)?code\\|sa"
            "\\|note\\|\\(end\\)?verbatim\\|return\\|arg\\|fn"
            "\\|hideinitializer\\|showinitializer"
            "\\|parblock\\|endparblock"
            ;; FIXME
            ;; How do I get & # < > % to work?
            ;;"\\|\\\\&\\|\\$\\|\\#\\|<\\|>\\|\\%"
            "\\|internal\\|nosubgrouping\\|author\\|date\\|endif"
            "\\|invariant\\|post\\|pre\\|remarks\\|since\\|test\\|version"
            "\\|\\(end\\)?htmlonly\\|\\(end\\)?latexonly\\|f\\$\\|file"
            "\\|\\(end\\)?xmlonly\\|\\(end\\)?manonly\\|property"
            "\\|mainpage\\|name\\|overload\\|typedef\\|deprecated\\|par"
            "\\|addindex\\|line\\|skip\\|skipline\\|until\\|see"
            "\\|endlink\\|callgraph\\|endcond\\|else\\)\\)\\>")
    '(0 font-lock-keyword-face prepend))
   ;; attention, warning, etc. given a different font
   (list
    "\\([@\\\\]\\(attention\\|warning\\|todo\\|bug\\)\\)\\>"
    '(0 font-lock-warning-face prepend))
   ;; keywords that take a variable name as an argument
   (list
    (concat "\\([@\\\\]\\(param\\(?:\\s-*\\[\\(?:in\\|out\\|in,out\\)\\]\\)?"
            "\\|a\\|namespace\\|relates\\(also\\)?"
            "\\|var\\|def\\)\\)\\s-+\\(\\sw+\\)")
    '(1 font-lock-keyword-face prepend)
    '(4 font-lock-variable-name-face prepend))
   ;; keywords that take a type name as an argument
   (list
    (concat "\\([@\\\\]\\(class\\|struct\\|union\\|exception\\|enum"
            "\\|throw\\|interface\\|protocol\\)\\)\\s-+\\(\\(\\sw\\|:\\)+\\)")
    '(1 font-lock-keyword-face prepend)
    '(3 font-lock-type-face prepend))
   ;; keywords that take a function name as an argument
   (list
    "\\([@\\\\]retval\\)\\s-+\\([^ \t\n]+\\)"
    '(1 font-lock-keyword-face prepend)
    '(2 font-lock-function-name-face prepend))
   ;; bold
   (list
    "\\([@\\\\]b\\)\\s-+\\([^ \t\n]+\\)"
    '(1 font-lock-keyword-face prepend)
    '(2 (quote bold) prepend))
   ;; code
   (list
    "\\([@\\\\][cp]\\)\\s-+\\([^ \t\n]+\\)"
    '(1 font-lock-keyword-face prepend)
    '(2 (quote underline) prepend))
   ;; italics/emphasised
   (list
    "\\([@\\\\]e\\(m\\)?\\)\\s-+\\([^ \t\n]+\\)"
    '(1 font-lock-keyword-face prepend)
    '(3 (quote italic) prepend))
   ;; keywords that take a list
   (list
    "\\([@\\\\]ingroup\\)\\s-+\\(\\(\\sw+\\s-*\\)+\\)\\s-*$"
    '(1 font-lock-keyword-face prepend)
    '(2 font-lock-string-face prepend))
   ;; one argument that can contain arbitrary non-whitespace stuff
   (list
    (concat "\\([@\\\\]\\(link\\|copydoc\\|xrefitem"
            "\\|if\\(not\\)?\\|elseif\\)\\)"
            "\\s-+\\([^ \t\n]+\\)")
    '(1 font-lock-keyword-face prepend)
    '(4 font-lock-string-face prepend))
   ;; one optional argument that can contain arbitrary non-whitespace stuff
   (list
    "\\([@\\\\]\\(cond\\|dir\\)\\(\\s-+[^ \t\n]+\\)?\\)"
    '(1 font-lock-keyword-face prepend)
    '(3 font-lock-string-face prepend t))
   ;; one optional argument with no space between
   (list
    "\\([@\\\\]\\(~\\)\\([^ \t\n]+\\)?\\)"
    '(1 font-lock-keyword-face prepend)
    '(3 font-lock-string-face prepend t))
   ;; one argument that has to be a filename
   (list
    (concat "\\([@\\\\]\\(example\\|\\(dont\\)?include\\|includelineno"
            "\\|htmlinclude\\|verbinclude\\)\\)\\s-+"
            "\\(\"?[~:\\/a-zA-Z0-9_. ]+\"?\\)")
    '(1 font-lock-keyword-face prepend)
    '(4 font-lock-string-face prepend))
   ;; dotfile <file> ["caption"]
   (list
    (concat "\\([@\\\\]dotfile\\)\\s-+"
            "\\(\"?[~:\\/a-zA-Z0-9_. ]+\"?\\)\\(\\s-+\"[^\"]+\"\\)?")
    '(1 font-lock-keyword-face prepend)
    '(2 font-lock-string-face prepend)
    '(3 font-lock-string-face prepend t))
   ;; image <format> <file> ["caption"] [<sizeindication>=<size>]
   (list
    "\\([@\\\\]image\\)\\s-+\\(html\\|latex\\)\\s-+\\(\"?[~:\\/a-zA-Z0-9_. ]+\"?\\)\\(\\s-+\"[^\"]+\"\\)?\\(\\s-+\\sw+=[0-9]+\\sw+\\)?"
    '(1 font-lock-keyword-face prepend)
    '(2 font-lock-string-face prepend)
    '(3 font-lock-string-face prepend)
    '(4 font-lock-string-face prepend t)
    '(5 font-lock-string-face prepend t))
   ;; one argument that has to be a word
   (list
    (concat "\\([@\\\\]\\(addtogroup\\|defgroup\\|weakgroup"
            "\\|page\\|anchor\\|ref\\|section\\|subsection\\|subsubsection\\|paragraph"
            "\\)\\)\\s-+\\(\\sw+\\)")
    '(1 font-lock-keyword-face prepend)
    '(3 font-lock-string-face prepend))))

(defconst doxygen-font-lock-keywords
  `((,(lambda (limit)
        (c-font-lock-doc-comments "/\\(\\*[\\*!]\\|/[/!]\\)<?" limit
          doxymacs-doxygen-keywords)))))

(setq c-doc-comment-style '((java-mode . javadoc)
                            (pike-mode . autodoc)
                            (c-mode . doxygen)
                            (c++-mode . doxygen)))

Python

(use-package pip-requirements
  :mode "^requirements.txt$")

JavaScript

(use-package js2-mode
  :mode "\\.js$"
  :init
  (add-hook 'js2-mode-hook 'color-identifiers-mode)
  :config

  (defun rasen/use-eslint-from-node-modules ()
    (let* ((root (locate-dominating-file
                  (or (buffer-file-name) default-directory)
                  "node_modules"))
           (eslint (and root
                        (expand-file-name "node_modules/eslint/bin/eslint.js"
                                          root))))
      (when (and eslint (file-executable-p eslint))
        (setq-local flycheck-javascript-eslint-executable eslint))))
  (add-hook 'flycheck-mode-hook #'rasen/use-eslint-from-node-modules)

  (add-hook 'js2-mode-hook
            (lambda ()
              (flycheck-select-checker 'javascript-eslint)))

  (setq-default flycheck-disabled-checkers
                (append flycheck-disabled-checkers
                        '(javascript-jshint)))
  (setq-default flycheck-enabled-checkers
                (append flycheck-enabled-checkers
                        '(javascript-eslint)))

  (flycheck-add-mode 'javascript-eslint 'js2-mode)

  (setq-default js2-strict-trailing-comma-warning nil))

(el-patch-feature flycheck)
(el-patch-defun flycheck-eslint-config-exists-p ()
  "Whether there is a valid eslint config for the current buffer."
  (let* ((executable (flycheck-find-checker-executable 'javascript-eslint))
         (el-patch-add
           (command (funcall flycheck-command-wrapper-function
                             (cons executable '("--print-config" ".")))))
         (exitcode (and executable
                        (el-patch-swap
                          (call-process executable nil nil nil "--print-config" ".")
                          (el-patch-literal command (apply 'call-process (car command) nil nil nil (cdr command)))))))
         (eq exitcode 0)))

(use-package rjsx-mode
  :mode "\\.js$"
  :config
  (setq-default js-indent-level 2))

Typescript

(use-package typescript-mode
  :init
  (add-hook 'web-mode-hook
            (lambda ()
              (when (or (string-equal "tsx" (file-name-extension buffer-file-name))
                        (string-equal "ts" (file-name-extension buffer-file-name)))
                (typescript-mode))))
  :config
  (setq-default typescript-indent-level 2))
(use-package tide
  :commands (tide-setup tide-hl-identifier-mode tide-format-before-save rasen/setup-tide-mode)
  :hook (typescript-mode . rasen/setup-tide-mode)
  :init
  ; (add-hook 'before-save 'tide-format-before-save)
  (defun rasen/setup-tide-mode ()
    (interactive)
    (tide-setup)
    (flycheck-mode +1)
    (setq flycheck-check-syntax-automatically '(save mode-enabled))
    (eldoc-mode +1)
    (tide-hl-identifier-mode +1)
    (company-mode +1)))
(use-package flycheck-jest
  :after flycheck)

Purescript

(use-package purescript-mode
  :mode "\\.purs$"
  :config
  (add-hook 'purescript-mode-hook 'turn-on-purescript-indentation))
(use-package psc-ide
  :commands (psc-ide-mode)
  :init
  (add-hook 'purescript-mode-hook 'psc-ide-mode))

PHP

(use-package php-mode
  :mode "\\.php$")

Web-mode

(use-package web-mode
  :commands (web-mode)
  :init
  (add-to-list 'auto-mode-alist '("\\.blade.php\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.ts\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode))
  :config
  (setq web-mode-engines-alist
        '(("php"    . "\\.phtml\\'")
          ("blade"  . "\\.blade\\."))
)
)

Groovy

(use-package groovy-mode
  :mode "\\.\\(groovy\\|gradle\\)$")

Go

(use-package go-mode
  :mode "\\.go$")

Lua

(use-package lua-mode
  :mode ("\\.lua$" . lua-mode)
  :config
  (setq lua-indent-level 4))

Ledger / Hledger

(use-package ledger-mode
  :mode "\\.journal$"
  :config
  (setq ledger-binary-path "hledger")
  (add-hook 'ledger-mode-hook 'orgstruct-mode))

Markdown

(use-package markdown-mode
  :mode ("\\.\\(markdown\\|mdown\\|md\\)$" . markdown-mode)
  :init
  (add-hook 'markdown-mode-hook 'visual-line-mode)
  :config
  (setq markdown-fontify-code-blocks-natively t))

Package edit-indirect needed to edit code blocks.

(use-package edit-indirect
  :after markdown-mode)

JSON

(use-package json-mode
  :mode "\\.json$")

YAML

(use-package yaml-mode
  :mode ("\\.\\(yml\\|yaml\\)$" . yaml-mode))

Jinja2

(use-package jinja2-mode
  :mode "\\.j2$")

gitconfig

(use-package gitconfig-mode
  :mode "^\\.gitconfig$")

restclient

(use-package restclient
  :mode "\\.http$")

terraform

(use-package terraform-mode
  :mode "\\.tf$")

graphviz

(use-package graphviz-dot-mode
  :mode "\\.dot$"
  :config
  (setq graphviz-dot-view-command "dotty %s"))

Mail setup

gnus

(use-package gnus
  :config
  (setq user-full-name "Alexey Shmalko"
        user-mail-address "[email protected]")

  (setq gnus-select-method
        '(nnimap "Mail"
                 (nnimap-stream shell)
                 (nnimap-shell-program "/var/run/current-system/sw/libexec/dovecot/imap")))
  (setq gnus-secondary-select-methods nil)

  (setq gnus-parameters
        '(("Work/?.*"
           (posting-style
            (name "Alexey Shmalko")
            (address "[email protected]")))
          ("KaaIoT/?.*"
           (posting-style
            (name "Alexey Shmalko")
            (address "[email protected]")))
          ("Personal/?.*"
           (posting-style
            (name "Alexey Shmalko")
            (address "[email protected]")))))

  (setq gnus-fetch-old-headers 'some)
  (setq gnus-ignored-newsgroups "^to\\.\\|^[0-9. ]+\\( \\|$\\)\\|^[\"]\"[#'()]")

  (setq message-sendmail-f-is-evil t
        message-sendmail-envelope-from nil ; 'header
        message-sendmail-extra-arguments '("--read-envelope-from")

        mail-specify-envelope-from nil
        send-mail-function 'message-send-mail-with-sendmail
        message-send-mail-function 'message-send-mail-with-sendmail
        sendmail-program "msmtp")

  (add-hook 'message-setup-hook 'mml-secure-message-sign-pgpmime)
  (setq mm-verify-option 'always)
  ; (add-to-list 'mm-automatic-display "application/pgp")
  ; (add-to-list 'mm-automatic-display "application/pgp-signature")
  ; (add-to-list 'mm-inlined-types "application/pgp")
  (setq gnus-buttonized-mime-types '("multipart/encrypted" "multipart/signed"))

  (setq gnus-check-new-newsgroups nil ;; NOTE: don't check for new groups
        gnus-save-newsrc-file nil ;; NOTE: don't write `.newsrc' file
        gnus-read-newsrc-file nil ;; NOTE: don't read it, either
        gnus-interactive-exit nil
        gnus-save-killed-list nil)

  ;; TODO uncomment
  ; (require 'gnus-article-treat-patch)
  ; (setq ft/gnus-article-patch-conditions
  ;       '( "^@@ -[0-9]+,[0-9]+ \\+[0-9]+,[0-9]+ @@" ))
)

mbsync

(use-package mbsync
  :bind (:map gnus-group-mode-map
         ("f" . mbsync))
  :config
  (setq mbsync-executable "mbsync")
  (add-hook 'mbsync-exit-hook 'gnus-group-get-new-news))

notmuch

(use-package notmuch
  :config
  (setq notmuch-archive-tags '("-unread"))
  (setq notmuch-saved-searches
        '(
          (:name "unread" :query "tag:unread and not tag:nixos" :key "u")
          (:name "unread-inbox" :query "tag:inbox and tag:unread" :key "i")
          (:name "unread-egoless" :query "tag:egoless and tag:unread" :key "e")
          (:name "unread-nixos" :query "tag:unread and tag:nixos and not tag:nixpkgs" :key "n")
          (:name "unread-nixpkgs" :query "tag:unread and tag:nixpkgs" :key "p")
          (:name "unread-triage" :query "tag:unread and tag:nixpkgs-participating" :key "t")
          (:name "unread-doctoright" :query "tag:unread and tag:doctoright" :key "d")
          (:name "unread-other" :query "tag:unread and not tag:nixos and not tag:inbox and not tag:doctoright" :key "o")
          (:name "later" :query "tag:later" :key "l")
          (:name "flagged" :query "tag:flagged" :key "f")
          (:name "personal" :query "tag:personal" :key "P")
          (:name "doctoright" :query "tag:doctoright" :key "D")
          (:name "sent" :query "tag:sent" :key "s")
          (:name "drafts" :query "tag:draft" :key "r")
          (:name "all mail" :query "*" :key "a")))
  (setq notmuch-hello-sections
        '(; notmuch-hello-insert-header
          notmuch-hello-insert-saved-searches
          ; notmuch-hello-insert-search
          notmuch-hello-insert-alltags
          notmuch-hello-insert-recent-searches
          ; notmuch-hello-insert-footer
         ))
  (setq-default notmuch-show-indent-content nil)

  (defun rasen/mbsync ()
    (interactive)
    (async-shell-command "mbsync sync-gmail & mbsync sync-egoless & mbsync sync-ps & wait; /home/rasen/dotfiles/notmuch.sh" "*mbsync*"))

  (define-key 'notmuch-hello-mode-map "f" 'rasen/mbsync)

  (define-key 'notmuch-hello-mode-map "g" 'notmuch-refresh-all-buffers)
  (define-key 'notmuch-search-mode-map "g" 'notmuch-refresh-all-buffers)
  (define-key 'notmuch-show-mode-map "g" 'notmuch-refresh-all-buffers)

  (define-key 'notmuch-search-mode-map "k" 'notmuch-search-archive-thread)
  (define-key 'notmuch-show-mode-map "k" 'notmuch-show-archive-thread-then-next)
  ; remap old function
  (define-key 'notmuch-search-mode-map "K" 'notmuch-tag-jump)
  (define-key 'notmuch-show-mode-map "K" 'notmuch-tag-jump)

  (define-key 'notmuch-show-mode-map (kbd "M-u")
    (lambda ()
      (interactive)
      (notmuch-show-tag '("+unread"))))

  ; notmuch-tag-formats

  (setq-default notmuch-tagging-keys
                '(("a" notmuch-archive-tags "Archive")
                  ("u" notmuch-show-mark-read-tags "Mark read")
                  ("m" ("+muted") "Mute")
                  ("f" ("+flagged") "Flag")
                  ("s" ("+spam" "-inbox") "Mark as spam")
                  ("d" ("+deleted" "-inbox") "Delete")))

  (require 'org-notmuch))

Workman

quail

Emacs has built-in capability to change keyboard layout (for insert state only), which is triggered by C-\. In order to work properly, Emacs needs to know my keyboard layout.

(use-package quail
  :ensure nil ; built-in?
  :config
  (add-to-list 'quail-keyboard-layout-alist
               '("workman" . "\
                              \
  1!2@3#4$5%6^7&8*9(0)-_=+`~  \
  qQdDrRwWbBjJfFuUpP;:[{]}\\|  \
  aAsShHtTgGyYnNeEoOiI'\"      \
  zZxXmMcCvVkKlL,<.>/?        \
                              "))
  (quail-set-keyboard-layout "workman"))

Look and feel

Remove the clutter

Hide menu, toolbar, scrollbar.

(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)

Do not show startup screen.

(setq inhibit-startup-screen t)

Color scheme

I use airline with molokai color scheme.

(use-package airline-themes
  :config
  (require 'cl)
  (setq-default powerline-display-mbsync-info t)
  (load-theme 'airline-molokai t)

  (add-to-list 'custom-theme-load-path "~/.emacs.d/themes")
  (load-theme 'molokai t))

Font

(defun rasen/font-exists-p (font)
  "Check if the FONT exists."
  (and (display-graphic-p) (not (null (x-list-fonts font)))))

(defun rasen/set-my-fonts ()
  (cond ((rasen/font-exists-p "Terminess Powerline")
         (set-face-attribute 'fixed-pitch nil :family "Terminess Powerline" :height 160)
         (set-face-attribute 'default nil :family "Terminess Powerline" :height 160))
        ((rasen/font-exists-p "Terminus")
         (set-face-attribute 'fixed-pitch nil :family "Terminus" :height 160)
         (set-face-attribute 'default nil :family "Terminus" :height 160))))

(rasen/set-my-fonts)

Apply my font setting when new frame is created (useful when emacs is started in daemon mode).

(defun rasen/font-hook (frame)
  (select-frame frame)
  (rasen/set-my-fonts))

(add-hook 'after-make-frame-functions 'rasen/font-hook)

Misc

Hightlight parentheses, show current column.

(show-paren-mode 1)
(column-number-mode 1)

Highlight current line.

(global-hl-line-mode)

Draw block cursor as wide as the glyph under it. For example, if a block cursor is over a tab, it will be drawn as wide as that tab on the display.

(setq-default x-stretch-cursor t)

scroll-margin is a number of lines of margin at the top and bottom of a window. Scroll the window whenever point gets within this many lines of the top or bottom of the window. (scroll-conservatively should be greater than 100 to never recenter point. Value 1 helps, but eventually recenters cursor if you scroll too fast.)

(setq scroll-margin 3
      scroll-conservatively 101)

Center all text in the buffer in some modes.

(use-package visual-fill-column
  :commands (visual-fill-column-mode)
  :init
  (add-hook 'org-mode-hook
            (lambda ()
              (setq-local fill-column 81)
              (visual-line-mode t)
              (visual-fill-column-mode t)))
  :config
  (setq-default visual-fill-column-center-text t
                visual-fill-column-fringes-outside-margins nil))

Add a little bit of highlighting for the cursor, when buffer scrolls, so I don’t lose it.

(use-package beacon
  :diminish beacon-mode
  :config
  (beacon-mode 1))

Quantified self

Add project name to the title, so I can later analyze my app usage.

(setq-default frame-title-format
              '("[%m] " (:eval (projectile-project-name))))