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 (org-src-get-lang-mode lang))
(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 (and (re-search-forward name-regexp nil t)
(not (org-in-commented-heading-p)))
(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
(unless (org-in-commented-heading-p)
(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.
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 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
;; 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)
I start using general to define my keybindings.
(use-package general)
I constantly improve my configuration to make me more efficient. The first step is actually optimizing the process of optimizing. The following provides key binding to quickly open my emacs or system configuration files.
(They are global and come first, so they are still defined if I mess up with the later configuration and the rest of config file is not loaded.)
(defun rasen/find-emacs-config (arg)
"Open emacs.org configuration file.
When PREFIX is set, open emacs.nix configuration file."
(interactive "P")
(if arg
(find-file (expand-file-name ".config/nixpkgs/emacs.nix" rasen/dotfiles-directory))
(find-file (expand-file-name "emacs.org" rasen/dotfiles-directory))))
(defun rasen/find-system-config ()
(interactive)
(find-file (expand-file-name "README.org" rasen/dotfiles-directory)))
(general-def
"<f12>" #'rasen/find-emacs-config
"<C-f12>" #'rasen/find-system-config)
Set PATH env variable from exec-path
. This is required for shell-command
to find executables available in exec-path
. (Most notably, org-mode latex preview fails without this.)
(setenv "PATH" (string-join exec-path ":"))
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)))))))
(use-package evil
:init
(setq evil-want-integration nil)
(setq evil-want-keybinding nil)
:config
<<evil-config>>
(evil-mode 1))
Use SPC
as a leader.
(general-def :states '(motion normal visual) "SPC" nil)
Hard way: prohibit usage of keybindings I have more efficient bindings for.
(defmacro rasen/hard-way (key)
`(lambda () (interactive) (error "Don't use this key! Use %s instead" ,key)))
Swap .
and ;
.
(general-def 'normal
";" #'evil-repeat
"." nil
"C-;" #'evil-repeat-pop
"C-." nil)
(general-def 'motion
"." #'evil-repeat-find-char
";" nil
"g." #'goto-last-change
"g;" nil)
(general-def 'motion
"SPC ;" #'eval-expression)
Close other window.
(defun rasen/quit-other ()
(interactive)
(other-window 1)
(quit-window))
(general-def 'motion
"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))))
(general-def 'motion
"H" #'rasen/smart-move-beginning-of-line
"L" #'evil-end-of-line)
Save buffer with SPC SPC
.
(defun rasen/save-buffer (arg)
"Save current buffer. With PREFIX, save all buffers."
(interactive "P")
(if arg
(save-some-buffers)
(save-buffer)))
(general-def 'normal
"SPC SPC" #'rasen/save-buffer)
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.
(general-def 'motion
"k" #'evil-next-visual-line
"j" #'evil-previous-visual-line
"gk" #'evil-next-line
"gj" #'evil-previous-line)
(general-def 'operator
"k" #'evil-next-line
"j" #'evil-previous-line
"gk" #'evil-next-visual-line
"gj" #'evil-previous-visual-line)
(general-def 'motion
"C-h" #'windmove-left
"C-k" #'windmove-down
"C-j" #'windmove-up
"C-l" #'windmove-right)
(general-swap-key nil 'motion
"C-w j" "C-w k")
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
:general
('normal
"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.)
(general-def 'motion
"RET" (lookup-key (current-global-map) (kbd "C-x")))
;; Unmap it from magit
(general-def magit-file-mode-map
"C-x" nil)
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
'(dired
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
xref))
(evil-collection-init))
(use-package evil-surround
:config
(global-evil-surround-mode t))
(use-package calc ; built-in
:general
('motion
"g =" #'quick-calc
"g +" #'calc))
(use-package compile ; built-in
:config
(setq compilation-scroll-output t))
And evil commands to go to navigate errors.
(general-def 'motion
"SPC ," #'previous-error
"SPC ." #'next-error
"M-," #'previous-error
"M-." #'next-error)
Not really “evilify.”
(general-def 'minibuffer-local-map
;; Finish input with C-e ("e" in Workman is qwerty's "k")
"C-e" #'exit-minibuffer
"RET" (rasen/hard-way "C-e"))
Default bindings for RET
prevent many of my commands from working. Remap RET
to C-RET
.
(general-def 'shell-mode-map
"RET" nil
"<C-return>" #'comint-send-input)
(use-package lispyville
:hook
((clojure-mode emacs-lisp-mode) . lispyville-mode)
:config
(lispyville-set-key-theme
'(operators
c-w
;; < and >
slurp/barf-cp
(atom-movement t)
commentary
;; wrap with M-(, M-[, or M-{
wrap
additional
;; M-o open below list, M-O open above list
additional-insert))
;; override drag directions
(lispyville--define-key 'normal
(kbd "M-j") #'lispyville-drag-backward
(kbd "M-k") #'lispyville-drag-forward))
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")))
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)
Run Emacs as a daemon.
(load "server")
(unless (server-running-p)
(server-start))
Source per-project from direnv.
(use-package direnv
:config
(direnv-mode))
(use-package ivy
:demand
:general
('motion
"SPC b" #'ivy-switch-buffer)
:diminish ivy-mode
:config
<<ivy-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)))
Do not show ./
and ../
during file name completion.
(setq-default ivy-extra-directories nil)
The normal C-j
is not placed conveniently on Workman layout, so move its function to C-e
(which is qwerty k
).
(general-def 'ivy-minibuffer-map
"C-e" #'ivy-alt-done
"C-M-e" #'ivy-immediate-done)
Evilify ivy-occur.
(general-def
:keymaps '(ivy-occur-mode-map ivy-occur-grep-mode-map)
:states 'normal
"k" #'ivy-occur-next-line
"j" #'ivy-occur-previous-line
"C-n" #'ivy-occur-next-line
"C-p" #'ivy-occur-previous-line
"RET" #'ivy-occur-press-and-switch
"C-e" #'ivy-occur-press-and-switch
"g r" #'ivy-occur-revert-buffer
"g g" #'evil-goto-first-line
"d" #'ivy-occur-delete-candidate
"r" #'read-only-mode
"a" #'ivy-occur-read-action
"c" #'ivy-occur-toggle-calling
"f" #'ivy-occur-press
"o" #'ivy-occur-dispatch
"q" #'quit-window)
(general-def 'normal 'ivy-occur-grep-mode-map
"w" #'ivy-wgrep-change-to-wgrep-mode)
Enable ivy.
(ivy-mode 1)
I use smex for improved counsel-M-x
(show most frequently used commands first).
(use-package smex
:config
(smex-initialize))
(use-package counsel
:demand
:diminish counsel-mode
:general
('motion
"SPC x" #'counsel-M-x
"SPC f" #'counsel-find-file
"g r" #'counsel-git-grep
"g /" #'counsel-rg)
('read-expression-map
"C-r" #'counsel-expression-history)
:config
;; reset ivy initial inputs for counsel
(setq-default ivy-initial-inputs-alist nil)
(counsel-mode 1))
Jump anywhere with a few keystrokes in tree-like way.
(use-package avy
:bind
:general
('motion
"K" #'avy-goto-char)
:custom
;; easy workman keys (excluding pinky)
(avy-keys '(?s ?h ?t ?n ?e ?o ?d ?r ?u ?p)))
Use imenu to jump to symbols in the current buffer.
(use-package imenu-list
:general
(:keymaps 'imenu-list-major-mode-map
:states 'normal
"RET" #'imenu-list-goto-entry
"TAB" #'imenu-list-display-entry
"<backtab>" #'hs-toggle-hiding
"g r" #'imenu-list-refresh
"q" #'imenu-list-quit-window))
(defun rasen/imenu-or-list (arg)
"Invoke `counsel-imenu'. If prefix is provided, toggle imenu-list"
(interactive "P")
(if arg
(imenu-list-smart-toggle)
(counsel-imenu)))
(general-def 'normal
"SPC g" #'rasen/imenu-or-list)
Edit grep buffers and apply changes to the files.
(use-package wgrep)
A good mode to highlight whitespace issues (leading/trailing spaces/newlines) and too long lines.
(use-package whitespace
:diminish (global-whitespace-mode
whitespace-mode
whitespace-newline-mode)
:hook (prog-mode . whitespace-mode)
:config
(setq-default whitespace-line-column 120
whitespace-style '(face
tab-mark
empty
trailing
lines-tail)))
Fix whitespaces on file save.
(use-package whitespace-cleanup-mode
:diminish whitespace-cleanup-mode
:config
(global-whitespace-cleanup-mode 1))
It’s enabled by default. Just diminish it.
(use-package undo-tree
:diminish (undo-tree-mode global-undo-tree-mode))
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))
(use-package nix-sandbox
:disabled
:commands (nix-shell-command
nix-shell
nix-compile
nix-find-sandbox
nix-current-sandbox
nix-executable-find))
(use-package projectile
:general
('motion
"SPC p" #'projectile-command-map
;; 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)
;; projectile-find-file is slow on very large projects. Enable
;; known-files caching for projectile to speed it up.
;; (Note: clear cache with `projectile-invalidate-cache', or C-u U)
;; (setq-default projectile-enable-caching t)
;; set projectile-enable-caching on a per-project basis in .dir-locals.el
(setq-default projectile-completion-system 'ivy)
(projectile-mode))
(use-package counsel-projectile
:after projectile
:config
(counsel-projectile-mode))
(use-package magit
:general
(:states 'motion
"g m" #'magit-status)
:diminish auto-revert-mode
:config
<<magit-config>>
)
Do not put files into trash can. Delete them for real.
(setq-default magit-delete-by-moving-to-trash nil)
Integrate with ivy.
(setq-default magit-completing-read-function 'ivy-completing-read)
Use q
to quit transient buffers.
(use-package transient
:defer t
:config
(transient-bind-q-to-quit))
Evilify magit-mode.
(use-package evil-magit
:config
<<evil-magit-config>>
)
(setq evil-magit-use-y-for-yank t)
Evilify magit-blame.
(general-def 'normal magit-blame-read-only-mode-map
"k" #'evil-next-visual-line
"j" #'evil-previous-visual-line
"C-k" #'magit-blame-next-chunk
"C-j" #'magit-blame-previous-chunk
"gk" #'magit-blame-next-chunk-same-commit
"gj" #'magit-blame-previous-chunk-same-commit)
(general-def 'motion magit-blame-mode-map
"SPC" (lookup-key evil-motion-state-map (kbd "SPC")))
(general-def
:states `(,evil-magit-state visual)
:keymaps 'magit-mode-map
"j" #'evil-previous-visual-line
"k" #'evil-next-visual-line
"C-j" #'magit-section-backward
"C-k" #'magit-section-forward
"gj" #'magit-section-backward-sibling
"gk" #'magit-section-forward-sibling)
Add a magit command to push HEAD
into a specified ref. Bound to p h
.
(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))
(evil-magit)
(defun rasen/magit-fco (remote refspec args)
"Fetch remote branch and checkout it (detached HEAD)."
(interactive
(let ((remote (magit-read-remote-or-url "Fetch from remote or url")))
(list remote
(magit-read-refspec "Fetch using refspec" remote)
(magit-fetch-arguments))))
(magit-git-fetch remote (cons refspec args))
;; FIXME: magit-checkout does not wait for git fetch to finish.
(magit-checkout "FETCH_HEAD"))
(if (fboundp 'transient-insert-suffix)
(transient-insert-suffix 'magit-fetch 'magit-fetch-modules
'(1 "c" "checkout" rasen/magit-fco)))
(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)
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"))
(use-package git-commit
:gfhook 'flyspell-mode
:general
(:keymaps 'with-editor-mode-map
:states 'normal
"'" #'with-editor-finish)
(:keymaps 'with-editor-mode-map
"C-c C-c" (rasen/hard-way "'"))
:config
(add-to-list 'evil-insert-state-modes 'with-editor-mode)
(setq evil-normal-state-modes (delete 'git-commit-mode evil-normal-state-modes)))
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)
(diff-hl-flydiff-mode t)
(global-diff-hl-mode t))
(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)
;; yasnippet's wrapping doesn't work well with evil. When you
;; trigger a snippet from a visual state, it switches into normal
;; state, but cursor moves in such a way, so that you leave the
;; snippet, so you're not able to supply other fields ($1, $2,
;; etc.).
;;
;; This function installed as `yas-before-expand-snippet-hook'
;; switches into insert state before expanding the snippet, so you
;; can supply all the fields.
;;
;; Note that it is not always desirable because some snippets don't
;; have extra fields, so switching to insert state has not sense.
;; In order for the switch to kick in, set `rasen/evil-state'
;; expand-env to "insert" like this:
;;
;; # expand-env: ((rasen/evil-state "insert"))
(defun rasen/yas-before-expand-snippet ()
(when (not (string-or-null-p snippet))
(let ((state (car (alist-get 'rasen/evil-state (yas--template-expand-env snippet)))))
(when (and (equal state "insert") (evil-visual-state-p))
(let ((beg evil-visual-beginning)
(end evil-visual-end))
(evil-insert-state nil)
;; restore mark and point
(if (eq (char-after beg) ?\s)
;; skip whitespaces, if present
(progn
(goto-char beg)
(forward-whitespace 1)
(set-mark (point)))
(set-mark beg))
(goto-char end)
(when (bolp)
(backward-char)))))))
(add-hook 'yas-before-expand-snippet-hook #'rasen/yas-before-expand-snippet)
(add-hook 'term-mode-hook (lambda ()
(setq-local yas-dont-activate-functions t))))
Company mode provides autocomplete features.
(use-package company
:defer 2
:general
(:keymaps 'company-mode-map
:states 'insert
"C-n" #'company-complete-common-or-cycle
"C-p" #'company-select-previous)
('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)
(setq-default company-search-filtering t)
(global-company-mode))
Company-box frontend works better with variable-pitch fonts.
(use-package company-box
:diminish
:hook (company-mode . company-box-mode))
(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))
Display flycheck error inline.
(use-package flycheck-inline
:after flycheck
:config
(global-flycheck-inline-mode))
Auto-close pairs.
(electric-pair-mode)
(use-package hippie-exp
:general
('insert
"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)))
(use-package color-identifiers-mode
:commands (color-identifiers-mode
global-color-identifiers-mode)
:diminish (color-identifiers-mode
global-color-identifiers-mode))
(defun rasen/tsid (&optional time)
"Return timestamp-id."
(format-time-string "%Y%m%d%H%M%S" time "UTC"))
(defun rasen/insert-tsid ()
"Insert timestamp-id at point."
(insert (rasen/tsid)))
(defun rasen/copy-file-path ()
"Copy the current buffer's path to kill ring."
(interactive)
;; TODO: optionally strip projectile-prefix
(kill-new (buffer-file-name)))
(defun rasen/org-copy-log-entry (arg)
"Copy the current org entry as a log line with timestamp. If ARG is provided, kill the entry."
(interactive "P")
(let* ((heading (org-get-heading))
(created (org-entry-get (point) "CREATED"))
(line (concat "- " created " " heading)))
(when arg
(org-cut-subtree)
(current-kill 1))
(kill-new (concat line "\n"))
(message line)))
Shamelessly stolen 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"))))
(use-package google-translate
:general ('normal '(markdown-mode-map org-mode-map)
"g t" #'rasen/google-translate-at-point
"g T" #'google-translate-smooth-translate)
:commands (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")))))
(use-package sql
:commands (sql-mode
sql-connect
sql-oracle
sql-sybase
sql-informix
sql-sqlite
sql-mysql
sql-solid
sql-ingres
sql-ms
sql-postgres
sql-interbase
sql-db2
sql-linter
sql-vertica)
:config
(add-hook 'sql-mode-hook (lambda () (toggle-truncate-lines t))))
I use Edit with Emacs firefox extension.
(use-package edit-server
:config
(edit-server-start))
(use-package lsp-mode
:hook (rust-mode . lsp)
:config
(setq lsp-prefer-flymake nil))
(use-package lsp-ui
:commands lsp-ui-mode)
(use-package company-lsp
:commands company-lsp)
Dim parens.
(use-package paren-face
:config
(global-paren-face-mode))
Automatically determine indent style.
(use-package dtrt-indent
:diminish
:config
(dtrt-indent-global-mode))
(setq epa-pinentry-mode 'loopback)
(use-package org
:mode ("\\.org$" . org-mode)
:general
("C-c l" #'org-store-link)
('motion
"SPC c" #'org-capture
"SPC a" #'org-agenda
"SPC o" #'org-clock-out
"SPC l" #'org-clock-in-last
"SPC j" #'org-clock-goto)
('normal
'org-mode-map
"SPC t" #'org-todo
"SPC s" #'org-schedule
"SPC d" #'org-deadline
"SPC i" #'org-clock-in
"SPC T" #'rasen/org-do-today
"SPC w" #'org-refile
"SPC r" #'org-archive-subtree-default
)
('(insert normal) 'org-mode-map
"C-c ," #'org-time-stamp-inactive)
:gfhook 'flyspell-mode
: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)
(setq org-ellipsis "…")
(setq org-hide-emphasis-markers t)
;; allow ndash/mdash before/after emphasis markers.
;; (copy-modified from original `org-emphasis-regexp-components' definition)
(org-set-emph-re 'org-emphasis-regexp-components
'("-–—[:space:]('\"{" "-–—[:space:].,:!?;'\")}\\[" "[:space:]" "." 1))
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 "~/wiki")
My helper to find all org files in a directory.
(defun rasen/org-files-in-dir (dir)
(f-files dir
(lambda (file) (or (f-ext? file "org")
(and (f-ext? file "gpg")
(f-ext? (f-no-ext file) "org"))))
nil))
Package for f-files
and f-ext?
functions.
(use-package f
:commands (f-files f-ext? f-no-ext))
(use-package org-drill
:commands (org-drill)
:config
(setq org-drill-scope (rasen/org-files-in-dir "~/org/drill"))
(add-to-list 'org-modules 'org-drill)
(setq org-drill-leech-failure-threshold 10
org-drill-leech-method 'warn))
Use the following states: TODO
NEXT
DONE
CANCELED
WAIT
.
(setq-default org-todo-keywords
'((sequence "TODO(t)" "NEXT(n!)" "|" "DONE(d!)")
(sequence "BUILD(b!)" "|")
(sequence "|" "CANCELED(c@)")
(sequence "WAIT(w@)" "|")))
(setq-default org-use-fast-todo-selection t)
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)
Fontify the whole line for done tasks.
(setq org-fontify-done-headline t)
Import org-expiry
for org-expiry-insert-created
—this inserts CREATED
property.
(require 'org-expiry)
(setq org-expiry-inactive-timestamps t)
(org-expiry-insinuate)
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"))
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)
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 rasen/org-refile-file (concat rasen/org-directory "/refile-" system-name ".org"))
(setq org-capture-templates
`(("u"
"Task: Read this URL"
entry
(file rasen/org-refile-file)
,(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 rasen/org-refile-file)
,(concat "* %(rasen/strip-url-from-title \"%:description\")\n"
":PROPERTIES:\n"
":CREATED: %U\n"
":SOURCE_URL: %:link\n"
":END:\n"
"#+begin_quote\n"
"%i\n"
"#+end_quote\n"
"%?\n")
:immediate-finish t)
("j" "Journal entry" plain
(file+datetree+prompt "~/org/journal.org")
,(concat
"TIL:\n- %?\n\n"
;; %U does not work here because timestamp is hijacked by
;; %file+datetime+prompt
"%(format-time-string (org-time-stamp-format t t))"
"\n"))
("t" "todo" entry (file rasen/org-refile-file)
"* TODO %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n" :clock-in t :clock-resume t)
("T" "today" entry (file rasen/org-refile-file)
"* NEXT %?\nSCHEDULED: %t\n:PROPERTIES:\n:CREATED: %U\n:END:\n" :clock-in t :clock-resume t)
("m" "meeting" entry (file rasen/org-refile-file)
"* %? :Meeting:\n:PROPERTIES:\n:CREATED: %U\n:END:\n" :clock-in t :clock-resume t)
("n" "note" entry (file rasen/org-refile-file)
"* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n")
("l" "link" entry (file rasen/org-refile-file)
"* %a \n:PROPERTIES:\n:CREATED: %U\n:END:\n"
:immediate-finish t)))
(defun rasen/org-capture-link ()
(interactive)
(org-capture nil "l"))
Enable org-protocol.
(require 'org-protocol)
%l
in org-capture fails with multiline context, so use only the first line as a context.
(setq org-context-in-file-links 1)
Instanly go into insert mode on capture.
(add-hook 'org-capture-mode-hook 'evil-insert-state)
(general-def
:keymaps 'org-capture-mode-map
:states 'normal
"'" #'org-capture-finalize
"SPC w" #'org-capture-refile)
(general-def
:keymaps 'org-capture-mode-map
"C-c C-c" (rasen/hard-way "'")
"C-c C-w" (rasen/hard-way "SPC w"))
(use-package org-download
:init
(el-patch-feature org-download)
:config
(setq org-download-method 'directory)
;; Do not prepend heading name to the file path
(setq-default org-download-heading-lvl nil)
;; "download" screenshots from clipboard
(setq org-download-screenshot-method "xclip -selection clipboard -t image/png -o > %s")
;; Use timestamp-ids
(el-patch-defun org-download-file-format-default (filename)
"It's affected by `org-download-timestamp'."
(concat
(el-patch-swap
(format-time-string org-download-timestamp)
(rasen/tsid))
(el-patch-add "-")
filename)))
;; adapted from org-capture module
(defun rasen/org-datetree-entry (arg)
"Add a date-tree entry in the current file. Interactive version."
(interactive "P")
(let ((d (calendar-gregorian-from-absolute
(if arg
;; Current date, possibly corrected for late night
;; workers.
(org-today)
(progn;; Prompt for date.
(let ((prompt-time (org-read-date
nil t nil "Date for tree entry:")))
(cond ((and (or (not (boundp 'org-time-was-given))
(not org-time-was-given))
(not (= (time-to-days prompt-time) (org-today))))
;; Use 00:00 when no time is given for another
;; date than today?
(apply #'encode-time 0 0
org-extend-today-until
(cl-cdddr (decode-time prompt-time))))
((string-match "\\([^ ]+\\)--?[^ ]+[ ]+\\(.*\\)"
org-read-date-final-answer)
;; Replace any time range by its start.
(apply #'encode-time
(org-read-date-analyze
(replace-match "\\1 \\2" nil nil
org-read-date-final-answer)
prompt-time (decode-time prompt-time))))
(t prompt-time))
(time-to-days prompt-time)))))))
(org-datetree-find-date-create d)))
(use-package org-cliplink
:config
;; I don't like titles clipping at 80. I'd rather get the full title
;; and edit it manually.
(setq org-cliplink-max-length 200))
(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)))
Adapted from Fast refiling in org-mode with hydras | Josh Moller-Mara. Extended to support refiling by outline path.
(defun rasen/concat (sequence separator)
(mapconcat 'identity sequence separator))
(defun rasen/org-refile-exact (file path &optional arg)
"Refile to a specific location.
With a `C-u' ARG argument, jump to that location."
(let* ((pos (org-find-olp (cons file path)))
(rfloc (list (rasen/concat path "/") file nil pos)))
(if (and (eq major-mode 'org-agenda-mode)
;; Don't use org-agenda-refile if we're just jumping
(not (and arg (listp arg))))
(org-agenda-refile nil rfloc)
(org-refile arg nil rfloc))))
(defun rasen/refile (file path &optional arg)
"Refile to PATH in FILE. Clean up org-capture if it's activated.
With a `C-u` ARG, just jump to the headline."
(interactive "P")
(let ((is-capturing (and (boundp 'org-capture-mode) org-capture-mode)))
(cond
((and arg (listp arg)) ;Are we jumping?
(rasen/org-refile-exact file path arg))
;; Are we in org-capture-mode?
(is-capturing
(rasen/org-capture-refile-but-with-args file path arg))
(t
(rasen/org-refile-exact file path arg)))
(when (or arg is-capturing)
(setq hydra-deactivate t))))
(defun rasen/org-capture-refile-but-with-args (file path &optional arg)
"Copied from `org-capture-refile' since it doesn't allow passing arguments. This does."
(unless (eq (org-capture-get :type 'local) 'entry)
(error
"Refiling from a capture buffer makes only sense for `entry'-type templates"))
(let ((pos (point))
(base (buffer-base-buffer (current-buffer)))
(org-capture-is-refiling t)
(kill-buffer (org-capture-get :kill-buffer 'local)))
(org-capture-put :kill-buffer nil)
(org-capture-finalize)
(save-window-excursion
(with-current-buffer (or base (current-buffer))
(org-with-wide-buffer
(goto-char pos)
(rasen/org-refile-exact file path arg))))
(when kill-buffer (kill-buffer base))))
(defmacro rasen/make-refile-hydra (hydraname name &rest options)
(declare (indent defun))
`(defhydra ,hydraname (:exit t)
,name
,@(mapcar (lambda (x)
(let ((key (nth 0 x))
(name (nth 1 x))
(file (nth 2 x))
(path (nthcdr 3 x)))
`(,key (rasen/refile ,file ',path current-prefix-arg) ,name)))
options)
("q" nil "cancel")))
Actual configuration.
(rasen/make-refile-hydra rasen/org-refile-hydra-projects "Projects"
("w" "Work" "~/org/ring.org.gpg" "Projects")
("p" "Personal" "~/org/plan.org" "Projects"))
(rasen/make-refile-hydra rasen/org-refile-hydra-areas "Areas"
("w" "Work" "~/org/ring.org.gpg" "Work")
("h" "Home" "~/org/plan.org" "Areas" "Home")
("p" "People" "~/org/plan.org" "Areas" "People")
("c" "Computers" "~/org/plan.org" "Areas" "Computers")
("o" "Open-source" "~/org/plan.org" "Areas" "Open-source")
("P" "Planning" "~/org/plan.org" "Areas" "Planning")
("s" "Self-development" "~/org/plan.org" "Areas" "Self-development")
("f" "Future of Software" "~/org/plan.org" "Areas" "Future of Software")
("H" "Health" "~/org/plan.org" "Areas" "Health")
("C" "Cats" "~/org/plan.org" "Areas" "Cats")
("e" "egoless.tech" "~/org/plan.org" "Areas" "egoless.tech")
("a" "alexeyshmalko.com" "~/org/plan.org" "Areas" "alexeyshmalko.com")
("m" "Misc" "~/org/plan.org" "Areas" "Misc"))
(rasen/make-refile-hydra rasen/org-refile-hydra-resources "Resources"
("w" "Work" "~/org/ring.org.gpg" "Resources")
("p" "Productivity" "~/org/plan.org" "Resources" "Productivity")
("s" "Software development" "~/org/plan.org" "Resources" "Software development, programming, architecture")
("m" "Martial Arts" "~/org/plan.org" "Resources" "Martial Arts")
("n" "Natural sciences" "~/org/plan.org" "Resources" "Natural sciences")
("d" "Design" "~/org/plan.org" "Resources" "Design")
("l" "Programming languages" "~/org/plan.org" "Resources" "Programming languages")
("W" "Writing" "~/org/plan.org" "Resources" "Writing")
("c" "Communication" "~/org/plan.org" "Resources" "Communication")
("b" "Business" "~/org/plan.org" "Resources" "Business")
("g" "Gamedev" "~/org/plan.org" "Resources" "Gamedev"))
(defhydra rasen/org-refile-hydra (:foreign-keys run :exit t)
"Refile"
("p" rasen/org-refile-hydra-projects/body "Projects")
("a" rasen/org-refile-hydra-areas/body "Areas")
("r" rasen/org-refile-hydra-resources/body "Resources")
("w" (if (eq major-mode 'org-agenda-mode)
(org-agenda-refile current-prefix-arg)
(org-refile current-prefix-arg))
"select")
("q" nil "cancel"))
(general-def 'normal 'org-mode-map
"SPC w" #'rasen/org-refile-hydra/body)
(general-def 'motion 'org-agenda-mode-map
"SPC w" #'rasen/org-refile-hydra/body)
(general-def 'normal 'org-capture-mode-map
"SPC w" #'rasen/org-refile-hydra/body)
I like my archive sibling to 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)
(beginning-of-line)
(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-link-bracket-re
"\\2"
(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)
(cond
((not org-log-refile))
(regionp
(org-map-region
(lambda nil
(org-add-log-setup 'refile nil nil 'time))
(point)
(+
(point)
(- region-end region-start))))
(t
(org-add-log-setup 'refile nil nil org-log-refile)))
(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 "%s to \"%s\" in file %s: done" actionmsg
(car it)
file)))))))
(setq-default org-archive-default-command 'org-archive-to-archive-sibling)
Set my org files location.
(setq org-directory "~/org"
org-default-notes-file rasen/org-refile-file
org-agenda-files (rasen/org-files-in-dir "~/org"))
Configure my agenda view.
(setq org-agenda-span 6)
Configure stuck projects.
(add-to-list 'org-tags-exclude-from-inheritance "PROJECT")
(setq org-stuck-projects
'("+PROJECT/-TODO-DONE-CANCELED-WAIT" ("NEXT" "WAIT") nil ""))
(use-package org-super-agenda
:config
(general-def org-super-agenda-header-map
"k" #'org-agenda-next-line
"j" #'org-agenda-previous-line)
(setq org-agenda-sticky t)
(setq org-agenda-block-separator nil
org-agenda-compact-blocks t
org-agenda-time-grid '((daily today require-timed) nil "......" "----------------"))
(setq rasen/org-agenda-work-files (mapcar (lambda (x) (expand-file-name x rasen/org-directory))
'("ring.org.gpg"
"refile-AlexeyShmalko.org")))
(setq rasen/org-agenda-personal-files (seq-filter (lambda (x) (not (member x rasen/org-agenda-work-files)))
org-agenda-files))
(setq org-agenda-custom-commands
'(("o" "Overview"
((agenda "" ((org-agenda-span 6)
(org-agenda-files rasen/org-agenda-personal-files)
(org-super-agenda-groups
'((:habit t
:order 10)
(:name "Work"
:category "work"
:tag "work"
:order 2)
(:name none
:time-grid t
:anything t)
))))
(alltodo "" ((org-agenda-overriding-header "")
(org-agenda-files rasen/org-agenda-personal-files)
(org-super-agenda-groups
'((:discard (:scheduled t))
(:name "Books"
:and (:category "books"
:todo "NEXT")
:order 11)
(:name "Projects"
:and (:tag "PROJECT"
:todo "NEXT")
:order 9)
(:name "Next"
:todo "NEXT"
:order 8)
(:todo "WAIT"
:order 12)
(:discard (:anything t))))))
(search "+{:CREATED:}" ((org-agenda-files (mapcar (lambda (x) (concat rasen/org-directory "/" x))
'("refile-omicron.org"
"orgzly.org")))
(org-agenda-overriding-header "")
(org-super-agenda-groups
'((:name "Inbox"
:auto-category t
:anything t)))))))
("w" "Work"
((agenda "" ((org-agenda-span 6)
(org-agenda-files rasen/org-agenda-work-files)
;; (org-super-agenda-groups
;; '((:name none
;; :time-grid t
;; :anything t)
;; )))
))
(alltodo "" ((org-agenda-overriding-header "")
(org-agenda-files rasen/org-agenda-work-files)
(org-super-agenda-groups
'((:discard (:scheduled t))
(:name "Projects"
:and (:tag "PROJECT"
:todo "NEXT")
:order 9)
(:name "Next"
:todo "NEXT"
:order 8)
(:todo "WAIT"
:order 12)
(:discard (:anything t))))))
(search "+{:CREATED:}" ((org-agenda-files (mapcar (lambda (x) (expand-file-name x rasen/org-directory))
'("refile-AlexeyShmalko.org")))
(org-agenda-overriding-header "")
(org-super-agenda-groups
'((:name "Inbox"
:auto-category t
:anything t)))))))
("N" tags "+TODO=\"NEXT\"-PROJECT|+TODO=\"WAIT\"-PROJECT")
("n" todo-tree "NEXT")
("p" "active projects" tags "+PROJECT/+NEXT")
("P" "all projects" tags "+PROJECT/-DONE-CANCELED")))
(org-super-agenda-mode))
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 (project title) 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))))))
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)
;; Use mhchem for chemistry formulas
(setq org-latex-packages-alist '(("" "mhchem" t)))
;; Store all preview in external directory
(setq org-preview-latex-image-directory (expand-file-name "cache/ltximg/" user-emacs-directory))
;; Enable latex preview by default
(setq org-startup-with-latex-preview t)
;; Scale inline images by default
(setq org-image-actual-width '(1024))
;; Show inline images by default
(setq org-startup-with-inline-images t)
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\}\}")
Allow encrypted entries in org files.
(require 'org-crypt)
(org-crypt-use-before-save-magic)
(add-to-list 'org-tags-exclude-from-inheritance "crypt")
(setq org-crypt-key "[email protected]")
(add-hook 'org-babel-pre-tangle-hook 'org-decrypt-entries t)
(require 'org-habit)
(setq org-habit-show-habits-only-for-today t)
(setq org-habit-preceding-days 25)
(setq org-habit-following-days 3)
Better line wrapping. (Use proper wrap-prefix in lists, etc.)
(use-package adaptive-wrap
:config
(add-hook 'org-mode-hook #'adaptive-wrap-prefix-mode))
(use-package org-roam
:after org
:diminish
:hook
(after-init . org-roam-mode)
:general
(:states 'normal
"SPC n r" #'org-roam
"SPC n f" #'org-roam-find-file
"SPC n b" #'org-roam-switch-to-buffer)
(:keymaps 'org-mode-map
:states '(insert visual)
"C-c i" #'org-roam-insert
;; C-i is interpreted as TAB
"C-c TAB" #'org-roam-insert)
:config
(require 'org-roam-protocol)
(setq org-roam-directory (concat rasen/org-directory "/roam")
org-roam-db-location (expand-file-name "cache/org-roam.db" user-emacs-directory))
(setq org-roam-capture-templates
'(("s" "static (slug)" plain (function org-roam--capture-get-point)
"%?"
:file-name "${slug}"
:head "#+TITLE: ${title}\n"
:unnarrowed t)
("d" "dynamic (timestamp)" plain (function org-roam--capture-get-point)
"%?"
:file-name "%(rasen/tsid)"
:head "#+TITLE: ${title}\n"
:unnarrowed t)
("m" "mixed (timestamp+slug)" plain (function org-roam--capture-get-point)
"%?"
:file-name "%(rasen/tsid)-${slug}"
:head "#+TITLE: ${title}\n"
:unnarrowed t)))
;; better defaults for graph view
(setq org-roam-graph-executable (executable-find "dot"))
(setq org-roam-graph-executable (executable-find "neato"))
(setq org-roam-graph-executable (executable-find "fdp"))
(setq org-roam-graph-executable (executable-find "sfdp"))
(setq org-roam-graph-extra-config '(;; ("concentrate" . "true")
("overlap" . "prism")
;; ("pack" . "false")
("sep" . "20.0")
("esep" . "0.01")
("splines" . "true")))
(setq org-roam-graph-node-extra-config '(("shape" . "rectangle")))
(setq org-roam-graph-edge-extra-config '(("dir" . "back")))
;; patch function to use kebab-case in file names
(el-patch-defun org-roam--title-to-slug (title)
"Convert TITLE to a filename-suitable slug."
(cl-flet* ((nonspacing-mark-p (char)
(eq 'Mn (get-char-code-property char 'general-category)))
(strip-nonspacing-marks (s)
(apply #'string (seq-remove #'nonspacing-mark-p
(ucs-normalize-NFD-string s))))
(cl-replace (title pair)
(replace-regexp-in-string (car pair) (cdr pair) title)))
(let* ((pairs `(el-patch-swap (("[^[:alnum:][:digit:]]" . "_") ;; convert anything not alphanumeric
("__*" . "_") ;; remove sequential underscores
("^_" . "") ;; remove starting underscore
("_$" . "")) ;; remove ending underscore
(("[^[:alnum:][:digit:]]" . "-") ;; convert anything not alphanumeric
("--*" . "-") ;; remove sequential dashes
("^-" . "") ;; remove starting dash
("-$" . "")))) ;; remove ending dash
(slug (-reduce-from #'cl-replace (strip-nonspacing-marks title) pairs)))
(s-downcase slug)))))
Enable auto-completion for notes.
(use-package company-org-roam
:after org org-roam company
:config
(push #'company-org-roam company-backends))
(use-package org-ref
:config
(setq reftex-default-bibliography '("~/org/roam/biblio/references.bib"))
(setq org-ref-default-bibliography '("~/org/roam/biblio/references.bib"))
(setq org-ref-pdf-directory "~/org/roam/biblio/pdf/")
(setq org-ref-bibliography-notes "~/org/roam/biblio/")
(setq org-ref-completion-library 'org-ref-ivy-cite)
(setq bibtex-completion-bibliography '("~/org/roam/biblio/references.bib"))
(setq bibtex-completion-library-path '("~/org/roam/biblio/pdf/"))
(setq bibtex-completion-notes-path "~/org/roam/biblio/")
(setq bibtex-autokey-year-length 4
bibtex-autokey-name-year-separator ""
bibtex-autokey-year-title-separator "-"
bibtex-autokey-titleword-separator "-"
bibtex-autokey-titlewords 5
bibtex-autokey-titlewords-stretch 1
bibtex-autokey-titleword-length 5)
;; patch isbn-to-bibtex to handle non-ascii names/titles.
(el-patch-defun isbn-to-bibtex (isbn bibfile)
"Get bibtex entry for ISBN and insert it into BIBFILE.
Nothing happens if an entry with the generated key already exists
in the file. Data comes from worldcat."
(interactive
(list
(read-string
"ISBN: "
;; now set initial input
(cond
;; If region is active and it starts with a number, we use it
((and (region-active-p)
(s-match "^[0-9]" (buffer-substring (region-beginning) (region-end))))
(buffer-substring (region-beginning) (region-end)))
;; if first entry in kill ring starts with a number assume it is an isbn
;; and use it as the guess
((stringp (car kill-ring))
(when (s-match "^[0-9]" (car kill-ring))
(car kill-ring)))
;; type or paste it in
(t
nil)))
(completing-read "Bibfile: " (org-ref-possible-bibfiles))))
(let* ((url (format "https://www.ottobib.com/isbn/%s/bibtex" isbn))
(entry))
(with-current-buffer (url-retrieve-synchronously url t t)
(goto-char (point-min))
(when (re-search-forward (el-patch-swap "@[a-zA-Z]+{.+\\(\n\s+[a-z\s={\.,?:;'0-9\n}-]+\\)+}$"
"@[a-zA-Z]+{.+\\([^<]+\\)+}$") nil t)
(setq entry (match-string 0))))
(if (not entry)
(message "Nothing found.")
(find-file bibfile)
(goto-char (point-max))
(insert (with-temp-buffer
(insert (concat entry "\n}"))
(goto-char (point-min))
(org-ref-isbn-clean-bibtex-entry)
(org-ref-clean-bibtex-entry)
(bibtex-fill-entry)
(s-trim (buffer-string))
(buffer-string)))
(save-buffer)))))
Citations and bibliography tools for org-mode.
(use-package org-roam-bibtex
:diminish
:hook (org-roam-mode . org-roam-bibtex-mode))
(defun rasen/toggle-org-view-mode ()
(interactive)
;; partially stolen from `org-toggle-link-display'
(if org-link-descriptive
(progn
(remove-from-invisibility-spec '(org-link))
(setq-local org-hide-emphasis-markers nil)
(org-clear-latex-preview (point-min) (point-max))
(org-remove-inline-images)
(message "org-view-mode disabled"))
(add-to-invisibility-spec '(org-link))
(setq org-hide-emphasis-markers t)
(org--latex-preview-region (point-min) (point-max))
(org-display-inline-images)
(message "org-view-mode enabled"))
(setq-local org-link-descriptive (not org-link-descriptive))
(font-lock-fontify-buffer))
(general-def 'normal 'org-mode-map
"SPC \\" #'rasen/toggle-org-view-mode)
(use-package evil-org
:after org
:diminish
: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)
(general-def 'normal org-mode-map
"'" #'org-edit-special
"C-c '" (rasen/hard-way "'")
"go" #'org-open-at-point
"C-c C-o" (rasen/hard-way "go"))
;; open file links in the same window
(push '(file . find-file) org-link-frame-setup)
(general-def 'normal org-src-mode-map
"'" #'org-edit-src-exit
"C-c '" (rasen/hard-way "'"))
(general-def '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
"C-k" #'org-agenda-next-item
"C-j" #'org-agenda-previous-item
"K" #'org-agenda-priority-down
"J" #'org-agenda-priority-up
"M-k" #'org-agenda-drag-line-forward
"M-j" #'org-agenda-drag-line-backward)
(general-def 'motion org-agenda-mode-map
;; unset prefix
"SPC" nil
"SPC SPC" #'org-save-all-org-buffers
"SPC s" #'org-agenda-schedule
"C-c C-s" (rasen/hard-way "SPC s")
"SPC d" #'org-agenda-deadline
"C-c C-d" (rasen/hard-way "SPC d")
"SPC w" #'org-agenda-refile
"C-c C-w" (rasen/hard-way "SPC w")
"SPC t" #'org-agenda-todo
"go" #'org-agenda-open-link
"gl" #'org-agenda-log-mode))
Use emacs-state in org-lint buffers.
(evil-set-initial-state 'org-lint--report-mode 'emacs)
(evil-set-initial-state 'epa-key-list-mode 'emacs)
(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)))
(general-def 'motion emacs-lisp-mode-map "SPC e" #'eval-last-sexp)
(general-def 'visual emacs-lisp-mode-map "SPC e" #'rasen/evil-eval)
Keep lisp code always indented.
(use-package aggressive-indent
:commands (aggressive-indent-mode aggressive-indent-global-mode)
:hook
(clojure-mode . aggressive-indent-mode)
(clojurescript-mode . aggressive-indent-mode)
(emacs-lisp-mode . aggressive-indent-mode))
Alternate indent function definition.
;; Fix the indentation of keyword lists in Emacs Lisp. See [1] and [2].
;;
;; Before:
;; (:foo bar
;; :baz quux)
;;
;; After:
;; (:foo bar
;; :bar quux)
;;
;; [1]: https://github.com/Fuco1/.emacs.d/blob/af82072196564fa57726bdbabf97f1d35c43b7f7/site-lisp/redef.el#L12-L94
;; [2]: http://emacs.stackexchange.com/q/10230/12534
(el-patch-defun (el-patch-swap lisp-indent-function rasen/emacs-lisp-indent-function) (indent-point state)
"This function is the normal value of the variable `lisp-indent-function'.
The function `calculate-lisp-indent' calls this to determine
if the arguments of a Lisp function call should be indented specially.
INDENT-POINT is the position at which the line being indented begins.
Point is located at the point to indent under (for default indentation);
STATE is the `parse-partial-sexp' state for that position.
If the current line is in a call to a Lisp function that has a non-nil
property `lisp-indent-function' (or the deprecated `lisp-indent-hook'),
it specifies how to indent. The property value can be:
* `defun', meaning indent `defun'-style
(this is also the case if there is no property and the function
has a name that begins with \"def\", and three or more arguments);
* an integer N, meaning indent the first N arguments specially
(like ordinary function arguments), and then indent any further
arguments like a body;
* a function to call that returns the indentation (or nil).
`lisp-indent-function' calls this function with the same two arguments
that it itself received.
This function returns either the indentation to use, or nil if the
Lisp function does not specify a special indentation."
(el-patch-let (($cond (and (elt state 2)
(el-patch-wrap 1 1
(or (not (looking-at "\\sw\\|\\s_"))
(looking-at ":")))))
($then (progn
(if (not (> (save-excursion (forward-line 1) (point))
calculate-lisp-indent-last-sexp))
(progn (goto-char calculate-lisp-indent-last-sexp)
(beginning-of-line)
(parse-partial-sexp (point)
calculate-lisp-indent-last-sexp 0 t)))
;; Indent under the list or under the first sexp on the same
;; line as calculate-lisp-indent-last-sexp. Note that first
;; thing on that line has to be complete sexp since we are
;; inside the innermost containing sexp.
(backward-prefix-chars)
(current-column)))
($else (let ((function (buffer-substring (point)
(progn (forward-sexp 1) (point))))
method)
(setq method (or (function-get (intern-soft function)
'lisp-indent-function)
(get (intern-soft function) 'lisp-indent-hook)))
(cond ((or (eq method 'defun)
(and (null method)
(> (length function) 3)
(string-match "\\`def" function)))
(lisp-indent-defform state indent-point))
((integerp method)
(lisp-indent-specform method state
indent-point normal-indent))
(method
(funcall method indent-point state))))))
(let ((normal-indent (current-column))
(el-patch-add
(orig-point (point))))
(goto-char (1+ (elt state 1)))
(parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
(el-patch-swap
(if $cond
;; car of form doesn't seem to be a symbol
$then
$else)
(cond
;; car of form doesn't seem to be a symbol, or is a keyword
($cond $then)
((and (save-excursion
(goto-char indent-point)
(skip-syntax-forward " ")
(not (looking-at ":")))
(save-excursion
(goto-char orig-point)
(looking-at ":")))
(save-excursion
(goto-char (+ 2 (elt state 1)))
(current-column)))
(t $else))))))
Apply it for emacs-lisp-mode.
(add-hook 'emacs-lisp-mode-hook
(lambda () (setq-local lisp-indent-function #'rasen/emacs-lisp-indent-function)))
Pretty self-explaining.
(use-package nix-mode
:mode "\\.nix$")
(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")
(general-def haskell-mode-map
[f8] #'haskell-navigate-imports
"C-c C-b" #'haskell-compile
"C-c v c" #'haskell-cabal-visit-file
;; haskell-interactive-mode
"C-x C-d" nil
"C-c C-z" #'haskell-interactive-switch
"C-c C-l" #'haskell-process-load-file
"C-c C-t" #'haskell-process-do-type
"C-c C-i" #'haskell-process-do-info
"C-c M-." nil
"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))))))
(use-package eldoc
:commands (eldoc-mode)
:diminish eldoc-mode)
(use-package rust-mode
:mode ("\\.rs$" . rust-mode)
:config
(add-hook 'rust-mode-hook (lambda () (setq-local fill-column 100))))
(use-package racer
:after rust-mode
:commands racer-mode
:diminish racer-mode
:config
(setq racer-rust-src-path nil) ; Nix manages that
(add-hook 'rust-mode-hook #'racer-mode)
(add-hook 'racer-mode-hook #'eldoc-mode))
(use-package flycheck-rust
:after rust-mode
:config
(add-hook 'flycheck-mode-hook #'flycheck-rust-setup))
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)))
(use-package cmake-mode
:mode
(("CMakeLists\\.txt\\'" . cmake-mode)
("\\.cmake\\'" . cmake-mode)))
(use-package elpy
:after (flycheck)
:config
;; Do not show vertical guides in python code
(setq elpy-modules (delq 'elpy-module-highlight-indentation elpy-modules))
;; Do not use flymake (flycheck will kick in instead)
(setq elpy-modules (delq 'elpy-module-flymake elpy-modules))
;; Use python3 (instead of python2)
(setq flycheck-python-flake8-executable "python3"
flycheck-python-pycompile-executable "python3"
flycheck-python-pylint-executable "python3")
(elpy-enable))
(use-package blacken
:hook (elpy-mode . blacken-mode))
(use-package py-autopep8
:hook (elpy-mode . py-autopep8-enable-on-save))
(use-package pip-requirements
:mode "^requirements.txt$")
(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" (or buffer-file-name "index.js"))))))
(exitcode (and executable
(el-patch-swap
(call-process executable nil nil nil "--print-config" (or buffer-file-name "index.js"))
(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))
(use-package typescript-mode
:commands (typescript-mode)
:init
(el-patch-feature typescript-mode)
(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))))
:general
('insert
'typescript-mode-map
"M-j" #'c-indent-new-comment-line
"C-M-j" #'c-indent-new-comment-line)
:config
(setq-default typescript-indent-level 2)
;; Add more jsdoc tags
(el-patch-defconst typescript-jsdoc-empty-tag-regexp
(concat typescript-jsdoc-before-tag-regexp
"\\(@"
(regexp-opt
'("abstract"
"addon"
"async"
"author"
"class"
"classdesc"
"const"
"constant"
"constructor"
"constructs"
"copyright"
"default"
"defaultvalue"
"deprecated"
"desc"
"description"
"event"
"example"
"exec"
"export"
"exports"
"file"
"fileoverview"
"final"
"func"
"function"
"generator"
"global"
"hidden"
"hideconstructor"
"ignore"
"implicitcast"
"inheritdoc"
"inner"
"instance"
"interface"
"license"
"method"
"mixin"
"noalias"
"noshadow"
"notypecheck"
"override"
"overview"
"owner"
"package"
"preserve"
"preservetry"
"private"
"protected"
"public"
"readonly"
"static"
"summary"
"supported"
"todo"
"tutorial"
"virtual"
(el-patch-add
"remarks"
"alpha"
"beta"
"defaultValue"
"eventProperty"
"example"
"experimental"
"inheritDoc"
"internal"
"label"
"link"
"packageDocumentation"
"privateRemarks"
"sealed"
"typeParam")))
"\\)\\s-*")
"Matches empty jsdoc tags."))
(use-package tide
:commands (tide-setup
tide-hl-identifier-mode
tide-format-before-save)
:hook
(typescript-mode . rasen/setup-tide-mode)
:general
;; K is used for avy
('normal
'tide-normal-map
"K" nil)
:init
(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 prettier-js
:commands (prettier-js prettier-js-mode)
:hook
(typescript-mode . prettier-js-mode))
(use-package flycheck-jest
:after flycheck)
(use-package vue-mode
:mode "\\.vue$")
(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\\."))
)
)
(use-package clojure-mode)
(use-package cider
:config
(evil-set-initial-state 'cider-stacktrace-mode 'emacs))
(use-package groovy-mode
:mode "\\.\\(groovy\\|gradle\\)$")
(use-package forth-mode
:config
(defun rasen/disable-electric-pair ()
(interactive)
(electric-pair-local-mode -1))
(add-hook 'forth-mode-hook #'rasen/disable-electric-pair))
(use-package lua-mode
:mode ("\\.lua$" . lua-mode)
:config
(setq lua-indent-level 4))
(use-package ledger-mode
:mode "\\.journal$"
:config
(setq ledger-binary-path "hledger")
(add-hook 'ledger-mode-hook 'orgstruct-mode))
(use-package markdown-mode
:mode ("\\.\\(markdown\\|mdown\\|md\\)$" . markdown-mode)
:commands gfm-view-mode
:init
(add-hook 'markdown-mode-hook 'visual-line-mode)
(add-hook 'markdown-mode-hook 'flyspell-mode)
:config
(defun rasen/insert-timestamp ()
"Insert current timestamp in ISO 8601 format"
(interactive)
(insert (format-time-string "%FT%R%z")))
(general-def 'normal markdown-mode-map
"'" #'markdown-edit-code-block
"C-c '" (rasen/hard-way "'"))
(general-def 'normal edit-indirect-mode-map
"'" #'edit-indirect-commit
"C-c '" (rasen/hard-way "'"))
(general-def 'insert markdown-mode-map
"C-c ," #'rasen/insert-timestamp)
(setq markdown-fontify-code-blocks-natively t))
Package edit-indirect needed to edit code blocks.
(use-package edit-indirect
:after markdown-mode)
(Uses org-cliplink.)
(defun rasen/md-link-transformer (url title)
(if title
(format "[%s](%s)"
(org-cliplink-elide-string
(org-cliplink-escape-html4
(org-cliplink-title-for-url url title))
org-cliplink-max-length)
url)
(format "<%s>" url)))
(defun rasen/md-cliplink ()
"Takes a URL from the clipboard and inserts an markdown-mode link
with the title of a page found by the URL into the current
buffer"
(interactive)
(org-cliplink-insert-transformed-title (org-cliplink-clipboard-content)
#'rasen/md-link-transformer))
See http://bnbeckwith.com/code/writegood-mode.html
(use-package writegood-mode
:hook (markdown-mode . writegood-mode))
(use-package json-mode
:mode "\\.json$")
(use-package yaml-mode
:mode ("\\.\\(yml\\|yaml\\)$" . yaml-mode))
(use-package jinja2-mode
:mode "\\.j2$")
(use-package dockerfile-mode
:mode "Dockerfile"
:config
(setq dockerfile-mode-command "env SUDO_ASKPASS=/usr/bin/ssh-askpass sudo -A docker"))
(use-package gitconfig-mode
:mode "^\\.gitconfig$")
(use-package restclient
:mode "\\.http$")
(use-package terraform-mode
:mode "\\.tf$")
(use-package graphviz-dot-mode
:mode "\\.dot$"
:config
(setq graphviz-dot-view-command "dotty %s"))
(use-package protbuf-mode
:mode "\\.proto$")
(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]+ @@" ))
)
(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))
(use-package notmuch
:config
;; (setq mm-text-html-renderer 'w3m)
(setq notmuch-archive-tags '("-unread"))
(setq notmuch-saved-searches
'(
(:name "unread-inbox" :query "tag:inbox and tag:unread" :key "u")
;; (:name "unread" :query "tag:unread and not tag:nixos and not tag:rust" :key "u")
(:name "unread-egoless" :query "tag:egoless and tag:unread" :key "e")
(:name "unread-rust" :query "tag:unread and tag:rust" :key "r")
(: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-participating" :query "tag:unread and tag: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 and not tag:rust" :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 "f")
(: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/moritz/nixos-config/notmuch.sh" "*mbsync*"))
(general-def notmuch-hello-mode-map "f" 'rasen/mbsync)
(general-def
:keymaps '(notmuch-hello-mode-map
notmuch-search-mode-map
notmuch-show-mode-map)
"g" 'notmuch-refresh-all-buffers)
(general-def 'notmuch-search-mode-map "k" #'notmuch-search-archive-thread)
(general-def 'notmuch-show-mode-map "k" #'notmuch-show-archive-thread-then-next)
;; remap old function
(general-def '(notmuch-search-mode-map
notmuch-show-mode-map)
"K" #'notmuch-tag-jump)
(general-def 'notmuch-show-mode-map
"C" #'rasen/org-capture-link)
(general-def 'notmuch-show-mode-map
"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")))
;; support for linking notmuch mails in org-mode
(require 'ol-notmuch))
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"))
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)
Do not blink cursor.
(blink-cursor-mode 0)
I use modus-operandi theme.
(use-package modus-operandi-theme
:config
(setq modus-operandi-theme-slanted-constructs t)
(setq modus-operandi-theme-bold-constructs t)
;; Use proportional fonts only when I explicitly configure them.
(setq modus-operandi-theme-proportional-fonts nil)
(load-theme 'modus-operandi t)
(let ((class '((class color) (min-colors 89)))
(fg-main "#000000") (bg-main "#ffffff")
(fg-alt "#505050") (bg-alt "#f3f1f3")
(fg-dim "#282828") (bg-dim "#f8f8f8")
;; specifically for on/off states (e.g. `mode-line')
;;
;; must be combined with themselves
(fg-active "#191919") (bg-active "#e0e0e0")
(fg-inactive "#424242") (bg-inactive "#efedef")
;; special base values, used only for cases where the above
;; fg-* or bg-* cannot or should not be used (to avoid confusion)
;; must be combined with: {fg,bg}-{main,alt,dim}
(fg-special-cold "#093060") (bg-special-cold "#dde3f4")
(fg-special-mild "#184034") (bg-special-mild "#c4ede0")
(fg-special-warm "#5d3026") (bg-special-warm "#f0e0d4")
(fg-special-calm "#61284f") (bg-special-calm "#f8ddea")
;; styles for the main constructs
;;
;; must be combined with: `bg-main', `bg-alt', `bg-dim'
(red "#a80000") (green "#005200")
(yellow "#8b3800") (blue "#0030a6")
(magenta "#721045") (cyan "#005589")
;; styles for common, but still specialised constructs
;;
;; must be combined with: `bg-main', `bg-alt', `bg-dim'
(red-alt "#880000") (green-alt "#4a5700")
(yellow-alt "#714900") (blue-alt "#223fbf")
(magenta-alt "#8f0075") (cyan-alt "#185870")
;; same purpose as above, just slight differences
;;
;; must be combined with: `bg-main', `bg-alt', `bg-dim'
(red-alt-other "#9d2020") (green-alt-other "#145a00")
(yellow-alt-other "#804000") (blue-alt-other "#0000bb")
(magenta-alt-other "#5317ac") (cyan-alt-other "#005a68")
;; styles for elements that should be very subtle
;;
;; must be combined with: `bg-main', `bg-alt', `bg-dim'
(red-nuanced "#4d0006") (green-nuanced "#003000")
(yellow-nuanced "#3a2a00") (blue-nuanced "#001170")
(magenta-nuanced "#381050") (cyan-nuanced "#003434")
;; styles for elements that should draw attention to themselves
;;
;; must be combined with: `bg-main'
(red-intense "#b60000") (green-intense "#006800")
(yellow-intense "#904200") (blue-intense "#1111ee")
(magenta-intense "#7000e0") (cyan-intense "#205b93")
;; styles for background elements that should be visible yet
;; subtle
;;
;; must be combined with: `fg-dim'
(red-subtle-bg "#f2b0a2") (green-subtle-bg "#aecf90")
(yellow-subtle-bg "#e4c340") (blue-subtle-bg "#b5d0ff")
(magenta-subtle-bg "#f0d3ff") (cyan-subtle-bg "#c0efff")
;; styles for background elements that should be visible and
;; distinguishable
;;
;; must be combined with: `fg-main'
(red-intense-bg "#ff8892") (green-intense-bg "#5ada88")
(yellow-intense-bg "#f5df23") (blue-intense-bg "#6aaeff")
(magenta-intense-bg "#d5baff") (cyan-intense-bg "#42cbd4")
;; styles for refined git diffs and other contexts where both the
;; foreground and the background need to have the same/similar hue
;;
;; must be combined with themselves OR the foregrounds can be
;; combined with any of the base backgrounds
(red-refine-bg "#ffcccc") (green-refine-bg "#aceaac")
(yellow-refine-bg "#fff29a") (blue-refine-bg "#8ac7ff")
(magenta-refine-bg "#ffccff") (cyan-refine-bg "#8eecf4")
(red-refine-fg "#780000") (green-refine-fg "#004c00")
(yellow-refine-fg "#604000") (blue-refine-fg "#002288")
(magenta-refine-fg "#770077") (cyan-refine-fg "#004850")
;; styles that are meant exclusively for the mode line
;;
;; must be combined with: `bg-active', `bg-inactive'
(red-active "#930000") (green-active "#005300")
(yellow-active "#703700") (blue-active "#0033c0")
(magenta-active "#6320a0") (cyan-active "#004882")
;; styles reserved for specific faces
;;
;; `bg-hl-line' is between `bg-dim' and `bg-alt', so it should
;; work with all accents that cover those two, plus `bg-main'
;;
;; `bg-header' is between `bg-active' and `bg-inactive', so it
;; can be combined with any of the "active" values, plus the
;; "special" and base foreground colours
;;
;; `bg-region' must be combined with `fg-main'
;;
;; the window divider colours apply to faces with just an fg value
;;
;; all other pairs are combinable with themselves
(bg-hl-line "#f1f2f6")
(bg-region "#bcbcbc")
(fg-window-divider-inner "#888888")
(fg-window-divider-outer "#585858")
(fg-header "#2a2a2a") (bg-header "#e5e5e5")
(fg-whitespace "#645060") (bg-whitespace "#fff8fc")
(fg-paren-match "#222222") (bg-paren-match "#deb8af"))
(custom-theme-set-faces
'modus-operandi
;; do not bold matches (this makes text jiggle with variable-pitch fonts
`(isearch ((,class (:inherit modus-theme-intense-green))))
`(query-replace ((,class :inherit modus-theme-intense-yellow)))
`(show-paren-match ((,class (:background ,bg-paren-match :foreground ,fg-paren-match))))
`(flyspell-duplicate
((,(append '((supports :underline (:style wave))) class)
(:underline (:style wave :color ,yellow)))
(,class (:underline ,yellow))))
`(flyspell-incorrect
((,(append '((supports :underline (:style wave))) class)
(:underline (:style wave :color ,red)))
(,class (:underline ,red))))
`(italic ((,class :slant italic)))
`(org-todo ((,class :foreground ,magenta-alt-other
:weight bold)))
`(org-archived ((,class (:foreground ,fg-alt))))
`(org-done ((,class :foreground ,fg-alt)))
`(org-headline-done ((,class :foreground ,fg-alt)))
`(org-agenda-done ((,class :foreground ,fg-alt)))
`(org-link ((,class :background ,bg-alt :underline nil :inherit link)))
`(org-roam-link ((,class :foreground ,green-alt-other
:inherit org-link)))
;; remove special styling from backlinks
`(org-roam-backlink ((,class))))
(setq org-todo-keyword-faces
`(("TODO" . (:foreground ,blue-intense :inherit fixed-pitch))
("NEXT" . (:foreground ,red-intense :inherit fixed-pitch))
("BUILD" . (:foreground ,red-intense :inherit fixed-pitch))
("WAIT" . (:foreground ,magenta-alt :inherit fixed-pitch))
("DONE" . (:foreground ,fg-alt :inherit fixed-pitch))
("CANCELED" . (:foreground ,fg-alt :inherit fixed-pitch))))))
I use spaceline.
(use-package spaceline
:config
(setq evil-normal-state-tag "N"
evil-insert-state-tag "I"
evil-visual-state-tag "V"
evil-operator-state-tag "O"
evil-replace-state-tag "R"
evil-motion-state-tag "M"
evil-emacs-state-tag "E")
(require 'spaceline-config)
(setq spaceline-highlight-face-func #'spaceline-highlight-face-evil-state)
;; modifications:
;; - always show org-clock (I often use org-clock for clocking tasks
;; that I do *outside* of emacs.)
;; - always show process (background tasks usually run in an
;; inactive window)
;; - show projectile-root
;; - hide file info (modified status, file size) when inactive
;; - hide right side when inactive (it has little useful info and is
;; less important than left side)
;;
;; note: patching has an additional benefit that `el-patch-validate'
;; can verify the original function did not change.
(el-patch-feature spaceline-config)
(el-patch-defun (el-patch-swap spaceline--theme rasen/--spaceline-theme) (left second-left &rest additional-segments)
"Convenience function for the spacemacs and emacs themes."
(spaceline-compile
`(,left
(anzu :priority 95)
auto-compile
(el-patch-add ((buffer-modified buffer-size)
:priority 98
:when active))
(el-patch-add projectile-root :when active)
,second-left
(major-mode :priority 79)
(process (el-patch-remove :when active))
((flycheck-error flycheck-warning flycheck-info)
:when active
:priority 89)
(minor-modes :when active
:priority 9)
(mu4e-alert-segment :when active)
(erc-track :when active)
(version-control :when active
:priority 78)
(org-pomodoro :when active)
(org-clock (el-patch-remove :when active))
nyan-cat)
`(which-function
(python-pyvenv :fallback python-pyenv)
(purpose :priority 94)
(battery :when active)
(selection-info :priority 95)
input-method
((buffer-encoding-abbrev
point-position
line-column)
:separator " | "
:priority 96
(el-patch-add :when active))
(global :when active)
,@additional-segments
(buffer-position :priority 99 (el-patch-add :when active))
(hud :priority 99 (el-patch-add :when active))))
(setq-default mode-line-format '("%e" (:eval (spaceline-ml-main)))))
(el-patch-defun (el-patch-swap spaceline-spacemacs-theme rasen/spaceline-theme) (&rest additional-segments)
"Install the modeline used by Spacemacs.
ADDITIONAL-SEGMENTS are inserted on the right, between `global' and
`buffer-position'."
(apply '(el-patch-swap spaceline--theme rasen/--spaceline-theme)
'((persp-name
workspace-number
window-number)
:fallback evil-state
:face highlight-face
:priority 100
(el-patch-add :when active))
'(((el-patch-remove buffer-modified buffer-size) buffer-id remote-host)
:priority 98)
additional-segments))
(rasen/spaceline-theme))
(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 "Input") ; check for custom four-family font first
(set-face-attribute 'fixed-pitch nil :family "Input" :height 65)
(set-face-attribute 'default nil :family "Input" :height 65))
((rasen/font-exists-p "Input Mono")
(set-face-attribute 'fixed-pitch nil :family "Input Mono" :height 65)
(set-face-attribute 'default nil :family "Input Mono" :height 65))
((rasen/font-exists-p "Fira Code Retina")
(set-face-attribute 'fixed-pitch nil :family "Fira Code Retina" :height 65)
(set-face-attribute 'default nil :family "Fira Code Retina" :height 65))
((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)))
(cond
((rasen/font-exists-p "Linux Libertine O")
(set-face-attribute 'variable-pitch nil :family "Linux Libertine O" :height 90))
((rasen/font-exists-p "Vollkorn")
(set-face-attribute 'variable-pitch nil :family "Vollkorn" :height 80))
((rasen/font-exists-p "DejaVu Sans")
(set-face-attribute 'variable-pitch nil :family "DejaVu Sans"))))
(rasen/set-my-fonts)
Apply my font settings 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)
Use variable-pitch fonts in org-mode.
(push "~/.emacs.d/site-lisp" load-path)
(require 'org-variable-pitch)
(setq org-variable-pitch-fixed-font "Input")
(set-face-attribute 'org-variable-pitch-fixed-face nil :height 65)
(set-face-attribute 'org-variable-pitch-fixed-face nil :weight 'regular)
(diminish 'org-variable-pitch-minor-mode)
(diminish 'buffer-face-mode)
;; Because asterisk in Follkorn is way too high, fontify leading asterisks with fixed pitch face instead.
(setq org-variable-pitch-fontify-headline-prefix t)
(add-hook 'org-mode-hook 'org-variable-pitch-minor-mode)
Patch adaptive-wrap, so it work better with org-variable-pitch-mode (i.e., make adaptive prefix fixed-pitch).
(el-patch-defun adaptive-wrap-prefix-function (beg end)
"Indent the region between BEG and END with adaptive filling."
;; Any change at the beginning of a line might change its wrap prefix, which
;; affects the whole line. So we need to "round-up" `end' to the nearest end
;; of line. We do the same with `beg' although it's probably not needed.
(goto-char end)
(unless (bolp) (forward-line 1))
(setq end (point))
(goto-char beg)
(forward-line 0)
(setq beg (point))
(while (< (point) end)
(let ((lbp (point)))
(put-text-property
(point) (progn (search-forward "\n" end 'move) (point))
'wrap-prefix
(let ((pfx (adaptive-wrap-fill-context-prefix
lbp (point))))
;; Remove any `wrap-prefix' property that
;; might have been added earlier.
;; Otherwise, we end up with a string
;; containing a `wrap-prefix' string
;; containing a `wrap-prefix' string ...
(remove-text-properties
0 (length pfx) '(wrap-prefix) pfx)
(let ((dp (get-text-property 0 'display pfx)))
(when (and dp (eq dp (get-text-property (1- lbp) 'display)))
;; There's a `display' property which covers not just the
;; prefix but also the previous newline. So it's not just making
;; the prefix more pretty and could interfere or even defeat our
;; efforts (e.g. it comes from `visual-fill-mode').
(remove-text-properties
0 (length pfx) '(display) pfx)))
(el-patch-add
(add-face-text-property 0 (length pfx) 'org-variable-pitch-fixed-face nil pfx))
pfx))))
`(jit-lock-bounds ,beg . ,end))
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)
:hook
(markdown-mode . rasen/activate-visual-fill-column)
(org-mode . rasen/activate-visual-fill-column)
:init
(defun rasen/activate-visual-fill-column ()
(interactive)
(setq-local fill-column 111)
(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
;; do not blink where beacon-mode plays badly
(defun rasen/dont-blink-predicate ()
(member major-mode '(notmuch-search-mode)))
(add-hook 'beacon-dont-blink-predicates #'rasen/dont-blink-predicate)
(beacon-mode 1))
Add project name to the title, so I can later analyze my app usage.
(setq-default frame-title-format
'("[%m] " (:eval (projectile-project-name))))