Emacs does not source this file automatically, so I need to instruct it to. Check org-babel documentation for more info. The following snippet is an adaptation of that idea and goes to my .emacs.d/init.el
.
;;; init.el --- The start of my configuration
;;; Commentary:
;;; Code:
(require 'package)
(setq package-enable-at-startup nil)
(package-initialize)
(defvar rasen/dotfiles-directory
(file-name-as-directory
(expand-file-name ".." (file-name-directory (file-truename user-init-file))))
"The path to the dotfiles directory.")
(require 'org-install)
(require 'ob-tangle)
(require 'el-patch)
;; org-babel fixes to tangle ALL matching sections
(defun rasen/map-regex (regex fn)
"Map the REGEX over the BUFFER executing FN.
FN is called with the match-data of the regex.
Returns the results of the FN as a list."
(save-excursion
(goto-char (point-min))
(let (res)
(save-match-data
(while (re-search-forward regex nil t)
(let ((f (match-data)))
(setq res
(append res
(list
(save-match-data
(funcall fn f))))))))
res)))
(el-patch-feature ob-core)
(el-patch-defun org-babel-expand-noweb-references (&optional info parent-buffer)
"Expand Noweb references in the body of the current source code block.
For example the following reference would be replaced with the
body of the source-code block named `example-block'.
<<example-block>>
Note that any text preceding the <<foo>> construct on a line will
be interposed between the lines of the replacement text. So for
example if <<foo>> is placed behind a comment, then the entire
replacement text will also be commented.
This function must be called from inside of the buffer containing
the source-code block which holds BODY.
In addition the following syntax can be used to insert the
results of evaluating the source-code block named `example-block'.
<<example-block()>>
Any optional arguments can be passed to example-block by placing
the arguments inside the parenthesis following the convention
defined by `org-babel-lob'. For example
<<example-block(a=9)>>
would set the value of argument \"a\" equal to \"9\". Note that
these arguments are not evaluated in the current source-code
block but are passed literally to the \"example-block\"."
(let* ((parent-buffer (or parent-buffer (current-buffer)))
(info (or info (org-babel-get-src-block-info 'light)))
(lang (nth 0 info))
(body (nth 1 info))
(ob-nww-start org-babel-noweb-wrap-start)
(ob-nww-end org-babel-noweb-wrap-end)
(new-body "")
(nb-add (lambda (text) (setq new-body (concat new-body text))))
index source-name evaluate prefix)
(with-temp-buffer
(setq-local org-babel-noweb-wrap-start ob-nww-start)
(setq-local org-babel-noweb-wrap-end ob-nww-end)
(insert body) (goto-char (point-min))
(setq index (point))
(while (and (re-search-forward (org-babel-noweb-wrap) nil t))
(save-match-data (setf source-name (match-string 1)))
(save-match-data (setq evaluate (string-match "(.*)" source-name)))
(save-match-data
(setq prefix
(buffer-substring (match-beginning 0)
(save-excursion
(beginning-of-line 1) (point)))))
;; add interval to new-body (removing noweb reference)
(goto-char (match-beginning 0))
(funcall nb-add (buffer-substring index (point)))
(goto-char (match-end 0))
(setq index (point))
(funcall
nb-add
(with-current-buffer parent-buffer
(save-restriction
(widen)
(mapconcat ;; Interpose PREFIX between every line.
#'identity
(split-string
(if evaluate
(let ((raw (org-babel-ref-resolve source-name)))
(if (stringp raw) raw (format "%S" raw)))
(or
;; Retrieve from the Library of Babel.
(nth 2 (assoc-string source-name org-babel-library-of-babel))
;; Return the contents of headlines literally.
(save-excursion
(when (org-babel-ref-goto-headline-id source-name)
(org-babel-ref-headline-body)))
;; Find the expansion of reference in this buffer.
(save-excursion
(goto-char (point-min))
(let* ((name-regexp
(org-babel-named-src-block-regexp-for-name
source-name))
(comment
(string= "noweb"
(cdr (assq :comments (nth 2 info)))))
(c-wrap
(lambda (s)
;; Comment, according to LANG mode,
;; string S. Return new string.
(with-temp-buffer
(funcall (intern (concat lang "-mode")))
(comment-region (point)
(progn (insert s) (point)))
(org-trim (buffer-string)))))
(expand-body
(lambda (i)
;; Expand body of code blocked
;; represented by block info I.
(let ((b (if (org-babel-noweb-p (nth 2 i) :eval)
(org-babel-expand-noweb-references i)
(nth 1 i))))
(if (not comment) b
(let ((cs (org-babel-tangle-comment-links i)))
(concat (funcall c-wrap (car cs)) "\n"
b "\n"
(funcall c-wrap (cadr cs)))))))))
(if (re-search-forward name-regexp nil t)
(el-patch-swap
(funcall expand-body
(org-babel-get-src-block-info 'light))
;; Found a source block named SOURCE-NAME.
;; Assume it is unique; do not look after
;; `:noweb-ref' header argument.
(mapconcat
#'identity
(rasen/map-regex name-regexp
(lambda (md)
(funcall expand-body
(org-babel-get-src-block-info 'light))))
"\n"))
;; Though luck. We go into the long process
;; of checking each source block and expand
;; those with a matching Noweb reference.
(let ((expansion nil))
(org-babel-map-src-blocks nil
(let* ((info (org-babel-get-src-block-info 'light))
(parameters (nth 2 info)))
(when (equal source-name
(cdr (assq :noweb-ref parameters)))
(push (funcall expand-body info) expansion)
(push (or (cdr (assq :noweb-sep parameters))
"\n")
expansion))))
(when expansion
(mapconcat #'identity
(nreverse (cdr expansion))
""))))))
;; Possibly raise an error if named block doesn't exist.
(if (or org-babel-noweb-error-all-langs
(member lang org-babel-noweb-error-langs))
(error "%s could not be resolved (see \
`org-babel-noweb-error-langs')"
(org-babel-noweb-wrap source-name))
"")))
"[\n\r]")
(concat "\n" prefix))))))
(funcall nb-add (buffer-substring index (point-max))))
new-body))
(org-babel-load-file (expand-file-name "emacs.org" rasen/dotfiles-directory))
;;; init.el ends here
You might notice that I don’t change load-path—that’s because my setup relies on org-plus-contrib
to be installed by NixOS.
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 ()
(interactive)
(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)
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))
A couple of functions to make configuration a little bit easier.
(defun nmap (key action)
(define-key evil-normal-state-map (kbd key) action))
(defun vmap (key action)
(define-key evil-visual-state-map (kbd key) action))
(defun imap (key action)
(define-key evil-insert-state-map (kbd key) action))
(defun mmap (key action)
(define-key evil-motion-state-map (kbd key) action))
(defun omap (key action)
(define-key evil-operator-state-map (kbd key) action))
Use SPC
as one of leaders.
(nmap "SPC" nil)
(vmap "SPC" nil)
(mmap "SPC" nil)
Hard way: prohibit usage of keybinding I have more efficient binding for.
(defmacro rasen/hard-way (key)
`(lambda () (interactive) (error "Don't use this key! Use %s instead" ,key)))
Prohibit usage of arrows.
(mmap "<left>" (rasen/hard-way "h"))
(mmap "<up>" (rasen/hard-way "j"))
(mmap "<down>" (rasen/hard-way "k"))
(mmap "<right>" (rasen/hard-way "l"))
Swap .
and ;
.
(nmap ";" 'evil-repeat)
(nmap "C-;" 'evil-repeat-pop)
(mmap "." 'evil-repeat-find-char)
(nmap "." nil)
(mmap "g." 'goto-last-change)
(mmap "SPC ;" 'eval-expression)
Close other window.
(defun rasen/quit-other ()
(interactive)
(other-window 1)
(quit-window))
(mmap "SPC q" 'rasen/quit-other)
Move to beginning/end of line with H
and L
respectively.
(defun rasen/smart-move-beginning-of-line (arg)
"Move point back to indentation of beginning of line.
Move point to the first non-whitespace character on this line.
If point is already there, move to the beginning of the line.
Effectively toggle between the first non-whitespace character and
the beginning of the line.
If ARG is not nil or 1, move forward ARG - 1 lines first. If
point reaches the beginning or end of the buffer, stop there."
(interactive "^p")
(setq arg (or arg 1))
;; Move lines first
(when (/= arg 1)
(let ((line-move-visual nil))
(forward-line (1- arg))))
(let ((orig-point (point)))
(back-to-indentation)
(when (= orig-point (point))
(move-beginning-of-line 1))))
(mmap "H" 'rasen/smart-move-beginning-of-line)
(mmap "L" 'evil-end-of-line)
Save buffer with SPC SPC
.
(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.
(mmap "k" 'evil-next-visual-line)
(mmap "j" 'evil-previous-visual-line)
(mmap "gk" 'evil-next-line)
(mmap "gj" 'evil-previous-line)
(omap "k" 'evil-next-line)
(omap "j" 'evil-previous-line)
(omap "gk" 'evil-next-visual-line)
(omap "gj" 'evil-previous-visual-line)
(mmap "C-h" 'windmove-left)
(mmap "C-k" 'windmove-down)
(mmap "C-j" 'windmove-up)
(mmap "C-l" 'windmove-right)
(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
:bind (:map evil-normal-state-map
("C-a" . evil-numbers/inc-at-pt)
("C-x" . evil-numbers/dec-at-pt)))
Now, remap C-x
to RET
. (Because C-x
is used for decrementing numbers.)
(mmap "RET" (lookup-key (current-global-map) (kbd "C-x")))
(use-package evil-swap-keys
:config
(global-evil-swap-keys-mode)
(add-hook 'prog-mode-hook #'evil-swap-keys-swap-number-row))
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 compile
:config
(setq compilation-scroll-output t))
And evil commands to go to navigate errors.
(mmap "SPC ," 'previous-error)
(mmap "SPC ." 'next-error)
(mmap "M-," 'previous-error)
(mmap "M-." 'next-error)
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)
(load "server")
(unless (server-running-p)
(server-start))
(use-package ivy
:demand
:bind (:map evil-motion-state-map
("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)))
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)
Remap k-j in ivy-occur
, make it default to emacs state.
(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)
(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
:bind (:map evil-motion-state-map
("SPC x" . counsel-M-x)
("SPC f" . counsel-find-file)
("g r" . counsel-git-grep)
("g /" . counsel-rg)
:map read-expression-map
("C-r" . counsel-expression-history))
:config
(counsel-mode 1))
Jump anywhere with a few keystrokes in tree-like way.
(use-package avy
:bind
(:map evil-motion-state-map
("K" . avy-goto-char))
:custom
;; easy workman keys (excluding pinky)
(avy-keys '(?s ?h ?t ?n ?e ?o ?d ?r ?u ?p)))
Edit grep buffers and apply changes to the files.
(use-package wgrep)
A good mode to highlight whitespace issues (leading/trainiling spaces/newlines) and too long lines.
(use-package whitespace
:diminish (global-whitespace-mode
whitespace-mode
whitespace-newline-mode)
:config
(setq-default whitespace-line-column 120
whitespace-style '(face
tab-mark
empty
trailing
lines-tail))
Original face overrides foreground, so you don’t see syntax highlight. Use underlines to show characters past limit.
(set-face-attribute 'whitespace-line nil
:foreground nil
:background nil
:underline (list :color "yellow4" :style 'wave))
Activate the mode in all programming modes.
(add-hook 'prog-mode-hook 'whitespace-mode))
Fix whitespaces on file save.
(use-package whitespace-cleanup-mode
:diminish whitespace-cleanup-mode
:config
(global-whitespace-cleanup-mode 1))
It’s enable 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
:bind (:map evil-motion-state-map
("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)
(setq-default projectile-completion-system 'ivy)
(projectile-mode))
(use-package counsel-projectile
:after projectile
:config
(counsel-projectile-mode))
(el-patch-feature projectile)
(with-eval-after-load 'projectile
(el-patch-defun projectile-default-generic-command (project-type command-type)
"Generic retrieval of COMMAND-TYPEs default cmd-value for PROJECT-TYPE.
If found, checks if value is symbol or string. In case of symbol
resolves to function `funcall's. Return value of function MUST
be string to be executed as command."
(let ((command (plist-get (alist-get project-type projectile-project-types) command-type)))
(cond
((stringp command) command)
((functionp command)
(if (fboundp command)
(funcall (symbol-function command))))
((and (not command) (eq command-type 'compilation-dir))
;; `compilation-dir' is special in that it is used as a fallback for the root
nil)
(el-patch-remove
(t
(user-error "The value for: %s in project-type: %s was neither a function nor a string." command-type project-type)))))))
(use-package magit
:bind (:map evil-motion-state-map
("g m" . magit-status))
:diminish auto-revert-mode
:init
(global-set-key (kbd "C-c m") (rasen/hard-way "g m"))
:config
<<magit-config>>
)
Do not put files into trash can. Delete them for real.
(setq-default magit-delete-by-moving-to-trash nil)
(setq-default magit-completing-read-function 'ivy-completing-read)
Evilify magit-mode.
(use-package evil-magit
:config
<<evil-magit-config>>
)
(setq evil-magit-use-y-for-yank t)
Evilify magit-blame.
(evil-define-key 'normal magit-blame-read-only-mode-map
(kbd "k") 'evil-next-visual-line
(kbd "j") 'evil-previous-visual-line
(kbd "C-k") 'magit-blame-next-chunk
(kbd "C-j") 'magit-blame-previous-chunk
(kbd "gk") 'magit-blame-next-chunk-same-commit
(kbd "gj") 'magit-blame-previous-chunk-same-commit)
(evil-define-key 'motion magit-blame-mode-map
(kbd "SPC") (lookup-key evil-motion-state-map (kbd "SPC")))
(evil-define-key `(,evil-magit-state visual) magit-mode-map
(kbd "j") 'evil-previous-visual-line
(kbd "k") 'evil-next-visual-line
(kbd "C-j") 'magit-section-backward
(kbd "C-k") 'magit-section-forward
(kbd "gj") 'magit-section-backward-sibling
(kbd "gk") 'magit-section-forward-sibling)
Add a magit command to push HEAD
into a specified ref.
(defun rasen/magit-push-head (target args)
"Push HEAD to a branch read in the minibuffer."
(interactive
(list (magit-read-remote-branch "Push HEAD to"
nil nil nil 'confirm)
(magit-push-arguments)))
(magit-git-push "HEAD" target args))
(if (fboundp 'transient-insert-suffix)
(transient-insert-suffix 'magit-push 'magit-push-other
'(1 "h" "HEAD" rasen/magit-push-head))
(magit-define-popup-action 'magit-push-popup
?h "HEAD" 'rasen/magit-push-head))
(evil-magit)
(defun rasen/magit-fco-master ()
"Fetch origin/master and checkout it."
(interactive)
(magit-git-fetch "origin" "master")
(magit-checkout "origin/master"))
(evil-magit-define-key evil-magit-state 'magit-mode-map
"g m" 'rasen/magit-fco-master)
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)
(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))
(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
:bind (:map evil-insert-state-map
("C-/" . hippie-expand))
:config
(setq hippie-expand-try-functions-list
'(try-expand-dabbrev-visible
try-expand-dabbrev
try-expand-dabbrev-all-buffers
try-complete-file-name-partially
try-complete-file-name
try-expand-line
try-expand-list)))
(use-package color-identifiers-mode
:commands (color-identifiers-mode
global-color-identifiers-mode)
:diminish (color-identifiers-mode
global-color-identifiers-mode))
Shamelessly stealed from https://github.com/purcell/emacs.d.
(defun rename-this-file-and-buffer (new-name)
"Renames both current buffer and file it's visiting to NEW-NAME."
(interactive "FNew name: ")
(let ((name (buffer-name))
(filename (buffer-file-name)))
(unless filename
(error "Buffer '%s' is not visiting file!" name))
(if (get-buffer new-name)
(message "A buffer named '%s' already exists!" new-name)
(progn
(when (file-exists-p filename)
(rename-file filename new-name 1))
(rename-buffer new-name)
(set-visited-file-name new-name)))))
(defun delete-this-file-and-buffer ()
"Delete the current file, and kill the buffer."
(interactive)
(or (buffer-file-name) (error "No file is currently being edited"))
(when (yes-or-no-p (format "Really delete '%s'?"
(file-name-nondirectory buffer-file-name)))
(delete-file (buffer-file-name))
(kill-buffer)))
(defun add-to-path (str)
"Add an STR to the PATH environment variable."
(setenv "PATH" (concat str ":" (getenv "PATH"))))
(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
: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
:commands lsp
:init
(add-hook 'rust-mode-hook #'lsp)
:config
(setq lsp-prefer-flymake nil))
(use-package lsp-ui
:commands lsp-ui-mode)
(use-package company-lsp
:commands company-lsp)
(use-package org
:mode ("\\.org$" . org-mode)
:bind (("C-c l" . org-store-link)
:map evil-motion-state-map
("SPC c" . org-capture)
("SPC a" . org-agenda)
:map 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)
Set tags faces, so they are more distinguishable.
(set-face-attribute 'org-tag nil :inverse-video t)
(set-face-attribute 'org-archived nil :inverse-video nil)
(set-face-attribute 'org-ellipsis nil :inverse-video nil)
(setq org-tag-faces
'(("ARCHIVE" . (:inverse-video nil))))
Open pdfs in external viewer:
(add-to-list 'org-file-apps '("\\.pdf\\'" . "zathura %s"))
Use whitespace-mode
in Org (but don’t show too long lines).
(add-hook 'org-mode-hook (lambda ()
(setq-local whitespace-style '(face
tab-mark
empty
trailing))
(whitespace-mode t)))
My directory for org files.
(setq rasen/org-directory "~/org")
My helper to find all org files in a directory.
(defun rasen/org-files-in-dir (dir)
(f-files dir
(lambda (file) (f-ext? file "org"))
nil))
Package for f-files
and f-ext?
functions.
(use-package f
:commands (f-files f-ext?))
For some reason, org-drill fails to load with the following error:
Debugger entered–Lisp error: (void-function copy-list) copy-list((1 (quote org-drill-visible-cloze-face) nil)) org-drill–compute-cloze-keywords() (defvar org-drill-cloze-keywords (org-drill–compute-cloze-keywords) nil) require(org-drill)
I googled it up and copy-list
seems to be defined in cl
, so load it first:
(require 'cl)
(require 'org-drill)
(setq org-drill-scope (rasen/org-files-in-dir "~/org/drill"))
(add-to-list 'org-modules 'org-drill)
(setq org-drill-leech-failure-threshold 10
org-drill-leech-method 'warn)
https://bitbucket.org/eeeickythump/org-drill/issues/62/org-drill-doesnt-work-with-org-mode-92
(el-patch-feature org-drill)
(el-patch-defun org-drill-hide-subheadings-if (test)
"TEST is a function taking no arguments. TEST will be called for each
of the immediate subheadings of the current drill item, with the point
on the relevant subheading. TEST should return nil if the subheading is
to be revealed, non-nil if it is to be hidden.
Returns a list containing the position of each immediate subheading of
the current topic."
(let ((drill-entry-level (org-current-level))
(drill-sections nil))
(org-show-subtree)
(save-excursion
(org-map-entries
(lambda ()
(when (and (not (org-invisible-p))
(> (org-current-level) drill-entry-level))
(when (or (/= (org-current-level) (1+ drill-entry-level))
(funcall test))
(hide-subtree))
(push (point) drill-sections)))
(el-patch-swap "" t) 'tree))
(reverse drill-sections)))
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-todo-keyword-faces
'(("TODO" . (:foreground "dodger blue" :weight bold))
("NEXT" . (:box t :foreground "red" :weight bold))
("BUILD" . (:box t :foreground "red" :weight bold))
("WAIT" . (:box t :foreground "magenta" :weight bold))
("DONE" . (:foreground "grey" :weight bold))
("CANCELED" . (:foreground "gray" :weight bold))))
(setq-default org-use-fast-todo-selection t)
Switch task state with SPC t
.
(evil-define-key 'normal org-mode-map (kbd "SPC t") 'org-todo)
When repeated task is finished, go back to TODO
state.
(setq-default org-todo-repeat-to-state "TODO")
Log state changes to “LOGBOOK” drawer.
(setq-default org-log-into-drawer 't)
Save CLOSED
timestamp when task is done.
(setq org-log-done t)
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"))
(general-def 'normal org-mode-map
"SPC n" #'rasen/org-do-today
"SPC s" #'org-schedule
"C-c C-s" (rasen/hard-way "SPC s")
"SPC d" #'org-deadline
"C-c C-d" (rasen/hard-way "SPC d"))
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)
Clocking bindings.
(general-def 'motion
"SPC o" #'org-clock-out
"SPC l" #'org-clock-in-last
"SPC j" #'org-clock-goto)
(general-def
:keymaps 'org-mode-map
:states 'normal
"SPC i" #'org-clock-in
"C-c C-x C-i" (rasen/hard-way "SPC i"))
I use an extension that adds page url to the title (used for page tracking). Strip it down here
(defun rasen/strip-url-from-title (title)
(message "stripping: %s" title)
(replace-regexp-in-string
" @ [^ ]*$"
""
(replace-regexp-in-string " \\[[^]]*\\]\\[[^]]*\\]$" "" title)))
My capture templates.
(setq org-capture-templates
`(("u"
"Task: Read this URL"
entry
(file+headline "refile.org" "Articles To Read")
,(concat "* TODO %(rasen/strip-url-from-title \"%:description\")\n:PROPERTIES:\n:CREATED: %U\n:END:\n%:link\n")
:immediate-finish t)
("w"
"Capture web snippet"
entry
(file+headline "refile.org" "Inbox")
,(concat "* Fact: '%(rasen/strip-url-from-title \"%:description\")' :"
(format "%s" org-drill-question-tag)
":\n:PROPERTIES:\n:CREATED: %U\n:SOURCE_URL: %:link\n:END:\n%i\n%?\n")
:immediate-finish t)
("j" "Journal entry" plain
(file+datetree+prompt "~/org/journal.org")
,(concat
"TIL:\n- %?\n\n"
;; %U does not here work as timestamp is hijacked by
;; file+datetime+prompt
"%(format-time-string (org-time-stamp-format t t))"
"\n"))
("f"
"Capture normal snippet"
entry
(file+headline "my-facts.org" "Inbox")
,(concat "* Fact: '%f' :"
(format "%s" org-drill-question-tag)
":\n:PROPERTIES:\n:CREATED: %U\n:SOURCE_URL: [[%l][%f]]\n:END:\n%i\n%?\n")
:immediate-finish t)
("t" "todo" entry (file "~/org/refile.org")
"* TODO %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n" :clock-in t :clock-resume t)
("m" "meeting" entry (file "~/org/refile.org")
"* %? :Meeting:\n:PROPERTIES:\n:CREATED: %U\n:END:\n" :clock-in t :clock-resume t)
("n" "note" entry (file "~/org/refile.org")
"* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n")))
Enable org-protocol.
(require 'org-protocol)
%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"))
(defun rasen/org-refile-files ()
(rasen/org-files-in-dir rasen/org-directory))
;; non-nil values work bad with ivy
(setq-default org-refile-use-outline-path 'file)
(setq-default org-outline-path-complete-in-steps nil)
(setq org-refile-targets
'(;(nil :maxlevel . 3)
(org-agenda-files :tag . "honeypot")
(org-agenda-files :tag . "PROJECT")
(org-agenda-files :maxlevel . 2)
(rasen/org-refile-files :maxlevel . 1)))
Better bindings.
(evil-define-key 'normal org-mode-map (kbd "SPC w") 'org-refile)
(evil-define-key 'normal org-mode-map (kbd "C-c C-w") (rasen/hard-way "SPC w"))
I like my archive sibling be the last child. The default org-refile ignores that at refiles all entries after archive.
So here is a little patch to refile before archive sibling if it is present.
(defun rasen/org-goto-last-child ()
"Goto the last child, even if it is invisible.
Return t when a child was found. Otherwise don't move point and return nil."
(when (org-goto-first-child)
(while (org-goto-sibling))
t))
(defun rasen/org-goto-last-archive ()
(and (rasen/org-goto-last-child)
(string= org-archive-sibling-heading (org-get-heading t t t t))
(member org-archive-tag (org-get-tags))
(point)))
(require 'org-archive) ; for org-archive-sibling-heading
(el-patch-feature org)
(el-patch-defun org-refile (&optional arg default-buffer rfloc msg)
"Move the entry or entries at point to another heading.
The list of target headings is compiled using the information in
`org-refile-targets', which see.
At the target location, the entry is filed as a subitem of the
target heading. Depending on `org-reverse-note-order', the new
subitem will either be the first or the last subitem.
If there is an active region, all entries in that region will be
refiled. However, the region must fulfill the requirement that
the first heading sets the top-level of the moved text.
With a `\\[universal-argument]' ARG, the command will only visit the target \
location
and not actually move anything.
With a prefix `\\[universal-argument] \\[universal-argument]', go to the \
location where the last
refiling operation has put the subtree.
With a numeric prefix argument of `2', refile to the running clock.
With a numeric prefix argument of `3', emulate `org-refile-keep'
being set to t and copy to the target location, don't move it.
Beware that keeping refiled entries may result in duplicated ID
properties.
RFLOC can be a refile location obtained in a different way.
MSG is a string to replace \"Refile\" in the default prompt with
another verb. E.g. `org-copy' sets this parameter to \"Copy\".
See also `org-refile-use-outline-path'.
If you are using target caching (see `org-refile-use-cache'), you
have to clear the target cache in order to find new targets.
This can be done with a `0' prefix (`C-0 C-c C-w') or a triple
prefix argument (`C-u C-u C-u C-c C-w')."
(interactive "P")
(if (member arg '(0 (64)))
(org-refile-cache-clear)
(let* ((actionmsg (cond (msg msg)
((equal arg 3) "Refile (and keep)")
(t "Refile")))
(regionp (org-region-active-p))
(region-start (and regionp (region-beginning)))
(region-end (and regionp (region-end)))
(org-refile-keep (if (equal arg 3) t org-refile-keep))
pos it nbuf file level reversed)
(setq last-command nil)
(when regionp
(goto-char region-start)
(or (bolp) (goto-char (point-at-bol)))
(setq region-start (point))
(unless (or (org-kill-is-subtree-p
(buffer-substring region-start region-end))
(prog1 org-refile-active-region-within-subtree
(let ((s (point-at-eol)))
(org-toggle-heading)
(setq region-end (+ (- (point-at-eol) s) region-end)))))
(user-error "The region is not a (sequence of) subtree(s)")))
(if (equal arg '(16))
(org-refile-goto-last-stored)
(when (or
(and (equal arg 2)
org-clock-hd-marker (marker-buffer org-clock-hd-marker)
(prog1
(setq it (list (or org-clock-heading "running clock")
(buffer-file-name
(marker-buffer org-clock-hd-marker))
""
(marker-position org-clock-hd-marker)))
(setq arg nil)))
(setq it
(or rfloc
(let (heading-text)
(save-excursion
(unless (and arg (listp arg))
(org-back-to-heading t)
(setq heading-text
(replace-regexp-in-string
org-bracket-link-regexp
"\\3"
(or (nth 4 (org-heading-components))
""))))
(org-refile-get-location
(cond ((and arg (listp arg)) "Goto")
(regionp (concat actionmsg " region to"))
(t (concat actionmsg " subtree \""
heading-text "\" to")))
default-buffer
(and (not (equal '(4) arg))
org-refile-allow-creating-parent-nodes)))))))
(setq file (nth 1 it)
pos (nth 3 it))
(when (and (not arg)
pos
(equal (buffer-file-name) file)
(if regionp
(and (>= pos region-start)
(<= pos region-end))
(and (>= pos (point))
(< pos (save-excursion
(org-end-of-subtree t t))))))
(error "Cannot refile to position inside the tree or region"))
(setq nbuf (or (find-buffer-visiting file)
(find-file-noselect file)))
(if (and arg (not (equal arg 3)))
(progn
(pop-to-buffer-same-window nbuf)
(goto-char (cond (pos)
((org-notes-order-reversed-p) (point-min))
(t (point-max))))
(org-show-context 'org-goto))
(if regionp
(progn
(org-kill-new (buffer-substring region-start region-end))
(org-save-markers-in-region region-start region-end))
(org-copy-subtree 1 nil t))
(with-current-buffer (setq nbuf (or (find-buffer-visiting file)
(find-file-noselect file)))
(setq reversed (org-notes-order-reversed-p))
(org-with-wide-buffer
(if pos
(progn
(goto-char pos)
(setq level (org-get-valid-level (funcall outline-level) 1))
(goto-char
(if reversed
(or (outline-next-heading) (point-max))
(or (el-patch-add (save-excursion (rasen/org-goto-last-archive)))
(save-excursion (org-get-next-sibling))
(org-end-of-subtree t t)
(point-max)))))
(setq level 1)
(if (not reversed)
(goto-char (point-max))
(goto-char (point-min))
(or (outline-next-heading) (goto-char (point-max)))))
(unless (bolp) (newline))
(org-paste-subtree level nil nil t)
(when org-log-refile
(org-add-log-setup 'refile nil nil org-log-refile)
(unless (eq org-log-refile 'note)
(save-excursion (org-add-log-note))))
(and org-auto-align-tags
(let ((org-loop-over-headlines-in-active-region nil))
(org-align-tags)))
(let ((bookmark-name (plist-get org-bookmark-names-plist
:last-refile)))
(when bookmark-name
(with-demoted-errors
(bookmark-set bookmark-name))))
;; If we are refiling for capture, make sure that the
;; last-capture pointers point here
(when (bound-and-true-p org-capture-is-refiling)
(let ((bookmark-name (plist-get org-bookmark-names-plist
:last-capture-marker)))
(when bookmark-name
(with-demoted-errors
(bookmark-set bookmark-name))))
(move-marker org-capture-last-stored-marker (point)))
(when (fboundp 'deactivate-mark) (deactivate-mark))
(run-hooks 'org-after-refile-insert-hook)))
(unless org-refile-keep
(if regionp
(delete-region (point) (+ (point) (- region-end region-start)))
(org-preserve-local-variables
(delete-region
(and (org-back-to-heading t) (point))
(min (1+ (buffer-size)) (org-end-of-subtree t t) (point))))))
(when (featurep 'org-inlinetask)
(org-inlinetask-remove-END-maybe))
(setq org-markers-to-move nil)
(message (concat actionmsg " to \"%s\" in file %s: done") (car it) file)))))))
(setq-default org-archive-default-command 'org-archive-to-archive-sibling)
Bindings.
(evil-define-key 'normal org-mode-map (kbd "SPC r") 'org-archive-subtree-default)
(evil-define-key 'normal org-mode-map (kbd "C-c C-x C-a") (rasen/hard-way "SPC r"))
Set my org files location.
(setq org-directory "~/org"
org-default-notes-file "~/org/refile.org"
org-agenda-files (rasen/org-files-in-dir "~/org"))
Configure my agenda view.
(setq org-agenda-span 6
org-agenda-start-day "-1d")
(setq org-agenda-custom-commands
'(("N" tags "+TODO=\"NEXT\"-PROJECT|+TODO=\"WAIT\"-PROJECT")
("n" todo-tree "NEXT")
("p" tags "+PROJECT/+NEXT") ; active projects
("P" tags "+PROJECT/-DONE-CANCELED") ; all projects
))
Configure stuck projects.
(setq org-tags-exclude-from-inheritance '("PROJECT"))
(setq org-use-tag-inheritance nil)
(setq org-stuck-projects
'("+PROJECT/-TODO-DONE-CANCELED-WAIT" ("NEXT" "WAIT") nil ""))
org-agenda-list-stuck-projects
marks project as unstuck if its header matches any of specified keywords. This makes all NEXT
projects automatically unstuck.
Fix this by skipping the first line in org-agenda-skip-function
.
(el-patch-feature org-agenda)
(el-patch-defun org-agenda-list-stuck-projects (&rest ignore)
"Create agenda view for projects that are stuck.
Stuck projects are project that have no next actions. For the definitions
of what a project is and how to check if it stuck, customize the variable
`org-stuck-projects'."
(interactive)
(let* ((org-agenda-overriding-header
(or org-agenda-overriding-header "List of stuck projects: "))
(matcher (nth 0 org-stuck-projects))
(todo (nth 1 org-stuck-projects))
(tags (nth 2 org-stuck-projects))
(gen-re (org-string-nw-p (nth 3 org-stuck-projects)))
(todo-wds
(if (not (member "*" todo)) todo
(org-agenda-prepare-buffers (org-agenda-files nil 'ifmode))
(org-delete-all org-done-keywords-for-agenda
(copy-sequence org-todo-keywords-for-agenda))))
(todo-re (and todo
(format "^\\*+[ \t]+\\(%s\\)\\>"
(mapconcat #'identity todo-wds "\\|"))))
(tags-re (cond ((null tags) nil)
((member "*" tags) org-tag-line-re)
(tags
(let ((other-tags (format "\\(?:%s:\\)*" org-tag-re)))
(concat org-outline-regexp-bol
".*?[ \t]:"
other-tags
(regexp-opt tags t)
":" other-tags "[ \t]*$")))
(t nil)))
(re-list (delq nil (list todo-re tags-re gen-re)))
(skip-re
(if (null re-list)
(error "Missing information to identify unstuck projects")
(mapconcat #'identity re-list "\\|")))
(org-agenda-skip-function
;; Skip entry if `org-agenda-skip-regexp' matches anywhere
;; in the subtree.
`(lambda ()
(and (save-excursion
(let ((case-fold-search nil)
(el-patch-add (subtree-end (save-excursion (org-end-of-subtree t)))))
(el-patch-add (forward-line))
(re-search-forward
,skip-re
(el-patch-swap
(save-excursion (org-end-of-subtree t))
subtree-end)
t)))
(progn (outline-next-heading) (point))))))
(org-tags-view nil matcher)
(setq org-agenda-buffer-name (buffer-name))
(with-current-buffer org-agenda-buffer-name
(setq org-agenda-redo-command
`(org-agenda-list-stuck-projects ,current-prefix-arg))
(let ((inhibit-read-only t))
(add-text-properties
(point-min) (point-max)
`(org-redo-cmd ,org-agenda-redo-command))))))
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)
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)
(setq org-tags-exclude-from-inheritance '("crypt"))
(setq org-crypt-key "[email protected]")
(add-hook 'org-babel-pre-tangle-hook 'org-decrypt-entries t)
(use-package org-pomodoro
:commands (org-pomodoro)
:config
(setq org-pomodoro-keep-killed-pomodoro-time 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)
(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"))
(evil-define-minor-mode-key 'normal 'org-src-mode
(kbd "'") 'org-edit-src-exit
(kbd "C-c '") (rasen/hard-way "'"))
(evil-define-key 'motion org-agenda-mode-map
"k" 'org-agenda-next-line
"j" 'org-agenda-previous-line
"gk" 'org-agenda-next-item
"gj" 'org-agenda-previous-item
(kbd "C-k") 'org-agenda-next-item
(kbd "C-j") 'org-agenda-previous-item
"K" 'org-agenda-priority-down
"J" 'org-agenda-priority-up
(kbd "M-k") 'org-agenda-drag-line-forward
(kbd "M-j") 'org-agenda-drag-line-backward)
(evil-define-key 'motion org-agenda-mode-map
;; unset prefix
(kbd "SPC") nil
(kbd "SPC s") 'org-agenda-schedule
(kbd "C-c C-s") (rasen/hard-way "SPC s")
(kbd "SPC d") 'org-agenda-deadline
(kbd "C-c C-d") (rasen/hard-way "SPC d")
(kbd "SPC w") 'org-agenda-refile
(kbd "C-c C-w") (rasen/hard-way "SPC w")
"go" 'org-agenda-open-link))
(use-package elisp-mode
:ensure nil ; built-in
:config
<<elisp-mode-config>>
)
Eval last sexp Vim-style.
(evil-define-operator rasen/evil-eval (beg end type)
"Evaluate region."
(if (eq type 'block)
(evil-apply-on-block 'eval-region beg end nil)
(eval-region beg end)))
(evil-define-key 'motion emacs-lisp-mode-map (kbd "SPC e") 'eval-last-sexp)
(evil-define-key 'visual emacs-lisp-mode-map (kbd "SPC e") 'rasen/evil-eval)
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")
(define-key haskell-mode-map [f8] 'haskell-navigate-imports)
(define-key haskell-mode-map (kbd "C-c C-b") 'haskell-compile)
(define-key haskell-mode-map (kbd "C-c v c") 'haskell-cabal-visit-file)
;; haskell-interactive-mode
(define-key haskell-mode-map (kbd "C-x C-d") nil)
(define-key haskell-mode-map (kbd "C-c C-z") 'haskell-interactive-switch)
(define-key haskell-mode-map (kbd "C-c C-l") 'haskell-process-load-file)
(define-key haskell-mode-map (kbd "C-c C-t") 'haskell-process-do-type)
(define-key haskell-mode-map (kbd "C-c C-i") 'haskell-process-do-info)
(define-key haskell-mode-map (kbd "C-c M-.") nil)
(define-key haskell-mode-map (kbd "C-c C-d") nil)
;; Disable popups (i.e., report errors in the interactive shell).
(setq haskell-interactive-popup-errors nil)
(setq haskell-process-suggest-remove-import-lines t
haskell-process-auto-import-loaded-modules t)
(with-eval-after-load 'align
(add-to-list 'align-rules-list
'(haskell-types
(regexp . "\\(\\s-+\\)\\(::\\|∷\\)\\s-+")
(modes . '(haskell-mode literate-haskell-mode))))
(add-to-list 'align-rules-list
'(haskell-assignment
(regexp . "\\(\\s-+\\)=\\s-+")
(modes . '(haskell-mode literate-haskell-mode))))
(add-to-list 'align-rules-list
'(haskell-arrows
(regexp . "\\(\\s-+\\)\\(->\\|→\\)\\s-+")
(modes . '(haskell-mode literate-haskell-mode))))
(add-to-list 'align-rules-list
'(haskell-left-arrows
(regexp . "\\(\\s-+\\)\\(<-\\|←\\)\\s-+")
(modes . '(haskell-mode literate-haskell-mode))))))
(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 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" ".")))))
(exitcode (and executable
(el-patch-swap
(call-process executable nil nil nil "--print-config" ".")
(el-patch-literal command (apply 'call-process (car command) nil nil nil (cdr command)))))))
(eq exitcode 0)))
(use-package rjsx-mode
:mode "\\.js$"
:config
(setq-default js-indent-level 2))
(use-package typescript-mode
:init
(add-hook 'web-mode-hook
(lambda ()
(when (or (string-equal "tsx" (file-name-extension buffer-file-name))
(string-equal "ts" (file-name-extension buffer-file-name)))
(typescript-mode))))
:config
(setq-default typescript-indent-level 2))
(use-package tide
:commands (tide-setup
tide-hl-identifier-mode
tide-format-before-save
rasen/setup-tide-mode)
:hook
(typescript-mode . rasen/setup-tide-mode)
:init
(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)
:init
(defun rasen/setup-prettier ()
(add-to-list 'exec-path (string-trim (shell-command-to-string "npm bin")))
(setq-local prettier-js-command (executable-find "prettier")))
(add-hook 'web-mode-hook #'rasen/setup-prettier))
(use-package flycheck-jest
:after flycheck)
(use-package purescript-mode
:mode "\\.purs$"
:config
(add-hook 'purescript-mode-hook 'turn-on-purescript-indentation))
(use-package psc-ide
:commands (psc-ide-mode)
:init
(add-hook 'purescript-mode-hook 'psc-ide-mode))
(use-package php-mode
:mode "\\.php$")
(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 groovy-mode
:mode "\\.\\(groovy\\|gradle\\)$")
(use-package go-mode
:mode "\\.go$")
(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)
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 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 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 notmuch-archive-tags '("-unread"))
(setq notmuch-saved-searches
'(
(:name "unread" :query "tag:unread and not tag:nixos and not tag:rust" :key "u")
(:name "unread-inbox" :query "tag:inbox and tag:unread" :key "i")
(:name "unread-egoless" :query "tag:egoless and tag:unread" :key "e")
(:name "unread-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/rasen/dotfiles/notmuch.sh" "*mbsync*"))
(define-key 'notmuch-hello-mode-map "f" 'rasen/mbsync)
(define-key 'notmuch-hello-mode-map "g" 'notmuch-refresh-all-buffers)
(define-key 'notmuch-search-mode-map "g" 'notmuch-refresh-all-buffers)
(define-key 'notmuch-show-mode-map "g" 'notmuch-refresh-all-buffers)
(define-key 'notmuch-search-mode-map "k" 'notmuch-search-archive-thread)
(define-key 'notmuch-show-mode-map "k" 'notmuch-show-archive-thread-then-next)
;; remap old function
(define-key 'notmuch-search-mode-map "K" 'notmuch-tag-jump)
(define-key 'notmuch-show-mode-map "K" 'notmuch-tag-jump)
(define-key 'notmuch-show-mode-map (kbd "M-u")
(lambda ()
(interactive)
(notmuch-show-tag '("+unread"))))
;; notmuch-tag-formats
(setq-default notmuch-tagging-keys
'(("a" notmuch-archive-tags "Archive")
("u" notmuch-show-mark-read-tags "Mark read")
("m" ("+muted") "Mute")
("f" ("+flagged") "Flag")
("s" ("+spam" "-inbox") "Mark as spam")
("d" ("+deleted" "-inbox") "Delete")))
(require 'org-notmuch))
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)
I use monokai color scheme.
(use-package monokai-theme
:config
(load-theme 'monokai t)
(let* (;; Darker and lighter accented colors
(monokai-yellow-d "#BEB244")
(monokai-yellow-l "#FFF7A8")
(monokai-orange-d "#D47402")
(monokai-orange-l "#FFAC4A")
(monokai-red-d "#F70057")
(monokai-red-l "#FA518D")
(monokai-magenta-d "#FB35EA")
(monokai-magenta-l "#FE8CF4")
(monokai-violet-d "#945AFF")
(monokai-violet-l "#C9ACFF")
(monokai-blue-d "#40CAE4")
(monokai-blue-l "#92E7F7")
(monokai-cyan-d "#74DBCD")
(monokai-cyan-l "#D3FBF6")
(monokai-green-d "#86C30D")
(monokai-green-l "#BBEF53")
(monokai-gray-d "#35331D")
(monokai-gray-l "#7B7962"))
(custom-theme-set-faces
'monokai
;; If org-block-end-line happens to be the last line in the
;; collapsed tree, the whole line after ellipsis is highlighted in
;; the background color, which is not nice.
`(org-block-begin-line ((t :foreground ,monokai-comments :background nil)))
`(org-block-end-line ((t :foreground ,monokai-comments :background nil)))
;; I just don't like boxes around checkboxes
`(org-checkbox ((t :background ,monokai-background
:foreground ,monokai-foreground
:box nil)))
;; Light colors on yellow background look terrible. Use something
;; legit instead.
`(org-clock-overlay ((t :background ,monokai-highlight-alt)))
`(org-tag ((t :weight bold :inverse-video t)))
`(org-archived ((t :foreground ,monokai-comments
:weight normal
:inverse-video nil)))
`(org-ellipsis ((t :foreground ,monokai-comments
:background ,monokai-background
:inverse-video nil)))
;; Default company theming makes selected annotations appear on
;; another background than the rest of tooltip. This changes
;; tooltip selection background, so both tooltips and annotations
;; are legible.
`(company-tooltip-selection ((t :foreground ,monokai-background
:background ,monokai-emphasis
:weight bold)))
`(company-tooltip-common-selection ((t :foreground ,monokai-background
:background ,monokai-emphasis
:weight bold
:underline t)))
`(company-tooltip-annotation-selection ((t :foreground ,monokai-green-d
:background ,monokai-emphasis
:weight bold
:inherit company-tooltip-annotation)))
;; spaceline theming
`(spaceline-evil-normal ((t :foreground "#3E3D31" :weight bold :background ,monokai-green :inherit 'mode-line)))
`(spaceline-evil-insert ((t :foreground "#3E3D31" :weight bold :background ,monokai-blue :inherit 'mode-line)))
`(spaceline-evil-emacs ((t :foreground "#3E3D31" :weight bold :background ,monokai-violet :inherit 'mode-line)))
`(spaceline-evil-replace ((t :foreground "#3E3D31" :weight bold :background ,monokai-red :inherit 'mode-line)))
`(spaceline-evil-visual ((t :foreground "#3E3D31" :weight bold :background ,monokai-yellow :inherit 'mode-line)))
`(spaceline-evil-motion ((t :foreground "#3E3D31" :weight bold :background ,monokai-orange :inherit 'mode-line)))
;; show lsp-ui on different background than normal code, so they
;; are distinguishable.
`(lsp-ui-sideline-global ((t :background ,monokai-highlight-alt))))))
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 "Terminess Powerline")
(set-face-attribute 'fixed-pitch nil :family "Terminess Powerline" :height 160)
(set-face-attribute 'default nil :family "Terminess Powerline" :height 160))
((rasen/font-exists-p "Terminus")
(set-face-attribute 'fixed-pitch nil :family "Terminus" :height 160)
(set-face-attribute 'default nil :family "Terminus" :height 160))))
(rasen/set-my-fonts)
Apply my font setting when new frame is created (useful when emacs is started in daemon mode).
(defun rasen/font-hook (frame)
(select-frame frame)
(rasen/set-my-fonts))
(add-hook 'after-make-frame-functions 'rasen/font-hook)
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 ()
(setq-local fill-column 81)
(visual-line-mode t)
(visual-fill-column-mode t))
:config
(setq-default visual-fill-column-center-text t
visual-fill-column-fringes-outside-margins nil))
Add a little bit of highlighting for the cursor, when buffer scrolls, so I don’t lose it.
(use-package beacon
:diminish beacon-mode
:config
(beacon-mode 1))
Add project name to the title, so I can later analyze my app usage.
(setq-default frame-title-format
'("[%m] " (:eval (projectile-project-name))))