This repository contains my Emacs configuration. It is written and documented in literate programming style.
If you’re new to Emacs and just want to have a look around: Lean back and relax while enjoying a deep dive into the wonderful world of the Emacs editor. I have a talk “Play Emacs like an instrument” which is a small teaser of what Emacs can do - and what kinds of features you’ll find in this repository: https://www.youtube.com/watch?v=gfZDwYeBlO4
Initial
Emacs configuration is usually done in the home directory in the
.emacs.d
folder. This holds true for Unix and Linux systems. For
Windows, look it up here.
git clone git@github.com:munen/emacs.d.git ~/.emacs.d
Dependencies
Emacs dependencies/libraries are managed via the internal package
management system. To initially install packages, open
~/.emacs.d/init.el
, refresh your package list with M-x
package-refresh-contents
and install everything using M-x
eval-buffer
.
(require 'package)
(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("marmalade" . "https://marmalade-repo.org/packages/")
("melpa" . "https://melpa.org/packages/")))
List all used third-party packages. Most will be configured further down in this file, some are used with the default configuration.
(defvar my-packages '(ac-cider
ac-js2
ag
atomic-chrome
auto-complete
beacon
browse-kill-ring
cider
clj-refactor
clojure-mode
coffee-mode
comment-tags
darktooth-theme
dired-narrow
diminish
dumb-jump
edit-indirect
editorconfig
elfeed
elfeed-goodies
enh-ruby-mode
erc-image
evil
evil-escape
evil-leader
evil-mc
evil-numbers
evil-surround
exec-path-from-shell
forge
flycheck
flycheck-flow
hide-mode-line
ido-vertical-mode
impatient-mode
ini-mode
ivy counsel swiper
json-mode
js2-mode
js2-refactor
js-comint
ledger-mode
magit
markdown-mode
parinfer
pdf-tools
projectile
rainbow-mode
ob-restclient
restclient
robe
sass-mode
spacemacs-theme
spaceline
smex
synosaurus
tide
visual-fill-column
web-mode
which-key
writegood-mode
writeroom-mode
yaml-mode
zenburn-theme))
(dolist (p my-packages)
(unless (package-installed-p p)
(package-refresh-contents)
(package-install p))
(add-to-list 'package-selected-packages p))
This section contains settings for built-in Emacs features.
Allow 20MB of memory (instead of 0.76MB) before calling garbage collection. This means GC runs less often, which speeds up some operations.
(setq gc-cons-threshold 20000000)
(setq make-backup-files nil)
Store backups and auto-saved files in TEMPORARY-FILE-DIRECTORY
(which
defaults to /tmp on Unix), instead of in the same directory as the
file.
(setq backup-directory-alist
`((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
`((".*" ,temporary-file-directory t)))
When opening a file, always follow symlinks.
(setq vc-follow-symlinks t)
Don’t assume that sentences should have two spaces after periods.
(setq sentence-end-double-space nil)
(setq confirm-kill-emacs 'y-or-n-p)
Ability to use a
to visit a new directory or file in dired
instead
of using RET
. RET
works just fine, but it will create a new buffer
for every interaction whereas a
reuses the current buffer.
(put 'dired-find-alternate-file 'disabled nil)
Human readable units
(setq-default dired-listing-switches "-alh")
dired-narrow
of the dired-hacks repository allows to dynamically
narrow a dired buffer down to contents of interest. A demo can be seen
on this blog post.
(require 'dired)
(define-key dired-mode-map (kbd "/") 'dired-narrow-fuzzy)
Commands:
/
starts fuzzy matching- Use the dired buffer as usual
g
to go back to the complete file listing
This is a favorable shorthand.
(fset 'yes-or-no-p 'y-or-n-p)
When something changes a file, automatically refresh the buffer containing that file so they can’t get out of sync.
(global-auto-revert-mode t)
(defun zoom-in ()
(interactive)
(let ((x (+ (face-attribute 'default :height)
10)))
(set-face-attribute 'default nil :height x)))
(defun zoom-out ()
(interactive)
(let ((x (- (face-attribute 'default :height)
10)))
(set-face-attribute 'default nil :height x)))
(define-key global-map (kbd "C-1") 'zoom-in)
(define-key global-map (kbd "C-0") 'zoom-out)
(setq inhibit-splash-screen t)
(setq inhibit-startup-message t)
(display-time-mode t)
(tool-bar-mode 0)
Do not enable automatic line breaks for all text-mode based hooks,
because several text-modes (markdown, mails) enjoy the pain of long
lines. So here, I only add whitelisted modes sparingly. The other
modes have a visual-clean
configuration which makes the text look
nice locally, at least.
(add-hook 'org-mode-hook 'auto-fill-mode)
Enable narrow-to-region (C-x n n / C-x n w). This is disabled by default to not confuse beginners.
(put 'narrow-to-region 'disabled nil)
(scroll-bar-mode -1)
(setq save-place-file "~/.emacs.d/saveplace")
(setq-default save-place t)
(require 'saveplace)
Set $MANPATH, $PATH and exec-path from shell even when started from GUI helpers like dmenu
or Spotlight
(exec-path-from-shell-initialize)
Windmove is built into Emacs. It lets you move point from window to window using Shift and the arrow keys. This is easier to type than ‘C-x o’ when there are multiple windows open.
(when (fboundp 'windmove-default-keybindings)
(windmove-default-keybindings))
Allows to ‘undo’ (and ‘redo’) changes in the window configuration with the key commands ‘C-c left’ and ‘C-c right’.
(when (fboundp 'winner-mode)
(winner-mode 1))
Getting from many windows to one window is easy: ‘C-x 1’ will do it. But getting back to a delicate WindowConfiguration is difficult. This is where Winner Mode comes in: With it, going back to a previous session is easy.
Do not ring the system bell, but show a visible feedback.
(setq visible-bell t)
Try to use passive mode for FTP.
Note: Some firewalls might not allow standard active mode. However: Some FTP Servers might not allow passive mode. So if there’s problems when connecting to an FTP, try to revert to active mode.
(setq ange-ftp-try-passive-mode t)
When entering eww, use cursors to scroll without changing point.
(add-hook 'eww-mode-hook 'scroll-lock-mode)
(setq custom-file "~/.emacs.d/custom-settings.el")
(load custom-file t)
I’m running Debian and for some things I use GNU Guix for package
management. For example mu4e is installed through guix, so that I can
always have a recent version. This adds the installed packages to the
standard Emacs load path, so that require
just works.
(add-to-list 'load-path "/home/munen/.guix-profile/share/emacs/site-lisp/")
Some helper functions and packages I wrote that are only accessible within this Git repository and not published to a package repository.
Elisp wrapper around the dict.cc translation service. Translations are exposed in an org-mode table.
(load "~/.emacs.d/dict")
When switching projects in Emacs, it can be prudent to clean up every
once in a while. Deleting all buffers except the current one is one of
the things I often do (especially in the long-running emacsclient
).
(defun kill-other-buffers ()
"Kill all other buffers."
(interactive)
(mapc 'kill-buffer (delq (current-buffer) (buffer-list))))
dired
will create buffers for every visited folder. This is a helper
to clear them out once you’re done working with those folders.
(defun kill-dired-buffers ()
"Kill all open dired buffers."
(interactive)
(mapc (lambda (buffer)
(when (eq 'dired-mode (buffer-local-value 'major-mode buffer))
(kill-buffer buffer)))
(buffer-list)))
Rudimentary function converting certain HTML syntax to HTML entities.
(defun encode-html (start end)
"Encodes HTML entities; works great in Visual Mode (START END)."
(interactive "r")
(save-excursion
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
(replace-string "&" "&")
(goto-char (point-min))
(replace-string "<" "<")
(goto-char (point-min))
(replace-string ">" ">"))))
When working on markdown or org-mode files that will be converted to
PDF, I use pdf-tools
to preview the PDF and shortcuts to
automatically save, compile and reload on demand.
Here is a screencast showing how I edit Markdown or org-mode files in Emacs whilst having a PDF preview.
In a screenshot, it looks like this:
(defun md-compile ()
"Compiles the currently loaded markdown file using pandoc into a PDF"
(interactive)
(save-buffer)
(shell-command (concat "pandoc " (buffer-file-name) " -o "
(replace-regexp-in-string "md" "pdf" (buffer-file-name)))))
(defun update-other-buffer ()
(interactive)
(other-window 1)
(revert-buffer nil t)
(other-window -1))
(defun md-compile-and-update-other-buffer ()
"Has as a premise that it's run from a markdown-mode buffer and the
other buffer already has the PDF open"
(interactive)
(md-compile)
(update-other-buffer))
(defun latex-compile-and-update-other-buffer ()
"Has as a premise that it's run from a latex-mode buffer and the
other buffer already has the PDF open"
(interactive)
(save-buffer)
(shell-command (concat "pdflatex " (buffer-file-name)))
(switch-to-buffer (other-buffer))
(kill-buffer)
(update-other-buffer))
(defun org-compile-beamer-and-update-other-buffer ()
"Has as a premise that it's run from an org-mode buffer and the
other buffer already has the PDF open"
(interactive)
(org-beamer-export-to-pdf)
(update-other-buffer))
(defun org-compile-latex-and-update-other-buffer ()
"Has as a premise that it's run from an org-mode buffer and the
other buffer already has the PDF open"
(interactive)
(org-latex-export-to-pdf)
(update-other-buffer))
(eval-after-load 'latex-mode
'(define-key latex-mode-map (kbd "C-c r") 'latex-compile-and-update-other-buffer))
(define-key org-mode-map (kbd "C-c lr") 'org-compile-latex-and-update-other-buffer)
(define-key org-mode-map (kbd "C-c br") 'org-compile-beamer-and-update-other-buffer)
(eval-after-load 'markdown-mode
'(define-key markdown-mode-map (kbd "C-c r") 'md-compile-and-update-other-buffer))
Unrelated to Emacs, in macOS, you can write Umlauts by using the combo
M-u [KEY]
. For example M-u u
will create the letter ü
.
This is actually faster than the default way of Emacs or that of VIM. The following code ports that functionality to Emacs.
Thx @jcfischer for the function!
(define-key key-translation-map [dead-diaeresis]
(lookup-key key-translation-map "\C-x8\""))
(define-key isearch-mode-map [dead-diaeresis] nil)
(global-set-key (kbd "M-u")
(lookup-key key-translation-map "\C-x8\""))
(defun visual-clean ()
"Clean up messy buffers (i.e. web wikis or elfeed-show)"
(interactive)
(visual-line-mode)
(visual-fill-column-mode))
Use this automatically for writeroom-mode
:
(advice-add 'writeroom-mode :after
'visual-clean)
Through pwgen
.
Thanks to @branch14 of 200ok fame for the function!
(defun generate-password-non-interactive ()
(string-trim (shell-command-to-string "pwgen -A 24")))
(defun generate-password ()
"Generates and inserts a new password"
(interactive)
(insert
(shell-command-to-string
(concat "pwgen -A " (read-string "Length: " "24") " 1"))))
Open the GPG encrypted password file.
Within this file, I’ll search for passwords with counsel-imenu
which
has nice auto-completion and means that the headers will always be
folded, so that no other person can see the passwords.
When the right header is found, I’ll copy the password under the current header to the clipboard from where I can use it where I need it (for example a browser):
(fset 'copy-password-to-clipboard
[?\C-s ?P ?a ?s ?s ?w ?o ?r ?d ?: return ?w ?v ?$ ?y C-up C-up C-up tab])
(defun passwords ()
"Open main 'passwords' file."
(interactive)
(find-file (concat org-directory "vault/primary.org.gpg")))
If you’re a zsh
user, you might have configured a custom prompt
and such. Also, you might be using a powerful $TERM
for that.
When running zsh
within M-x shell
, you will have to set the
$TERM
to dumb
, though. Otherwise you’ll get all kinds of escape
sequences instead of colored text.
I’m using this within my ~/.zshrc
# This allows running `shell` properly within Emacs
if [ -n "$INSIDE_EMACS" ]; then
export TERM=dumb
else
export TERM=xterm-256color
fi
This is the converse function to the built-in server-start
.
(defun server-shutdown ()
"Save buffers, Quit, and Shutdown (kill) server"
(interactive)
(save-some-buffers)
(kill-emacs))
(defmacro measure-time (&rest body)
"Measure the time it takes to evaluate BODY."
`(let ((time (current-time)))
,@body
(message "%.06f" (float-time (time-since time)))))
For example (measure-time (prettier-eslint)
.
If the current buffer is not writable, ask if it should be saved with
sudo
.
Happily taken from Pascals configuration: https://github.com/SirPscl/emacs.d#sudo-save
(defun ph/sudo-file-name (filename)
"Prepend '/sudo:root@`system-name`:' to FILENAME if appropriate.
This is, when it doesn't already have a sudo-prefix."
(if (not (or (string-prefix-p "/sudo:root@localhost:"
filename)
(string-prefix-p (format "/sudo:root@%s:" system-name)
filename)))
(format "/sudo:root@%s:%s" system-name filename)
filename))
(defun ph/sudo-save-buffer ()
"Save FILENAME with sudo if the user approves."
(interactive)
(when buffer-file-name
(let ((file (ph/sudo-file-name buffer-file-name)))
(if (yes-or-no-p (format "Save file as %s ? " file))
(write-file file)))))
(advice-add 'save-buffer :around
'(lambda (fn &rest args)
(when (or (not (buffer-file-name))
(not (buffer-modified-p))
(file-writable-p (buffer-file-name))
(not (ph/sudo-save-buffer)))
(call-interactively fn args))))
This section contains settings for non-built-in Emacs features that are generally applicable to different kinds of modes.
https://github.com/Malabarba/beacon
Whenever the window scrolls a light will shine on top of your cursor so you know where it is.
(beacon-mode 1)
Ever wish you could just look through everything you’ve killed recently to find out if you killed that piece of text that you think you killed (or yanked), but you’re not quite sure? If so, then browse-kill-ring is the Emacs extension for you.
(require 'browse-kill-ring)
(setq browse-kill-ring-highlight-inserted-item t
browse-kill-ring-highlight-current-entry nil
browse-kill-ring-show-preview t)
(define-key browse-kill-ring-mode-map (kbd "j") 'browse-kill-ring-forward)
(define-key browse-kill-ring-mode-map (kbd "k") 'browse-kill-ring-previous)
Evil is an extensible Vim layer for Emacs.
This combines the best of both worlds: VIM being a great text-editor with modal editing through semantic commands and Emacs being a LISP REPL.
(evil-mode t)
;; Enable "M-x" in evil mode
(global-set-key (kbd "M-x") 'execute-extended-command)
(global-evil-leader-mode)
(evil-leader/set-leader ",")
(evil-leader/set-key
"w" 'basic-save-buffer
"s" 'flyspell-buffer
"b" 'evil-buffer
"q" 'evil-quit)
(require 'evil-surround)
(global-evil-surround-mode 1)
https://github.com/gabesoft/evil-mc
evil-mc
provides multiple cursors functionality for Emacs when used
with evil-mode
.
C-n / C-p
are used for creating cursors, and M-n / M-p
are used
for cycling through cursors. The commands that create cursors wrap
around; but, the ones that cycle them do not. To skip creating a
cursor forward use C-t
or grn
and backward grp
. Finally use
gru
to remove all cursors.
(global-evil-mc-mode 1)
(define-key evil-normal-state-map (kbd "{") 'evil-next-buffer)
(define-key evil-normal-state-map (kbd "}") 'evil-prev-buffer)
(global-set-key (kbd "C-=") 'evil-numbers/inc-at-pt)
(global-set-key (kbd "C--") 'evil-numbers/dec-at-pt)
(define-key evil-normal-state-map (kbd "C-=") 'evil-numbers/inc-at-pt)
(define-key evil-normal-state-map (kbd "C--") 'evil-numbers/dec-at-pt)
(define-key evil-normal-state-map (kbd "j") 'evil-next-visual-line)
(define-key evil-normal-state-map (kbd "k") 'evil-previous-visual-line)
(define-key evil-insert-state-map (kbd "C-v") 'evil-visual-paste)
Since Emacs is a multi-purpose LISP REPL, there are many modes that
are not primarily (or not at all) centered about text-manipulation.
For those, it is reasonable to disable evil-mode
, because it will
bring nothing to the table, but might just shadow some keyboard
shortcuts.
(mapc (lambda (mode)
(evil-set-initial-state mode 'emacs)) '(elfeed-show-mode
elfeed-search-mode
forge-pullreq-list-mode
forge-topic-list-mode
dired-mode
tide-references-mode
image-dired-mode
image-dired-thumbnail-mode
eww-mode))
M-.
and M-,= are popular keybindings for "jump to definition" and
"back". =evil-mode
by default binds those to rather rarely used
functions evil-repeat-pop-next
and xref-pop-marker-stack
, for some reason.
(define-key evil-normal-state-map (kbd "M-.") nil)
(define-key evil-normal-state-map (kbd "M-,") nil)
https://github.com/syl20bnr/evil-escape
Escape from insert state and everything else.
(setq-default evil-escape-delay 0.2)
(setq-default evil-escape-key-sequence "jk")
(evil-escape-mode)
This results in the same feature-set like this vim keybinding:
"Remap ESC to jk
:imap jk <esc>
which-key
displays available keybindings in a popup.
(add-hook 'org-mode-hook 'which-key-mode)
(add-hook 'cider-mode-hook 'which-key-mode)
Use which-key
to show VIM shortcuts, too.
(setq which-key-allow-evil-operators t)
(setq which-key-show-operator-state-maps t)
https://github.com/auto-complete/auto-complete
Basic Configuration
(ac-config-default)
Set tab width to 2 for all buffers
(setq-default tab-width 2)
Use 2 spaces instead of a tab.
(setq-default tab-width 2 indent-tabs-mode nil)
Indentation cannot insert tabs.
(setq-default indent-tabs-mode nil)
Use 2 spaces instead of tabs for programming languages.
(setq js-indent-level 2)
(setq coffee-tab-width 2)
(setq python-indent 2)
(setq css-indent-offset 2)
(add-hook 'sh-mode-hook
(lambda ()
(setq sh-basic-offset 2
sh-indentation 2)))
(setq web-mode-markup-indent-offset 2)
Enable global on the fly syntax checking through flycheck
.
(add-hook 'after-init-hook #'global-flycheck-mode)
https://github.com/vincekd/comment-tags
comment-tags
highlights and lists comment tags such as ‘TODO’, ‘FIXME’, ‘XXX’.
Commands (prefixed by C-c t
):
b
to list tags in current buffer (comment-tags-list-tags-buffer).a
to list tags in all buffers (comment-tags-list-tags-buffers).s
to jump to tag in current buffer by a word or phrase using reading-completion (comment-tags-find-tags-buffer).n
to jump to next tag from point (comment-tags-next-tag).p
to jump to previous tag from point (comment-tags-previous-tag).
(setq comment-tags-keymap-prefix (kbd "C-c t"))
(with-eval-after-load "comment-tags"
(setq comment-tags-keyword-faces
`(("TODO" . ,(list :weight 'bold :foreground "#DF5427"))
("FIXME" . ,(list :weight 'bold :foreground "#DF5427"))
("BUG" . ,(list :weight 'bold :foreground "#DF5427"))
("HACK" . ,(list :weight 'bold :foreground "#DF5427"))
("KLUDGE" . ,(list :weight 'bold :foreground "#DF5427"))
("XXX" . ,(list :weight 'bold :foreground "#DF5427"))
("INFO" . ,(list :weight 'bold :foreground "#1FDA9A"))
("DONE" . ,(list :weight 'bold :foreground "#1FDA9A"))))
(setq comment-tags-comment-start-only t
comment-tags-require-colon t
comment-tags-case-sensitive t
comment-tags-show-faces t
comment-tags-lighter nil))
(add-hook 'prog-mode-hook 'comment-tags-mode)
(define-key global-map (kbd "RET") 'newline-and-indent)
(show-paren-mode t)
Delete trailing whitespace in all modes. Except when editing Markdown, because it uses two trailing blanks as a signal to create a line break.
(add-hook 'before-save-hook '(lambda()
(when (not (or (derived-mode-p 'markdown-mode)))
(delete-trailing-whitespace))))
Enable code folding for programming modes.
zc
: Foldza
: UnfoldzR
: Unfold everything
(add-hook 'prog-mode-hook #'hs-minor-mode)
For syntax checking to work, installing the command-line linter tools ruby-lint and eslint are a premise:
gem install rubocop ruby-lint
npm install -g eslint
(setq ruby-indent-level 2)
(add-to-list 'auto-mode-alist '("\\.scss?\\'" . scss-mode))
;; Don't compile scss on save
(setq scss-compile-at-save nil)
(add-to-list 'auto-mode-alist '("\\.rb?\\'" . enh-ruby-mode))
(add-to-list 'auto-mode-alist '("\\.rake?\\'" . enh-ruby-mode))
(add-hook 'enh-ruby-mode-hook 'linum-mode)
https://github.com/dgutov/robe
Code navigation, documentation lookup and completion for Ruby
(add-hook 'enh-ruby-mode-hook 'robe-mode)
(add-hook 'robe-mode-hook 'ac-robe-setup)
(add-to-list 'auto-mode-alist '("\\.erb?\\'" . robe-mode))
Start robe-mode
with M-x robe-start
.
Shortcuts:
C-c C-d
Lookup documentationM-.
Jump to definitionTAB
Auto-completion throughauto-complete-mode
(add-hook 'enh-ruby-mode-hook 'auto-complete-mode)
https://github.com/clojure-emacs/cider
Cider is short for The “Clojure Interactive Development Environment that Rocks for Emacs”. For good reasons, it is the most popular IDE for developing Clojure.
M-x cider-jack-in
To start REPLC-c C-k
Evaluate current bufferC-c M-n
Change ns in cider-nrepl to current nsC-c C-d C-d
Display documentation for the symbol under pointC-c C-d C-a
Apropos search for arbitrary text across function names and documentation
C-↑, C-↓
Cycle through REPL history.- More Cider shortcuts here.
Create a ~/.lein/profiles.clj
file with:
{:user {:plugins [[cider/cider-nrepl "0.13.0-SNAPSHOT"]
[refactor-nrepl "2.2.0"]]
:dependencies [[org.clojure/tools.nrepl "0.2.12"]]}}
Setup Cider with auto-complete
.
(require 'ac-cider)
;;(setq ac-quick-help-delay 0.5)
(add-hook 'cider-mode-hook 'ac-flyspell-workaround)
(add-hook 'cider-mode-hook 'ac-cider-setup)
(add-hook 'cider-repl-mode-hook 'ac-cider-setup)
(eval-after-load "auto-complete"
'(progn
(add-to-list 'ac-modes 'cider-mode)
(add-to-list 'ac-modes 'cider-repl-mode)))
When connecting to a repl, don’t pop to the new repl buffer.
(setq cider-repl-pop-to-buffer-on-connect nil)
https://github.com/clojure-emacs/clj-refactor.el/
A collection of Clojure refactoring functions for Emacs.
(require 'clj-refactor)
(add-hook 'clojure-mode-hook
(lambda ()
(clj-refactor-mode 1)
(setq cljr-warn-on-eval nil)
(yas-minor-mode 1) ; for adding require/use/import statements
;; This choice of keybinding leaves cider-macroexpand-1 unbound
(cljr-add-keybindings-with-prefix "C-c C-m")))
clj-refactor
enables refactorings like extracting functions (C-c
C-m ef
). Find the list of available refactorings here.
Integrant configures, starts and manages a system
and exposes a
lifecycle for it.
For REPL-driven development this adds one layer of indirection: When
starting a service through lein run
(or bundled in a Docker
container), the system
will already be started by Integrant. Without
having a ref to this system
, we cannot stop it, we can only start
new systems. This means that reloading the code will only start new
systems, but not be able to halt the old one. The internal code from
Integrant relies on spawning a thread after initializing a system
through lein run
and will not return until the process is done.
Therefore we cannot retrieve the system when running lein run
.
When Emacs has a connection to a REPL for an Integrant based application, this snippet actually enables reloading of front and back-ends. The code doesn’t use cider internal functions for interacting with the REPL, because not all buffers might be connected (for example the CLJS buffers might not have a dedicated REPL themselves). Instead, it uses common Elisp.
(defun ok-cider-reload-integrant ()
(interactive)
(require 'seq)
(save-buffer)
(let ((cider-buffer (first (seq-filter '(lambda (buf)
(string-match "cider-repl" buf))
(mapcar 'buffer-name (buffer-list))))))
(if cider-buffer
(progn
(switch-to-buffer cider-buffer)
(insert "(in-ns 'dev)(integrant.repl/reset)")
(cider-repl-return)
(switch-to-buffer (other-buffer)))
(message "No Cider buffer!"))))
(define-key
clojure-mode-map
(kbd "C-c r")
'ok-cider-reload-integrant)
Usage
When you want to reload the system
, use C-c r
. It will save your
current buffer and reload the system
.
https://github.com/ananthakumaran/tide
Claim: TypeScript Interactive Development Environment for Emacs.
However, also JavaScript development gets big improvements with
tide-mode
.
Tide is an alternative to Tern which also has great Emacs integration and which I have happily been using for years. However, tide works even better (in my experience).
For completion to work in a Node.js project, a jsconfig.json
file
like this is required:
{
"compilerOptions": {
"target": "es6"
},
"exclude": [
"node_modules"
]
}
If no project file is found, it’ll fall back to an inferred configuration.
Tide default shortcuts:
M-.
Jump to the definition of the thing under the cursor.- =M-,= Brings you back to last place you were when you pressed M-..
(require 'js2-mode)
(define-key js2-mode-map (kbd "C-c C-r") 'tide-rename-symbol)
(define-key js2-mode-map (kbd "C-c C-d") 'tide-documentation-at-point)
(defun setup-tide-mode ()
(interactive)
;; For bigger JS projects and intense tasks like =tide=references=
;; the default of 2s will time out
(setq tide-sync-request-timeout 10)
(tide-setup)
;; Increase sync request timeout for bigger projects
(flycheck-mode +1)
(setq flycheck-check-syntax-automatically '(save mode-enabled))
(eldoc-mode +1)
(tide-hl-identifier-mode +1))
(add-hook 'js2-mode-hook #'setup-tide-mode)
https://github.com/redguardtoo/js-comint
Run a JavaScript interpreter in an inferior process window.
(require 'js-comint)
(add-hook 'js2-mode-hook
(lambda ()
(local-set-key (kbd "C-x C-e") 'js-send-last-sexp)
(local-set-key (kbd "C-M-x") 'js-send-last-sexp-and-go)
(local-set-key (kbd "C-c b") 'js-send-buffer)
(local-set-key (kbd "C-c C-b") 'js-send-buffer-and-go)
(local-set-key (kbd "C-c l") 'js-load-file-and-go)))
https://github.com/flowtype/flow-for-emacs/
An emacs plugin for Flow, a static typechecker for JavaScript.
- Shows errors found by typechecking JavaScript code with Flow.
- Provides a bunch of common IDE features powered by Flow to aid reading and writing JavaScript code.
(load-file "~/.emacs.d/flow-for-emacs/flow.el")
Flow is a static type checker for JavaScript.
Flow uses type inference to find bugs even without type annotations. It precisely tracks the types of variables as they flow through your program.
Flow is designed for JavaScript programmers. It understands common JavaScript idioms and very dynamic code.
Flow incrementally rechecks your changes as you work, preserving the fast feedback cycle of developing plain JavaScript.
(require 'flycheck-flow)
(add-hook 'javascript-mode-hook 'flycheck-mode)
(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
(add-hook 'js-mode-hook 'js2-minor-mode)
(setq js2-highlight-level 3)
(setq js-indent-level 2)
;; Semicolons are optional in JS, do not warn about them missing
(setq js2-strict-missing-semi-warning nil)
rainbow-mode
is a minor mode for Emacs which displays strings
representing colors with the color they represent as background.
(add-hook 'prog-mode-hook 'rainbow-mode)
https://github.com/netguy204/imp.el
Live JavaScript Coding Emacs/Browser: See your changes in the browser as you type
Enable the web server provided by simple-httpd: M-x httpd-start
Publish buffers by enabling the minor mode impatient-mode: M-x impatient-mode
And then point your browser to http://localhost:8080/imp/, select a buffer, and watch your changes appear as you type!
jq is a lightweight and flexible command-line JSON processor.
Thanks to @branch14 of 200ok fame for the function!
(defun jq-json ()
(interactive)
(save-excursion
(shell-command-on-region
(point-min)
(point-max)
(read-string "Command: " "jq -M '.'") t t)))
web-mode.el is an autonomous major-mode for editing web templates.
(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
;; Ruby Templates
(add-to-list 'auto-mode-alist '("\\.erb?\\'" . web-mode))
;; Handlebars
(add-to-list 'auto-mode-alist '("\\.hbs?\\'" . web-mode))
;; JSON
(add-to-list 'auto-mode-alist '("\\.json?\\'" . web-mode))
(setq web-mode-enable-current-element-highlight t)
(setq web-mode-ac-sources-alist
'(("html" . (ac-source-words-in-buffer ac-source-abbrev))))
p_slides is a static files only, dead simple way, to create semantic
slides. The slide content is markdown, embedded in a HTML file. When
opening a presentation.html
file, enable markdown-mode
.
(add-to-list 'auto-mode-alist '("presentation.html" . markdown-mode))
Introducing a custom browser-reloading-mode
. It’s a quick
implementation and not a real derived mode.
When enabling browser-reloading-mode
for a specific buffer, whenever
this buffer is saved, a command-line utility reload_chromium.sh
is
called. This in turn is a wrapper around xdotool
with which a
reloading of the Chromium browser is triggered.
This is handy when working in a web environment that doesn’t natively
support hot-reloading (static web pages, for instance) and the page
has too much (dynamic) content to be displayed properly in
impatient-mode
. I’m using it for example when working on a p_slides
slide deck.
(defun reload-chromium ()
(when enable-browser-reloading
(shell-command-to-string "reload_chromium.sh")))
(defun browser-reloading-mode ()
"Finds the open chromium session and reloads the tab"
(interactive)
;; When set, disable the local binding and therefore disable the mode
(if enable-browser-reloading
(setq enable-browser-reloading nil)
;; Otherwise create a local var and set it to True
(progn
(make-local-variable 'enable-browser-reloading)
(setq enable-browser-reloading t))))
;; By default, disable the guard against using `reload-chromium`
(setq enable-browser-reloading nil)
(add-hook 'after-save-hook #'reload-chromium)
(require 'yaml-mode)
(add-to-list 'auto-mode-alist '("\\.yml$" . yaml-mode))
(add-hook 'markdown-mode-hook 'flyspell-mode)
Unfortunately line breaks are semantic in some versions of markdown
(for example Github). So doing automatic line breaks would be harmful.
However, this leads to super long lines in many documents which is
unreadable. Therefore, always use visual-clean
.
(add-hook 'markdown-mode-hook 'visual-clean)
<<magit>>
https://github.com/magit/magit
Magit is an interface to the version control system Git.
Create shortcut for Magit
.
(global-set-key (kbd "C-x g") 'magit-status)
Always sign commits with GPG
(setq magit-commit-arguments (quote ("--gpg-sign=137099B38E1FC0E9")))
(add-hook 'with-editor-mode-hook 'evil-normal-state)
https://github.com/magit/forge/
Work with Git forges from the comfort of Magit.
(with-eval-after-load 'magit
(require 'forge))
Add 200ok gitlab instance to list of known forges
(with-eval-after-load 'forge
(add-to-list 'forge-alist
'("gitlab.200ok.ch"
"gitlab.200ok.ch/api/v4"
"gitlab.200ok.ch"
forge-gitlab-repository)))
https://github.com/bbatsov/projectile
Projectile is a project interaction library. For instance - finding
project files (C-c p f
) or jumping to a new project (C-c p p
).
Enable Projectile globally
(projectile-mode +1)
(define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
https://github.com/jacktasia/dumb-jump
“Jump to definition” with support for multiple programming languages that favors “just working”. This means minimal – and ideally zero – configuration with absolutely no stored indexes (TAGS) or persistent background processes.
Dumb Jump uses The Silver Searcher ag, ripgrep rg, or grep to find potential definitions of a function or variable under point. It uses a set of regular expressions based on the file extension, or major-mode, of the current buffer.
(dumb-jump-mode)
(setq dumb-jump-selector 'ivy)
The one important shortcut is C-M-g
which attempts to jump to the
definition of the thing under point.
Automatically format code for different languages and frameworks.
This implements the interactive function autoformat
which is a thin
wrapper around command-line based code autoformatters which it
utilizes through a strategy pattern.
To add a new language/framework, the only required change is to add
the respective command-line tool configuration into a separate
strategy function. It is trivial to do if the new language/framework
has a command-line tool which takes code into stdin
and formats it
to stdout
.
This setup is completely local and doesn’t impose dependencies to other team members.
This requires prettier
, @prettier/plugin-ruby
and
prettier-eslint-cli
to be installed:
npm install -g prettier-eslint-cli prettier @prettier/plugin-ruby=
(defun autoformat ()
"Automatically format current buffer."
(interactive)
(let ((eslint-path (concat (projectile-project-root)
".eslintrc.json")))
(autoformat-with
(cond ((derived-mode-p 'web-mode) 'autoformat-html-command)
((derived-mode-p 'css-mode) 'autoformat-css-command)
((derived-mode-p 'json-mode) 'autoformat-json-command)
((derived-mode-p 'sass-mode) 'autoformat-sass-command)
((derived-mode-p 'yaml-mode) 'autoformat-yaml-command)
((derived-mode-p 'enh-ruby-mode) 'autoformat-ruby-command)
;; JS projects with eslint config
((and (file-exists-p eslint-path)
(derived-mode-p 'js2-mode))
'autoformat-prettier-eslint-command)
((derived-mode-p 'js2-mode) 'autoformat-javascript-command)))))
(defun autoformat-with (strategy)
"Automatically format current buffer using STRATEGY."
(let ((p (point))
(s (window-start)))
;; Remember the current position
(save-mark-and-excursion
;; Call prettier-eslint binary with the contents of the current
;; buffer
(shell-command-on-region
(point-min) (point-max)
(funcall strategy)
;; Write into a temporary buffer
(get-buffer-create "*Temp autoformat buffer*")
;; Replace the current buffer with the output of
;; the =autoformat strategy= output
t
;; If the =autoformat strategy= returns an error, show it in a
;; separate error buffer
(get-buffer-create "*replace-errors*")
;; Automatically show error buffer
t))
;; Return to the previous point and scrolling position (the point
;; was lost, because the whole buffer got replaced.
(set-window-start (selected-window) s)
(goto-char p)))
(defun autoformat-ruby-command ()
"CLI tool to format Ruby."
"prettier --stdin --parser ruby")
(defun autoformat-javascript-command ()
"CLI tool to format Javascript."
"prettier --stdin --parser babel")
(defun autoformat-html-command ()
"CLI tool to format HTML."
"prettier --stdin --parser html")
(defun autoformat-css-command ()
"CLI tool to format CSS."
"prettier --stdin --parser css")
(defun autoformat-sass-command ()
"CLI tool to format SASS."
"prettier --stdin --parser sass")
(defun autoformat-json-command ()
"CLI tool to format JSON."
"prettier --stdin --parser json")
(defun autoformat-yaml-command ()
"CLI tool to format YAML."
"prettier --stdin --parser yaml")
(defun autoformat-prettier-eslint-command ()
"CLI tool to format Javascript with .eslintrc.json configuration."
(concat "prettier-eslint --eslint-config-path "
;; Hand over the path of the current projec
(concat
(projectile-project-root)
".eslintrc.json")
" --parser babel --stdin"))
Shortcut
(setq ok-autoformat-modes (list 'web-mode
'css-mode
'json-mode
'sass-mode
'enh-ruby-mode
'yaml-mode
'js2-mode))
(dolist (mode ok-autoformat-modes)
(evil-leader/set-key-for-mode mode "f" 'autoformat))
Demo
Call the function on every save
The overhead of prettier + eslint is about 1.3s on a maxed out X1 Carbon 6th gen. That’s too long in my book, that’s why I’m not enabling completely automatic formatting but opt for calling it when needed (i.e. I see a linting error). Just using prettier is really fast, however I want to enable the same workflow for all those languages, so I’m not enabling formatting on save for those either.
;; (add-hook 'before-save-hook '(lambda()
;; (when (or
;; (derived-mode-p 'js2-mode)
;; (derived-mode-p 'web-mode)
;; (derived-mode-p 'css-mode)
;; (derived-mode-p 'json-mode)
;; (derived-mode-p 'sass-mode)
;; (derived-mode-p 'yaml-mode))
;; (autoformat))))
EditorConfig helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs. I’m an Emacs guy, however, when in an heterogeneous team, it does make sense to adhere to some commonly shared definitions.
With this plugin, if there is an .editorconfig
in a project, the
settings in this file will trump my personal config.
(editorconfig-mode 1)
Outline-based notes management and organizer. It is an outline-mode for keeping track of everything.
(setq org-directory "~/Dropbox/org/")
Allow ‘a.’, ‘A.’, ‘a)’ and ‘A) as list elements:
(setq org-list-allow-alphabetical t)
(require 'org)
; languages for org-babel support
(org-babel-do-load-languages
'org-babel-load-languages
'(
(shell . t)
(js . t)
(ruby . t)
))
(add-hook 'org-mode-hook 'auto-fill-mode)
(add-hook 'org-mode-hook 'flyspell-mode)
(evil-leader/set-key
"a" 'org-archive-subtree-default)
;; Allow =pdflatex= to use shell-commands
;; This will allow it to use =pygments= as syntax highlighter for exports to PDF
(setq org-latex-pdf-process
'("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
;; Include =minted= package for LaTeX exports
(add-to-list 'org-latex-packages-alist '("" "minted"))
(setq org-latex-listings 'minted)
;; Don’t ask every time when executing a code block.
(setq org-confirm-babel-evaluate nil)
(require 'ox-latex)
(add-to-list 'org-latex-classes
'("scrartcl"
"\\documentclass{scrartcl}"
("\\section{%s}" . "\\section*{%s}")))
(require 'ox-latex)
(add-to-list 'org-latex-classes
'("tuftehandout"
"\\documentclass{tufte-handout}
\\usepackage{color}
\\usepackage{amssymb}
\\usepackage{amsmath}
\\usepackage{gensymb}
\\usepackage{nicefrac}
\\usepackage{units}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
Set up capture templates for:
- Todos which land in
Inbox
- Code Snippets which land in
snippets.org
- Shopping Items which get appended to the Shopping List in
things.org
- Media Entries (watch/read later items) that land in
media.org
Org Capture Templates are explained here, Org Template expansion here.
;; Set org-capture inbox
(setq org-default-notes-file (concat org-directory "inbox.org"))
(define-key global-map "\C-cc" 'org-capture)
(setq things-file (expand-file-name "things.org" org-directory))
(setq media-file (expand-file-name "media.org" org-directory))
(defun get-domainname (address)
"Extract TLD (without country) from ADDRESS.
Example: Return '200ok' from 'alain@200ok.ch'."
(replace-regexp-in-string
"\-" "_"
(nth 0
(split-string (nth 1 (split-string address "@"))
"\\."))))
(defun from-name (fromname fromaddress from)
"Return the first non-empty match for FROMNAME FROMADDRESS and FROM."
(nth 0
(seq-filter '(lambda (s)
(not (string-empty-p s)))
(list fromname fromaddress from))))
(setq org-capture-templates
'(("t" "Todo" entry (file+olp things-file "Inbox" "Tasks")
"* TODO %?\n %U\n %i\n %a")
("m" "Mail" entry (file+olp things-file "Inbox" "Mails")
;; Creates "* TODO <2019-05-01 Wed> FromName [[mu4e:msgid:uuid][MessageSubject]] :200ok:
;; Therefore Emails can be properly:
;; - Used as tasks
;; - Attributed tags
;; - Ordered by priority
;; - Scheduled
;; - etc
"* TODO %(org-insert-time-stamp (org-read-date nil t \"%:date\")) %(from-name \"%:fromname\" \"%:fromaddress\" \"%:from\") %a \t :%(get-domainname \"%:toaddress\"):")
("z" "Deep focus (Zen to Done [daily])" plain (file+olp things-file "NEXT")
(file "~/.emacs.d/org-templates/deep_focus.org"))
("s" "Code Snippet" entry (file+headline "~/src/200ok/knowledge/README.org" "Snippets")
;; Prompt for tag and language
"* %?\t%^g\n#+BEGIN_SRC %^{language}\n%i\n#+END_SRC")
("p" "password" entry (file+headline "~/Dropbox/org/vault/primary.org.gpg" "Passwords")
;; Prompt for name
"* %^{name}
:PROPERTIES:
:username: %^{username}
:password: %(generate-password-non-interactive)
:url: %^{url}
:END:")
("u" "URL" entry
(file+datetree media-file)
"* %?\nURL: \nEntered on %U\n")))
A leightweight implementation of the Pomodoro Technique is implemented
through customizing orgmode. For every Clock that is started (C-c C-x
C-i
) an automatic Timer is scheduled to 25min. After these 25min are
up, a “Time to take a break!” message is played and a pop-up
notification is shown.
The timer is not automatically stopped on clocking out, because clocking in should still work on new tasks without resetting the Pomodoro.
The timer can manyally be stopped with M-x org-timer-stop
.
A break can be started with M-x pomodoro-break
. A pomodoro can also
manually be started without clocking in via M-x pomodoro-start
.
(load "~/.emacs.d/org-pomodoro")
I use two workflow sets:
- One for TODOs which can either be TODO or DONE
- Another for tasks that I am WAITING for something to happen or which are in PROGRESS
Additionally I sometimes use the keywords PROJECT and AGENDA to denote special bullets that I might tag (schedule/deadline) in the agenda. These keywords give semantics to those bullets.
Note that “|” denotes a semantic state change that is reflected in a different color. Putting the pipe at the end means that all states prior should be shown in the same color.
(setq org-todo-keywords
'((sequence "TODO" "|" "DONE")
(sequence "PROJECT" "AGENDA" "|" "MINUTES")
(sequence "WAITING" "|" "PROGRESS")))
When using a clock table, org will by default sum up the time in perfectly human readable terms like this:
Headline | Time |
---|---|
Total time | 1d 1:03 |
For easy calculations (I don’t want to parse our hours, weeks and what
not), I do prefer that the summation is done only in hours and
minutes. Therefore, I over-wrote the org-time-clocksum-format
function:
(setq org-duration-format 'h:mm)
This will render the same time as above as:
Headline | Time |
---|---|
Total time | 25:03 |
Do not wrap clock entries into a :LOGBOOK: (mainly for backwards
compatability reasons with ok-export-org-timetable
.
(setq org-clock-into-drawer nil)
(defun set-org-agenda-files ()
"Set different org-files to be used in `org-agenda`."
(setq org-agenda-files (list (concat org-directory "things.org")
(concat org-directory "refile-beorg.org")
(concat org-directory "reference.org")
(concat org-directory "media.org")
"~/Dropbox/ZHAW/web3-unterlagen/README.org"
"~/Dropbox/ZHAW/weng-unterlagen/README.org"
"~/src/200ok/swiss-crowdfunder/TODO.org"
"~/src/200ok/200ok-admin/THINGS.org")))
(set-org-agenda-files)
(global-set-key "\C-cl" 'org-store-link)
(defun things ()
"Open main 'org-mode' file and start 'org-agenda' for today."
(interactive)
(find-file (concat org-directory "things.org"))
(set-org-agenda-files)
(org-agenda-list)
(org-agenda-day-view)
(shrink-window-if-larger-than-buffer)
(other-window 1))
For a proficient GTD workflow, it is important to be able to refile
one item from one list easily to another (for example when processing
an inbox). Orgmode makes this easy with the refile command C-c C-w
.
Define where the refiling can happen (the default is to the local buffer):
(setq org-refile-targets (quote ((org-agenda-files :maxlevel . 3))))
(setq calendar-week-start-day 1)
(setq calendar-intermonth-text
'(propertize
(format "%2d"
(car
(calendar-iso-from-absolute
(calendar-absolute-from-gregorian (list month day year)))))
'font-lock-face 'font-lock-warning-face))
(setq calendar-intermonth-header
(propertize "CW"
'font-lock-face 'font-lock-keyword-face))
(setq org-cycle-separator-lines 0)
https://github.com/pashky/restclient.el
HTTP REST client tool for emacs
https://github.com/alf/ob-restclient.el
An extension to restclient.el for emacs that provides org-babel support.
(org-babel-do-load-languages
'org-babel-load-languages
'((restclient . t)))
https://github.com/politza/pdf-tools
PDF Tools is, among other things, a replacement of DocView for PDF files. The key difference is that pages are not pre-rendered by e.g. ghostscript and stored in the file-system, but rather created on-demand and stored in memory.
PDF Tools for me is - hands down - the best PDF viewer! It’s not an excuse to do even more within Emacs.
When using evil-mode
and pdf-tools
and looking at a zoomed PDF, it
will blink, because the cursor blinks. This configuration disables
this whilst retaining the blinking cursor in other modes.
(evil-set-initial-state 'pdf-view-mode 'emacs)
(add-hook 'pdf-view-mode-hook
(lambda ()
(set (make-local-variable 'evil-emacs-state-cursor) (list nil))))
Elfeed is an extensible web feed reader for Emacs, supporting both Atom and RSS.
(require 'elfeed)
(require 'elfeed-goodies)
(elfeed-goodies/setup)
Automatic word-wrap for elfeed entries:
(add-hook 'elfeed-show-mode-hook 'visual-clean)
Use VIM style scrolling in elfeed entries:
(define-key elfeed-show-mode-map (kbd "C-e") 'evil-scroll-line-down)
(define-key elfeed-show-mode-map (kbd "C-y") 'evil-scroll-line-up)
(load "~/.emacs.d/elfeed-feeds.el")
Editing text areas in browsers can be quite tedious for the lack of a good editor. Luckily, there’s good extensions for both Chrome/Chromium and Firefox to have a live binding to an Emacs session.
There is a good Emacs package called Atomic Chrome which is similar to Edit with Emacs, but has some advantages as below with the help of websockets:
- The input on Emacs is reflected to the browser instantly and continuously.
- You can use both the browser and Emacs at the same time. They are updated to the same content bi-directionally.
The name “Atomic Chrome” is a bit misleading, because it actually supports the “GhostText” protocol which allows it to be used with Firefox, as well.
On Firefox, I’m using the GhostText addon. On Chromium, I’m using
the AtomicChrome extension. GhostText is also available for Chrome,
but it doesn’t work for me which is a non-issue, because both plugins
work just the same way: Enter a textarea, hit a button, Emacs opens
up, type the text, end the session with C-c C-c
.
(require 'atomic-chrome)
;; Handle if there is an Emacs instance running which has the server already
;; started
(ignore-errors
;; Start the server
(atomic-chrome-start-server))
Note: I opened a PR against AtomicChrome which will make the safe-guard obsolete.
Default mode
(setq atomic-chrome-default-major-mode 'markdown-mode)
Copy to clipboard
Some websites have aggressive JS which triggers when text is entered
to a textarea which can lead to bugs in combination with AtomicChrome.
There’s some websites where I regularly lose the text that’s entered.
While I’m editing, the textarea is updating, but on C-c C-c
, Emacs
closes and the textarea is empty. For such cases, I’m using this
simple workaround: Copy the contents to clipboard just before closing
Emacs. So if the contents are lost, I can just paste the text into the
textarea. Not a perfect solution, but this happens seldomly enough,
that it’s good enough for me.
(advice-add 'atomic-chrome-close-current-buffer
:before
'(lambda()
(clipboard-kill-ring-save (point-min) (point-max))))
Writing and reading mail is inherently a text-based workflow. Yes, there’s HTML mails and attachments, but at the core Email is probably the place where many people write and consume the most text. To utilize the best text-processing program available, makes a lot of sense.
When combined with other powerful features of Emacs (such as org-mode for organizing mails into projects and todos), processing mails within Emacs not only makes a lot of sense, but becomes a powerhouse.
Emacs has many options for MTAs. I’m using MU4E which is a little
similar to using mutt with notmuch. As SMTP, I’m using the built-in
smtpmail
Emacs package.
MU works on a local Maildir folder. For synchronization offlineimap is used. Install:
- Debian:
apt-get install offlineimap
- macOS:
brew install offlineimap
For MU4E to work, install MU and MU4E:
- Debian:
apt-get install mu4e
- Guix:
guix package -i mu
- macOS:
brew install mu --with-emacs
For starttls to work when sending mail, install gnutls:
- Debian:
apt-get install gnutls-bin
- macOS:
brew install gnutls
- Configure
.offlineimaprc
file for IMAP - Configure
.authinfo
file for SMTP - https://www.emacswiki.org/emacs/GnusAuthinfo
Tell Emacs where to find the encrypted .authinfo
file.
(setq auth-sources '((:source "~/.authinfo.gpg")))
To open PDFs within Mu4e with Emacs, then there’s one thing to
configure. Mu4e uses xdg-open
to chose the app to open any mime type.
Configure xdg-open
to use Emacs in .local/share/applications/mimeapps.list
:
xdg-mime default emacs.desktop application/pdf
Accounts setup
(require 'mu4e)
(require 'org-mu4e)
(setq send-mail-function 'smtpmail-send-it)
;; Default account on startup
(setq user-full-name "Alain M. Lafon"
mu4e-sent-folder "/200ok/INBOX.Sent"
mu4e-drafts-folder "/200ok/INBOX.Drafts"
mu4e-trash-folder "/200ok/INBOX.Trash")
(setq smtpmail-debug-info t
message-kill-buffer-on-exit t
;; Custom script to run offlineimap in parallel for multiple
;; accounts as discussed here:
;; http://www.offlineimap.org/configuration/2016/01/29/why-i-m-not-using-maxconnctions.html
;; This halves the time for checking mails for 4 accounts for me
;; (when nothing has to be synched anyway)
mu4e-get-mail-command "offlineimap_parallel.sh"
mu4e-attachment-dir "~/Dropbox/org/files/inbox")
(setq mu4e-maildir "~/Maildir/")
;; show full addresses in view message (instead of just names)
;; toggle per name with M-RET
(setq mu4e-view-show-addresses t)
;; Do not show related messages by default (toggle with =W= works
;; anyway)
(setq mu4e-headers-include-related nil)
;; Alternatives are the following, however in first tests they
;; show inferior results
;; (setq mu4e-html2text-command "textutil -stdin -format html -convert txt -stdout")
;; (setq mu4e-html2text-command "html2text -utf8 -width 72")
;; (setq mu4e-html2text-command "w3m -dump -T text/html")
(defvar my-mu4e-account-alist
'(("200ok"
(user-full-name "Alain M. Lafon")
(mu4e-compose-signature "200ok GmbH\nGeschäftsführer\n\nalain@200ok.ch\n+41 76 405 05 67\nhttps://200ok.ch/\n\nCheck out our newest spin-off: https://quickshift.ch/")
(mu4e-compose-signature-auto-include t)
(mu4e-sent-folder "/200ok/INBOX.Sent")
(mu4e-drafts-folder "/200ok/INBOX.Drafts")
(mu4e-trash-folder "/200ok/INBOX.Trash")
(user-mail-address "alain@200ok.ch")
(smtpmail-default-smtp-server "mail.your-server.de")
(smtpmail-local-domain "200ok.ch")
(smtpmail-smtp-user "munen@200ok.ch")
(smtpmail-smtp-server "mail.your-server.de")
(smtpmail-stream-type starttls)
(smtpmail-smtp-service 25))
("zhaw"
(mu4e-compose-signature-auto-include nil)
(user-full-name "Alain M. Lafon")
(mu4e-sent-folder "/zhaw/Sent Items")
(mu4e-drafts-folder "/zhaw/Drafts")
(mu4e-trash-folder "/zhaw/Deleted Items")
(user-mail-address "lafo@zhaw.ch")
(smtpmail-default-smtp-server "smtps.zhaw.ch")
(smtpmail-smtp-server "smtps.zhaw.ch")
(smtpmail-local-domain "zhaw.ch")
(smtpmail-smtp-user "lafo@zhaw.ch")
(smtpmail-stream-type starttls)
(smtpmail-smtp-service 587))
("zen-tempel"
(user-full-name "Zen Mönch Alain M. Lafon")
(mu4e-compose-signature "Insopor Zen Akademie\nZen Mönch\n\nalain@zen-tempel.ch\n+41 76 405 05 67\n\nhttps://zen-temple.net/")
(mu4e-compose-signature-auto-include t)
(mu4e-sent-folder "/zen-tempel/INBOX.Sent")
(mu4e-drafts-folder "/zen-tempel/INBOX.Drafts")
(mu4e-trash-folder "/zen-tempel/INBOX.Trash")
(user-mail-address "alain@zen-tempel.ch")
(smtpmail-default-smtp-server "mail.your-server.de")
(smtpmail-local-domain "zen-tempel.ch")
(smtpmail-smtp-user "alain@zen-tempel.ch")
(smtpmail-smtp-server "mail.your-server.de")
(smtpmail-stream-type starttls)
(smtpmail-smtp-service 25))
("dispatched"
(user-full-name "Alain M. Lafon")
(mu4e-compose-signature-auto-include nil)
(mu4e-sent-folder "/dispatched/INBOX.Sent")
(mu4e-drafts-folder "/dispatched/INBOX.Drafts")
(mu4e-trash-folder "/dispatched/INBOX.Trash")
(user-mail-address "alain.lafon@dispatched.ch")
(smtpmail-default-smtp-server "mail.your-server.de")
(smtpmail-local-domain "dispatched.ch")
(smtpmail-smtp-user "munen@dispatched.ch")
(smtpmail-smtp-server "mail.your-server.de")
(smtpmail-stream-type starttls)
(smtpmail-smtp-service 25))))
;; Whenever a new mail is to be composed, change all relevant
;; configuration variables to the respective account. This method is
;; taken from the MU4E documentation:
;; http://www.djcbsoftware.nl/code/mu/mu4e/Multiple-accounts.html#Multiple-accounts
(defun my-mu4e-set-account ()
"Set the account for composing a message."
(let* ((account
(if mu4e-compose-parent-message
(let ((maildir (mu4e-message-field mu4e-compose-parent-message :maildir)))
(string-match "/\\(.*?\\)/" maildir)
(match-string 1 maildir))
(completing-read (format "Compose with account: (%s) "
(mapconcat #'(lambda (var) (car var))
my-mu4e-account-alist "/"))
(mapcar #'(lambda (var) (car var)) my-mu4e-account-alist)
nil t nil nil (caar my-mu4e-account-alist))))
(account-vars (cdr (assoc account my-mu4e-account-alist))))
(if account-vars
(mapc #'(lambda (var)
(set (car var) (cadr var)))
account-vars)
(error "No email account found"))))
(add-hook 'mu4e-compose-pre-hook 'my-mu4e-set-account)
(add-hook 'mu4e-compose-mode-hook (lambda ()
(ispell-change-dictionary "deutsch")))
(setq mu4e-refile-folder
(lambda (msg)
(cond
((string-match "^/dispatched.*"
(mu4e-message-field msg :maildir))
"/dispatched/INBOX.Archive")
((string-match "^/zen-tempel.*"
(mu4e-message-field msg :maildir))
"/zen-tempel/INBOX.Archive")
((string-match "^/200ok.*"
(mu4e-message-field msg :maildir))
"/200ok/INBOX.Archive")
((string-match "^/zhaw.*"
(mu4e-message-field msg :maildir))
"/zhaw/Archive")
;; everything else goes to /archive
(t "/archive"))))
;; Empty the initial bookmark list
(setq mu4e-bookmarks '())
;; Re-define all standard bookmarks to not include the spam folders
;; for searches
(defvar d-spam "NOT (maildir:/dispatched/INBOX.spambucket OR maildir:/zen-tempel/INBOX.spambucket OR maildir:/200ok/INBOX.spambucket OR maildir:/zhaw/\"Junk E-Mail\" OR maildir:/zhaw/\"Deleted Items\")")
;; All archived folders
(defvar d-archive "NOT (maildir:/dispatched/INBOX.Archive OR maildir:/zen-tempel/INBOX.Archive OR maildir:/200ok/INBOX.Archive OR maildir:/zhaw/Archive)")
(defvar inbox-folders (string-join '("maildir:/dispatched/INBOX"
"maildir:/zhaw/INBOX"
"maildir:/zen-tempel/INBOX"
"maildir:/200ok/INBOX")
" OR "))
(defvar draft-folders (string-join '("maildir:/dispatched/INBOX.Drafts"
"maildir:/zhaw/Drafts"
"maildir:/zen-tempel/INBOX.Drafts"
"maildir:/200ok/INBOX.Drafts")
" OR "))
(defvar spam-folders (string-join '("maildir:/dispatched/INBOX.spambucket"
"maildir:/zhaw/INBOX.spambucket"
"maildir:/zen-tempel/INBOX.spambucket"
"maildir:/200ok/INBOX.spambucket")
" OR "))
(add-to-list 'mu4e-bookmarks
'((concat d-spam " AND date:today..now") "Today's messages" ?t))
(add-to-list 'mu4e-bookmarks
'((concat d-spam " AND date:7d..now") "Last 7 days" ?w))
(add-to-list 'mu4e-bookmarks
'((concat d-spam " AND flag:flagged") "Flagged" ?f))
(add-to-list 'mu4e-bookmarks
'((concat d-spam " AND mime:image/*") "Messages with images" ?p))
(add-to-list 'mu4e-bookmarks
'(spam-folders "All spambuckets" ?S))
(add-to-list 'mu4e-bookmarks
'(draft-folders "All drafts" ?d))
(add-to-list 'mu4e-bookmarks
'(inbox-folders "All inbox mails" ?i))
(add-to-list 'mu4e-bookmarks
'((concat d-spam d-archive " AND (flag:unread OR flag:flagged) AND NOT flag:trashed")
"Unread messages" ?u))
Check for supposed attachments prior to sending them
(defvar my-message-attachment-regexp "\\(
[Ww]e send\\|
[Ii] send\\|
attach\\|
[aA]ngehängt\\|
[aA]nhang\\|
[sS]chicke\\|
angehaengt\\|
haenge\\|
hänge\\)")
(defun my-message-check-attachment nil
"Check if there is an attachment in the message if I claim it."
(save-excursion
(message-goto-body)
(when (search-forward-regexp my-message-attachment-regexp nil t nil)
(message-goto-body)
(unless (or (search-forward "<#part" nil t nil)
(message-y-or-n-p
"No attachment. Send the message ?" nil nil))
(error "No message sent")))))
(add-hook 'message-send-hook 'my-message-check-attachment)
For mail completion, only consider emails that have been seen in the last 6 months. This gets rid of legacy mail addresses of people.
(setq mu4e-compose-complete-only-after (format-time-string
"%Y-%m-%d"
(time-subtract (current-time) (days-to-time 150))))
HTML Mails
(require 'mu4e-contrib)
(setq mu4e-html2text-command 'mu4e-shr2text)
;;(setq mu4e-html2text-command "iconv -c -t utf-8 | pandoc -f html -t plain")
(add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t)
Disable “HTML over plain text” heuristic. This variable officially has this rationale: “Ratio between the length of the html and the plain text part below which mu4e will consider the plain text part to be ‘This messages requires html’ text bodies. You can neutralize it (always show the text version) by using `most-positive-fixnum’.”
This heuristic overwrites the default setting (and configuration) that Plain text should be preferred over HTML!
In my experience, HTML Emails are WAY longer than only 5x the Plain text (Doodle, Airbnb, Meetup, etc), so this will yield me a lot of false positives whereas I have never seen a “This message requires HTML” body.
I wrote an accompanying blog post with further information: https://200ok.ch/posts/2018-10-25_disable_mu4e_html_over_plain_text_heuristic.html
(setq mu4e-view-html-plaintext-ratio-heuristic most-positive-fixnum)
Setting Format-Flowed
for non-text-based mail clients which don’t
respect actual formatting, but let the text “flow” as they please.
(setq mu4e-compose-format-flowed t)
Do not auto-wrap lines in favor of Format-Flowed
, but still display
them nicely wrapped in my editor, because everything else is insane!
(add-hook 'mu4e-compose-mode-hook 'visual-clean)
(add-hook 'mu4e-compose-mode-hook 'flyspell-mode)
Updating mails:
- Periodic - every 15 minutes
- Happening in the background
Note: There’s no notifications, because that’s only distracting.
(setq mu4e-update-interval (* 15 60))
(setq mu4e-index-update-in-background t)
GPG configuration:
C-c RET s o
to signC-c RET C-c
to encryptC-c C-e v
to verify the signatureC-c C-e d
to decrypt
Always sign outgoing emails:
(setq mu4e-compose-crypto-reply-plain-policy 'sign)
(add-hook 'mu4e-compose-mode-hook 'epa-mail-mode)
(add-hook 'mu4e-view-mode-hook 'epa-mail-mode)
Automatic line breaks when reading mail
(add-hook 'mu4e-view-mode-hook 'visual-line-mode)
Do not reply to self
(setq mu4e-compose-dont-reply-to-self t)
(add-to-list 'mu4e-user-mail-address-list "alain@200ok.ch")
(add-to-list 'mu4e-user-mail-address-list "alain.lafon@dispatched.ch")
(add-to-list 'mu4e-user-mail-address-list "lafo@zhaw.ch")
(add-to-list 'mu4e-user-mail-address-list "alain@zen-tempel.ch")
Store link to message if in header view, not to header query.
(setq org-mu4e-link-query-in-headers-mode nil)
Customize header fields to show in mu4e-view
.
This only adds :bcc
.
(setq mu4e-view-fields '(:from :to :cc :bcc :subject :flags :date :maildir :mailing-list :tags :attachments :signature :decryption))
Close mu4e without asking.
(setq mu4e-confirm-quit nil)
https://mathiasbynens.be/notes/gmail-plain-text https://mothereff.in/quoted-printable https://www.gnu.org/software/emacs/manual/html_node/emacs-mime/qp.html
Add a header action “Block” which add the Senders Name and From Address to a procmail blacklist.
(defun append-line-to-file (line path)
"Append a `line` to a file behind `path`"
(write-region (concat line "\n") nil path 'append))
(defun mu4e-blacklist-from (msg)
"Add the `from` of a message to the procmail blacklist"
(let* ((from (mu4e-message-field msg :from))
(from_name (car (car from)))
(from_address (cdr (car from))))
;; Block the senders Name
(if from_name
(append-line-to-file from_name "~/.procmail/blacklist_from.txt"))
;; Block the Email-Address
(append-line-to-file from_address "~/.procmail/blacklist_from.txt")
(message "Blocking: %s" from)))
(defun mu4e-blacklist-subject (msg)
"Add the `subject` of a message to the procmail blacklist"
(let* ((subject (mu4e-message-field msg :subject)))
(if subject
(append-line-to-file subject "~/.procmail/blacklist_subject.txt"))
(message "Blocking: %s" subject)))
(add-to-list 'mu4e-headers-actions
'("F Block 'From:'" . mu4e-blacklist-from) t)
(add-to-list 'mu4e-headers-actions
'("S Block 'Subject:'" . mu4e-blacklist-subject) t)
ido
means “Interactively Do Things”. ido
has a completion engine
that’s sensible to use everywhere. It is built-in and nice and could
change a lot of defaults like find-file
and switching buffers.
It works well while not breaking Emacs defaults.
(ido-mode t)
(ido-everywhere t)
(setq ido-enable-flex-matching t)
https://github.com/creichert/ido-vertical-mode.el
ido-vertical-mode
makes ido-mode
display vertically.
(ido-vertical-mode 1)
(setq ido-vertical-define-keys 'C-n-and-C-p-only)
(setq ido-vertical-show-count t)
https://github.com/abo-abo/swiper
Ivy, a generic completion mechanism for Emacs.
Counsel, a collection of Ivy-enhanced versions of common Emacs commands.
Swiper, an Ivy-enhanced alternative to isearch.
Ivy
is an interactive interface for completion in Emacs. Therefore
it overlaps in functionality with ido
. While Ivy
is more powerful,
it breaks certain standard functionality. So ido
is enabled globally
by default and for certain tasks, Ivy
overrides ido
.
Emacs uses completion mechanism in a variety of contexts: code, menus,
commands, variables, functions, etc. Completion entails listing,
sorting, filtering, previewing, and applying actions on selected
items. When active, ivy-mode
completes the selection process by
narrowing available choices while previewing in the minibuffer.
Selecting the final candidate is either through simple keyboard
character inputs or through powerful regular expressions.
(setq enable-recursive-minibuffers t)
(global-set-key (kbd "<f6>") 'ivy-resume)
Show total amount of matches and the index of the current match
(setq ivy-count-format "(%d/%d) ")
Wrap to the first result when on the last result and vice versa.
(setq ivy-wrap t)
Enable Swiper
(global-set-key "\C-s" 'swiper)
Configure Counsel
(global-set-key (kbd "C-x b") 'counsel-ibuffer)
;; Run `counsel-ag` against the current directory and not against the
;; whole project
(global-set-key (kbd "C-c k") '(lambda()
(interactive)
(counsel-ag "" default-directory nil nil)))
(global-set-key (kbd "C-x l") 'counsel-locate)
(define-key minibuffer-local-map (kbd "C-r") 'counsel-minibuffer-history)
Next to counsel, there’s also smex
which is M-x
combined with
ido
. smex
has a better sorting algorithm than Counsel
and having
both installed means that we get the Counsel
interface with smex
sorting. Best of both worlds.
By default, counsel-M-x
starts with a ^
. More often than not, this
will be in the way of me fuzzy matching a function. Therefore I’ll
start it with an empty string as argument.
(global-set-key (kbd "M-x") (lambda ()
(interactive)
(counsel-M-x "")))
Some basic features are overwritten when “everything” becomes an Ivy
search buffer. For example:
- When two
dired
buffers are open and files should be copied from one to the other, one can use theup
anddown
keys to toggle the destination. When this is a search buffer, it will auto complete for all local folders, instead. Since copying files is something I do often, this already means I have to disableIvy
globally. Tramp
auto-completion doesn’t work for me. I’m usingsudo:
,ssh:
and the likes a lot indired
mode. Auto completion when withinTramp
is broken for me, so I always have to type out the whole connection string whenIvy
is enabled fordired
. Since this includes missing auto-completion on remote systems and such, it’s another valid reason to disableIvy
globally.
Ivy/Swiper cannot search in PDFs. It tries to search in the PDF source code. Therefore I fall back to using isearch within PDFs.
(add-hook 'pdf-view-mode-hook '(lambda()
(define-key pdf-view-mode-map "\C-s" 'isearch-forward)))
Projectile completion (Default is ido
)
(setq projectile-completion-system 'ivy)
Mu4e “folder” and “from” completion (Default is ido
)
(setq mu4e-completing-read-function 'ivy-completing-read)
Synosaurus completion (Default is ido
)
(setq synosaurus-choose-method 'ivy-read)
I used to use isearch
instead of Swiper
.
Replace i-search-(forward|backward) with their respective regexp capable counterparts
;;(global-set-key (kbd "C-s") 'isearch-forward-regexp)
;;(global-set-key (kbd "C-r") 'isearch-backward-regexp)
For chat-based communication, I like to use IRC. In my
~/.authinfo.gpg
file, I have a line like:
machine irc.freenode.net login "munen" password SECRET_PASSWORD
This file is automatically read when connecting to servers. It’s the same for SMTP servers, for example.
For connecting to IRC, I’m using the built-in package erc
.
Configure automatic join list
(setq erc-autojoin-channels-alist '(("freenode.net" "#200ok" "#erc" "#guix")))
*Configure
(setq erc-channel-hide-list '(("#erc" "JOIN" "PART" "QUIT")
("#emacs" "JOIN" "PART" "QUIT")))
Automatically unfold images when links are shared
(require 'erc-image)
(add-to-list 'erc-modules 'image)
(erc-update-modules)
Logging
(setq erc-log-channels-directory "~/.erc/logs/")
(add-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs)
Notify when someone is addressing me
(setq erc-pals '("phi|" "branch14"))
(add-hook 'erc-text-matched-hook '(lambda(match-type nickuserhost msg)
(shell-command-to-string (format "notify-send erc '%s'" msg))))
https://github.com/TheBB/spaceline
This part of the configuration was kindly provided by SirPscl.
(require 'spaceline)
Slightly simplified flycheck segments for info
, warning
and error
.
(spaceline-define-segment ph/flycheck-warning-segment
(if (flycheck-has-current-errors-p)
(let ((c (cdr (assq 'warning (flycheck-count-errors
flycheck-current-errors)))))
(powerline-raw
(if c (format "%s" c))))))
(spaceline-define-segment ph/flycheck-error-segment
(if (flycheck-has-current-errors-p)
(let ((c (cdr (assq 'error (flycheck-count-errors
flycheck-current-errors)))))
(powerline-raw
(if c (format "%s" c))))))
(spaceline-define-segment ph/flycheck-info-segment
(if (flycheck-has-current-errors-p)
(let ((c (cdr (assq 'info (flycheck-count-errors
flycheck-current-errors)))))
(powerline-raw
(if c (format "%s" c))))))
Default faces for the flycheck segments.
(defface ph/spaceline-flycheck-error-face
'((t :inherit 'mode-line
:weight bold
:foreground "white"
:background "dark red"))
"Flycheck Error Face"
:group 'spaceline)
(defface ph/spaceline-flycheck-warning-face
'((t :inherit 'mode-line
:weight bold
:foreground "white"
:background "DarkOrange3"))
"Flycheck Warning Face"
:group 'spaceline)
(defface ph/spaceline-flycheck-info-face
'((t :inherit 'mode-line
:weight bold
:foreground "white"
:background "dark green"))
"Flycheck Info Face"
:group 'spaceline)
Setting the face according to evil-state
.
(defun ph/spaceline-highlight-face-evil-state ()
"Set the highlight face depending on the evil state."
(if (bound-and-true-p evil-local-mode)
(let* ((face (assq evil-state spaceline-evil-state-faces)))
(if face (cdr face) (spaceline-highlight-face-default)))
(spaceline-highlight-face-default)))
(setq-default spaceline-highlight-face-func
'ph/spaceline-highlight-face-evil-state)
Set the evil-state segment colors for operator-state
.
(defface ph/spaceline-evil-operator-face
'((t (:background "cornflower blue"
:inherit 'spaceline-evil-normal)))
"Spaceline Evil Operator State"
:group 'spaceline)
(add-to-list 'spaceline-evil-state-faces
'(operator . ph/spaceline-evil-operator-face))
(defun ph/git-branch-name ()
(replace-regexp-in-string "^ Git[:-]" "" vc-mode))
(spaceline-define-segment ph/version-control
"Version control information."
(when vc-mode
(s-trim (concat (ph/git-branch-name)))))
Tramp offers the following file name syntax to refer to files on other machines.
/method:host:filename
/method:user@host:filename
/method:user@host#port:filename
The following segemnts display the current buffer’s method
and user@host
.
(spaceline-define-segment ph/remote-method
(when (and default-directory
(file-remote-p default-directory 'method))
(file-remote-p default-directory 'method)))
(spaceline-define-segment ph/remote-user-and-host
(when (and default-directory
(or
(file-remote-p default-directory 'user)
(file-remote-p default-directory 'host)))
(concat
(file-remote-p default-directory 'user) "@"
(file-remote-p default-directory 'host))))
Default faces for the tramp segments.
(defface ph/spaceline-tramp-user-host-face
'((t :inherit 'mode-line
:foreground "black"
:background "#fce94f"))
"Tramp User@Host Face"
:group 'spaceline)
(defface ph/spaceline-tramp-method-face
'((t :inherit 'mode-line
:foreground "black"
:background "#ff5d17"))
"Tramp Method Face"
:group 'spaceline)
I’m not using Mu4e contexts, yet, because my configuration started before they were introduced. I’m leaving the segment configuration for the future.
;; (spaceline-define-segment ph/mu4e-context-segment
;; (let ((context (mu4e-context-current)))
;; (when (and context
;; (string-prefix-p "mu4e" (symbol-name major-mode)))
;; (mu4e-context-name context))))
Face for mu4e
segemnt.
;; (defface ph/spaceline-mu4e-context-face
;; '((t :inherit 'mode-line
;; :weight bold))
;; "mu4e face"
;; :group 'spaceline)
I like to set timers, for example through org-pomodoro.el
(spaceline-define-segment org-timer-left-time
"Show the time left in the current org-timer (i.e. a pomodoro)."
(when (boundp 'org-timer-countdown-timer)
(if org-timer-countdown-timer
(let* ((rtime (decode-time
(time-subtract (timer--time org-timer-countdown-timer)
(current-time))))
(rsecs (nth 0 rtime))
;; Show time only in 15s increments (so it's not too
;; distracting). This could probably done in math instead
;; of a cond statement.
(dsecs (cond
((>= rsecs 45) 45)
((>= rsecs 30) 30)
((>= rsecs 15) 15)
((< rsecs 15) 0)))
(rmins (nth 1 rtime)))
(format "%02d:%02d" rmins dsecs)))))
Setting up the mode-line and order of segements. Compile the modeline with M-x
spaceline-compile
.
(require 'spaceline-config)
(spaceline-emacs-theme)
(spaceline-install
'main
'((evil-state :face highlight-face)
(buffer-id)
(org-timer-left-time)
;; (ph/mu4e-context-segment :face 'ph/spaceline-mu4e-context-face)
(ph/remote-method :face 'ph/spaceline-tramp-method-face)
(ph/remote-user-and-host :face 'ph/spaceline-tramp-user-host-face)
(buffer-modified))
'(;;(minor-modes :when active)
(projectile-root)
(ph/version-control)
;(line-column :when active)
;(buffer-position :when active)
(ph/flycheck-info-segment :face 'ph/spaceline-flycheck-info-face :when active)
(ph/flycheck-warning-segment :face 'ph/spaceline-flycheck-warning-face :when active)
(ph/flycheck-error-segment :face 'ph/spaceline-flycheck-error-face :when active)
(line-column)
(major-mode)))
Set mode-line always active (don’t hide segments when focus is on a different window).
(defun powerline-selected-window-active () t)
Diminish implements hiding or abbreviation of the mode line displays (lighters) of minor-modes.
(eval-after-load "auto-revert"
'(diminish 'auto-revert-mode))
(eval-after-load "beacon"
'(diminish 'beacon-mode))
(eval-after-load "ivy"
'(diminish 'ivy-mode))
(eval-after-load "projectile"
'(diminish 'projectile-mode))
(eval-after-load "projectile-rails"
'(diminish 'projectile-rails-mode))
(eval-after-load "rainbow-mode"
'(diminish 'rainbow-mode))
(eval-after-load "undo-tree"
'(diminish 'undo-tree-mode))
(eval-after-load "which-key"
'(diminish 'which-key-mode))
https://github.com/hlissner/emacs-hide-mode-line
A minor mode that hides (or masks) the mode-line in your current buffer. It can be used to toggle an alternative mode-line, toggle its visibility, or simply disable the mode-line in buffers where it isn’t very useful otherwise.
(require 'hide-mode-line)
(add-hook 'pdf-view-mode-hook #'hide-mode-line-mode)
https://github.com/bnbeckwith/writegood-mode
This is a minor mode to aid in finding common writing problems.
It highlights text based on a set of weasel-words, passive-voice and duplicate words.
https://github.com/hpdeifel/synosaurus/
Synosaurus is a thesaurus front-end with pluggable back-end.
Use the openthesaurus.de back-end.
(setq synosaurus-backend 'synosaurus-backend-openthesaurus)
Flyspell is a built-in minor mode for on-the-fly spell checking.
Flyspell uses ispell or aspell in the background. I’m using the default (ispell) and have installed a German dictionary from here.
Order corrections by likeliness, not by the default of alphabetical ordering.
(setq flyspell-sort-corrections nil)
Do not print messages for every word (when checking the entire buffer). This is a major performance gain.
(setq flyspell-issue-message-flag nil)
Switch between German and English dictionaries.
(defun flyspell-switch-dictionary()
"Switch between German and English dictionaries"
(interactive)
(let* ((dic ispell-current-dictionary)
(change (if (string= dic "deutsch") "english" "deutsch")))
(ispell-change-dictionary change)
(message "Dictionary switched from %s to %s" dic change)))
Advice to re-check the buffer after a word has been added to the dictionary. This has the benefit of the word actually being cleared, but the downside that the whole buffer has to be re-checked which an take some time.
(defun flyspell-buffer-after-pdict-save (&rest _)
(flyspell-buffer))
(advice-add 'ispell-pdict-save :after #'flyspell-buffer-after-pdict-save)
The proper solution (for which I don’t have time now) is to just mark all further occurrences of the word you just saved as correct (without having to recheck the whole buffer).
“Fira Code Retina” as default font. Get it via the fonts-firacode
Debian package.
(when (eq system-type 'gnu/linux)
(set-frame-font "Fira Code Retina 15")
;; Default Browser
(setq browse-url-browser-function 'browse-url-generic
browse-url-generic-program "firefox"
browse-url-new-window-flag t)
(menu-bar-mode -1)
;; enable pdf-tools
(pdf-tools-install))
Display Emoji (requires the fonts-symbola
Debian package)
(set-fontset-font t nil "Symbola" nil 'prepend)
(when (eq system-type 'darwin)
(set-frame-font "Menlo 14")
; Use Spotlight to search with M-x locate
(setq locate-command "mdfind"))
The following packages would be nice, in theory. In practice something is yet amiss, but it might be different in the future. That’s why I’m keeping them around and will try them at another time.
https://github.com/bburns/clipmon
Proposition: Monitors system clipboard and puts everything in the kill-ring.
Caveat: In theory, I liked the package. However, it seemed to cause racing conditions and crashed Emacs multiple times a day. When this is re-implemented in a non-blocking mode, this would be nice.
;; (add-to-list 'after-init-hook 'clipmon-mode-start)
Theoretically this is really nice to have functionality. However, I couldn’t run it for long. Emacs started freezing a lot on the day when I added this lib. I assume, because clipmon is blocking - and I always run multiple instances of Emacs in parallel. They might be in for a classic racing condition. Might be just another bug.
Proposition: When working with Lisp, there’s the option of handing parentheses manually or let them be dealt with by the magic that is Parinfer. I’m using the wonderful parinfer-mode.
Caveat: The original Parinfer curiously is written in JavaScript.
parinfer-mode
is a re-implementation in Elisp. When I tried it, it
was still in it’s early stages and quite buggy. However, the original
Parinfer algorithm is quite nice. I’ll try again at some point.
;; (add-hook 'clojure-mode-hook #'parinfer-mode)
;; (add-hook 'emacs-lisp-mode-hook #'parinfer-mode)
;; (setq parinfer-extensions '(company pretty-parens evil))
;; (eval-after-load "parinfer"
;; '(progn
;; (define-key parinfer-mode-map (kbd "C-,") 'parinfer-toggle-mode)
;; (define-key parinfer-region-mode-map (kbd ">") 'parinfer-shift-right)
;; (define-key parinfer-region-mode-map (kbd "<") 'parinfer-shift-left)))
(setq custom-safe-themes
(quote
("df3e05e16180d77732ceab47a43f2fcdb099714c1c47e91e8089d2fcf5882ea3"
"d09467d742f713443c7699a546c0300db1a75fed347e09e3f178ab2f3aa2c617"
"8db4b03b9ae654d4a57804286eb3e332725c84d7cdab38463cb6b97d5762ad26"
"85c59044bd46f4a0deedc8315ffe23aa46d2a967a81750360fb8600b53519b8a"
default)))
(defun dark-mode ()
"Default theme and font size. Pendant: (presentation-mode)."
(interactive)
(mapcar 'disable-theme custom-enabled-themes)
(set-face-attribute 'default nil :height 150)
;; Themes
;; (set-frame-parameter nil 'background-mode 'dark)
;; Dark, High Contrast <- favorite
(load-theme 'wombat)
(setq frame-background-mode (quote dark))
;; Dark, Low contrast
;; (load-theme 'darktooth)
;; Dark, Lowest contrast
;; (load-theme 'zenburn)
)
(defun light-mode ()
"Enables a light theme."
(interactive)
(set-face-attribute 'default nil :height 150)
(mapcar 'disable-theme custom-enabled-themes)
(load-theme 'spacemacs-light t))
(defun presentation-mode ()
"Presentation friendly theme and font size."
(interactive)
(load-theme 'leuven t)
(mapcar 'disable-theme custom-enabled-themes)
(set-face-attribute 'default nil :height 150))
(light-mode)
Create a customized time table ready for CSV export.
Usage:
#+name: ok-timetable
#+BEGIN_SRC elisp
(ok-export-org-timetable "2018-05-09")
#+END_SRC
When evaluating the src-block above, it’ll yield a table like:
#+RESULTS: ok-timetable
| date | hours | task |
|------------+--------+----------------------------------|
| 2018-05-09 | 0:02 | #support |
| 2018-05-09 | 0:17 | #support |
|------------+--------+----------------------------------|
(require 'seq)
(defun ok-filter-table-by-date (tbl from-date table-row)
"Filter a TBL by FROM-DATE which is found in TABLE-ROW."
;; Sort by date
(seq-sort '(lambda (e1 e2)
(string-lessp (nth table-row e1)
(nth table-row e2)))
;; Filter to start with FROM-DATE
(seq-filter (lambda (elem)
(let ((date-elem (nth table-row elem)))
;; >=
(when (or (string-greaterp date-elem from-date)
(string-equal date-elem from-date))
elem)))
tbl)))
(defun ok-hm-to-hours (worktime)
"Casts HH:MM WORKTIME into a floating point number."
(condition-case worktime
(let* ((time (split-string worktime ":"))
(minutes (/ (string-to-number (second time))
60.0))
(hours (string-to-number (first time))))
(format "%.3f" (+ hours minutes)))
(error 0)))
(defun ok-split-hash-and-description (text)
"Given a TEXT like '#tag1 #tag2 some description' and return tags and description as a list."
;; The concat is a little hack, so that there's always a minimum
;; description to be found
(let ((text (concat text " ")))
;; A hashtag can have numbers, dashes and a-z
(if (string-match "\\(#[a-z-0-9]+ \\)+" text)
(let* ((hashtags (match-string 0 text))
;; Couldn't figure out how to get the description
;; through an elisp regexp, so I'm just reading the
;; remainder of the text after all hashtags here
(description (substring text (length hashtags) (length text))))
(list
(string-trim hashtags)
(string-trim description))))))
(defun ok-generate-clock-table ()
"Generate a list of org elements of type 'clock."
(let* ((ast (org-element-parse-buffer 'element)))
;; Map a function to all elements of TYPE 'clock which extracts
;; the TITLE, DURATION and DATE of a TODO.
(org-element-map ast 'clock
(lambda (clock-elem)
(let* ((val (org-element-property :value clock-elem))
(task (org-element-property :parent (org-element-property :parent clock-elem)))
(hash-and-description (ok-split-hash-and-description
(org-element-property :title task))))
`(,(let ((year (org-element-property :year-start val))
(month (org-element-property :month-start val))
(day (org-element-property :day-start val)))
(format "%4d-%02d-%02d" year month day ))
,(ok-hm-to-hours (org-element-property :duration clock-elem))
,(first hash-and-description)
,(second hash-and-description)))))))
(defun ok-export-org-timetable (from-date)
"Generate a list from 'org-mode' clock elements starting from FROM-DATE."
;; Concatenate header, element data and footer into one list which
;; will automatically be rendered by org-mode as a table.
(append
'(("date" "duration" "hashtags" "description"))
'(hline)
;; Generate tree of all visible elements within buffer (narrowing
;; works).
(ok-filter-table-by-date (ok-generate-clock-table) from-date 0)))
(defun ok-export-next-table-to (target-path)
"Exports the contents of the next org table to a CSV file at TARGET-PATH."
(let ((p (point)))
(search-forward "RESULTS")
(evil-next-visual-line)
(org-table-export target-path "orgtbl-to-csv")
(goto-char p)
(message "Table export completed")))
(load-file "~/src/200ok/200ok-admin/src/export-org-estimations/ok-export-org-estimations.el")
- A business partners configuration: https://github.com/SirPscl/emacs.d
Modes I probably could use, but haven’t tried out, yet.
Kudos SirPscl