This emacs configuration is written in org-mode. The source code is available at: https://github.com/pmeiyu/dot-config/blob/master/emacs/init.org

Emacs saves you time when you work, and takes it back when you play with it.
init.el
Use M-x org-babel-tangle
or C-c C-v t
to manually extract lisp code.
Personal Information
Name and mail addresses
(setq user-full-name "Peng Mei Yu"
user-mail-address "[email protected]")
Identities
(setq my-irc-nick "pengmeiyu")
Bootstrap
Startup optimizations
;; Set garbage collection threshold.
(setq gc-cons-threshold-original gc-cons-threshold)
(setq gc-cons-threshold (* 100 gc-cons-threshold-original))
;; Restore default values after initialization.
(add-hook 'after-init-hook
(lambda ()
(setq gc-cons-threshold gc-cons-threshold-original)
(makunbound 'gc-cons-threshold-original)
(message "Restored gc-cons-threshold")))
Enforce minimum Emacs version
(let ((min-version "26.0"))
(when (version< emacs-version min-version)
(error "Gnu Emacs %s or newer is required" min-version)))
(setq load-prefer-newer t)
Emacs server
Detect existing emacs server and switch to it if possible
(require 'server)
(defun my-server-shunt ()
"Shunts to emacsclient"
(let ((args (append '("emacsclient" "-a" "\"\"" "-n")
(cdr command-line-args))))
(shell-command (substring (format "%S" args) 1 -1))
(kill-emacs)))
;; Keep only one Emacs server instance
(if (server-running-p)
(if (daemonp)
(error "Another running Emacs server detected, abort")
(my-server-shunt))
(server-start))
Require common lisp
(require 'cl-lib)
XDG specification
Define XDG directories
(require 'xdg)
(defvar user-emacs-config-dir
(expand-file-name "emacs" (xdg-config-home)))
(defvar user-emacs-data-dir
(expand-file-name "emacs" (xdg-data-home)))
(defvar user-emacs-cache-dir
(expand-file-name "emacs" (xdg-cache-home)))
Package manager
(require 'package)
(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/"))
(package-initialize)
Bootstrap use-package
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(eval-when-compile
(require 'use-package))
(require 'bind-key)
(setq use-package-verbose t)
;; Disable lazy loading in daemon mode
(if (daemonp)
(setq use-package-always-demand t))
custom.el
Variables configured via the interactive ‘customize’ interface
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(load custom-file 'noerror)
Core
Emacs built-in features
environment
Determine operating system type
(defconst *os-is-gnu* (eq system-type 'gnu/linux))
(defconst *os-is-mac* (eq system-type 'darwin))
(defconst *os-is-windows* (eq system-type 'windows-nt))
Language
(set-language-environment "UTF-8")
Locale
`set-locale-environment` changes the default coding system, therefore call it before setting coding system.
(if *os-is-gnu*
(set-locale-environment "en_US.UTF-8"))
(if *os-is-mac*
(set-locale-environment "en_US.UTF-8"))
(if *os-is-windows*
(set-locale-environment "ENU"))
Encoding
;;(set-default-coding-systems 'utf-8-unix)
(prefer-coding-system 'utf-8-unix)
Use hexadecimal instead of octal for quoted-insert (C-q).
(setq read-quoted-char-radix 16)
Font
(defun my-font-available-p (font)
"Detect if the FONT is available."
(if (find-font (font-spec :family font))
t
nil))
(defun my-set-font (font-list &optional font-size)
"Set default font to the first available font in FONT-LIST."
(let ((font (cl-find-if #'my-font-available-p font-list)))
(if (null font)
(user-error "No font is available in FONT-LIST"))
(message "Set default font to %s" font)
(set-face-font 'default
(font-spec :family font :size font-size))))
My preferred fonts
(when (display-graphic-p)
(my-set-font '("DejaVu Sans Mono" "Monospace")))
Basic interface
Inhibits the startup screen
(setq inhibit-startup-screen t)
*scratch*
buffer’s default content
(setq initial-scratch-message nil)
Hide all kinds of bars
(menu-bar-mode -1)
(if (fboundp 'tool-bar-mode)
(tool-bar-mode -1))
(if (fboundp 'scroll-bar-mode)
(scroll-bar-mode -1))
mode line
(line-number-mode t)
(column-number-mode t)
(size-indication-mode t)
ring
(setq ring-bell-function 'ignore)
buffer name
(require 'uniquify)
(setq uniquify-buffer-name-style 'forward)
(setq uniquify-separator "/")
(setq uniquify-after-kill-buffer-p t)
(setq uniquify-ignore-buffers-re "^\\*")
frame name
;; show either a file or a buffer name
(setq frame-title-format
'("" invocation-name " - "
(:eval (if (buffer-file-name)
(abbreviate-file-name (buffer-file-name))
"%b"))))
Key bindings
yes-or-no-p
(defalias 'yes-or-no-p 'y-or-n-p)
Bind C-x k
to kill-this-buffer
(global-set-key (kbd "C-x k") 'kill-this-buffer)
Expand text
(global-set-key (kbd "M-/") 'hippie-expand)
Upcase or downcase text
(global-set-key (kbd "M-u") 'upcase-dwim)
(global-set-key (kbd "M-l") 'downcase-dwim)
shell
(global-set-key (kbd "C-c s") 'eshell)
Editing
Fill column
(setq-default fill-column 80)
Final new line
(setq require-final-newline t)
Delete the selection with a key press
(delete-selection-mode t)
Smart tab key behavior, indent or complete
(setq tab-always-indent 'complete)
Indentation
;; don't use tabs to indent
(setq-default indent-tabs-mode nil)
(setq-default tab-width 8)
Revert buffers automatically when underlying files are changed externally
(global-auto-revert-mode t)
Automatically save buffers to file when losing focus
(defun my-save-buffers ()
"Save all file-visiting buffers."
(save-some-buffers t nil))
(add-hook 'focus-out-hook 'my-save-buffers)
Automatically make a shell script executable on save
(add-hook 'after-save-hook
'executable-make-buffer-file-executable-if-script-p)
Highlight
(blink-cursor-mode -1)
;; highlight the current line
(global-hl-line-mode 1)
;; highlight matching parentheses when the point is on them
(show-paren-mode t)
(setq blink-matching-paren nil)
whitespace-mode
(require 'whitespace)
(setq whitespace-style '(face empty trailing lines-tail indentation))
(setq whitespace-line-column 80)
(defun my-whitespace-mode-setup ()
(whitespace-mode 1)
(add-hook 'before-save-hook 'whitespace-cleanup nil t))
Basic major modes
text-mode
(add-hook 'text-mode-hook 'auto-fill-mode)
(add-hook 'text-mode-hook 'my-whitespace-mode-setup)
prog-mode
(add-hook 'prog-mode-hook 'abbrev-mode)
(add-hook 'prog-mode-hook 'my-whitespace-mode-setup)
(defun my-prog-mode-setup ()
(which-function-mode 1)
(setq-local comment-auto-fill-only-comments t)
(auto-fill-mode 1)
;; highlight a bunch of well known comment annotations
(font-lock-add-keywords
nil
'(("\\<\\(\\(FIX\\(ME\\)?\\|TODO\\|OPTIMIZE\\|HACK\\|REFACTOR\\):\\)"
1 font-lock-warning-face t))))
(add-hook 'prog-mode-hook 'my-prog-mode-setup)
Tramp
(require 'tramp)
(setq tramp-default-method "ssh")
Dired
(setq dired-recursive-copies 'always)
(setq dired-recursive-deletes 'always)
(require 'dired-x)
(setq dired-clean-confirm-killing-deleted-buffers nil)
Bookmark
(require 'bookmark)
(setq bookmark-save-flag 1)
Internet
Don’t send anything in HTTP header field
(setq url-privacy-level 'paranoid)
Proxy
SOCKS 5 proxy
(setq url-gateway-method 'socks)
(setq socks-server '("Default server" "localhost" 1080 5))
HTTP proxy
(setq url-proxy-services
'(("no_proxy" . "^\\(localhost\\|10\\..*\\|192\\.168\\..*\\)")
("http" . "localhost:1081")
("https" . "localhost:1081")))
Browser
eww
(global-set-key (kbd "C-c w") 'eww)
(global-set-key (kbd "C-c b") 'eww-list-bookmarks)
message mode
;; Turn on PGP
(add-hook 'message-mode-hook 'epa-mail-mode)
(add-hook 'message-send-hook 'message-sign-encrypt-if-all-keys-available)
(setq mml-secure-openpgp-encrypt-to-self t)
;; Message signature
(setq message-signature-directory
(expand-file-name "signature" (xdg-config-home)))
(setq message-signature-file "personal")
;; Don't keep message buffer after sending a message.
(setq message-kill-buffer-on-exit t)
SMTP
(setq message-send-mail-function 'message-smtpmail-send-it)
(setq smtpmail-smtp-server "smtp.mailgun.com"
smtpmail-stream-type 'ssl ;; StartTLS is evil.
smtpmail-smtp-service 465)
sendmail
(setq message-send-mail-function 'message-send-mail-with-sendmail)
;; Use the "From:" address in mail header as envelope-from address.
(setq mail-specify-envelope-from t
mail-envelope-from 'header)
(setq message-sendmail-envelope-from 'header)
msmtp
(setq sendmail-program "msmtp")
Security
GPG
Query passphrase through the minibuffer, instead of the pinentry program
(setq epg-pinentry-mode 'loopback)
auth-source
(setq auth-sources
(list (expand-file-name "auth/netrc.gpg" (xdg-data-home))))
Get secret from auth-source
(cl-defun my-get-secret (&rest spec &key domain port user &allow-other-keys)
(let ((record (nth 0 (auth-source-search :max 1
:host domain
:port port
:user user
:require '(:secret)))))
(if record
(let ((secret (plist-get record :secret)))
(if (functionp secret)
(funcall secret)
secret))
nil)))
Session
Desktop
(setq desktop-auto-save-timeout 600)
(desktop-save-mode t)
Recent files
(require 'recentf)
(setq recentf-auto-cleanup 'never)
(setq recentf-exclude
(mapcar 'expand-file-name
(list "/gnu" "/nix" "/tmp" "/ssh:" "~/.cache"
package-user-dir)))
(recentf-mode 1)
minibuffer history
(savehist-mode 1)
Auto-save
(setq auto-save-list-file-prefix
(expand-file-name "auto-save-list/" user-emacs-cache-dir))
Backup
(let ((backup-dir (expand-file-name "backup" user-emacs-cache-dir)))
(setq-default backup-directory-alist `((".*" . ,backup-dir))))
Theme
Theme
(use-package zenburn-theme
:ensure t
:config
(load-theme 'zenburn))
Transparency
alpha ‘( . )
(set-frame-parameter (selected-frame) 'alpha '(95 . 60))
(add-to-list 'default-frame-alist '(alpha . (98 . 80)))
Mode line
(use-package diminish
:ensure t
:config
(diminish 'abbrev-mode)
(diminish 'auto-fill-function)
(diminish 'auto-revert-mode)
(diminish 'eldoc-mode)
(diminish 'whitespace-mode))
Cursor
Highlight the cursor whenever the window scrolls
(use-package beacon
:ensure t
:diminish beacon-mode
:config
(beacon-mode t))
Utilities
Helm
(use-package helm
:ensure t
:defer 3
:diminish helm-mode
:bind-keymap ("C-c h" . helm-command-map)
:bind (("C-c f" . helm-recentf)
("C-h a" . helm-apropos)
("C-x b" . helm-mini)
("C-x C-b" . helm-buffers-list)
("C-x C-d" . helm-browse-project)
("C-x C-f" . helm-find-files)
("M-x" . helm-M-x)
("M-y" . helm-show-kill-ring)
("M-s o" . helm-occur)
:map helm-command-map
("M-g g" . helm-do-grep-rg))
:init
(defalias 'helm-do-grep-rg 'helm-do-grep-ag)
:config
(require 'helm-config)
(helm-mode 1)
(setq helm-move-to-line-cycle-in-source t)
;; fuzzy matching
(setq helm-mode-fuzzy-match t)
(setq helm-completion-in-region-fuzzy-match t)
(setq helm-M-x-fuzzy-match t
helm-buffers-fuzzy-matching t
helm-recentf-fuzzy-match t)
(add-to-list 'helm-mini-default-sources 'helm-source-bookmarks 'append)
(setq helm-ff-file-name-history-use-recentf t)
(setq helm-ff-skip-boring-files t)
;; ripgrep
(setq helm-grep-ag-command
(concat "rg --color=always --colors 'match:fg:black'"
" --colors 'match:bg:yellow' --smart-case"
" --no-heading --line-number %s %s %s"))
(setq helm-grep-ag-pipe-cmd-switches
'("--colors 'match:fg:black'" "--colors 'match:bg:yellow'")))
helm-rg
(use-package helm-rg
:ensure t
:after helm
:bind (:map helm-command-map
("g" . helm-rg)))
helm-ls-git
(use-package helm-ls-git
:ensure t
:after helm)
Projectile
(use-package projectile
:ensure t
:defer 10
:diminish projectile-mode
:bind-keymap (("C-c p" . projectile-command-map))
:config
(projectile-mode 1)
(setq projectile-project-search-path '("~/Projects")))
(use-package helm-projectile
:ensure t
:after (helm projectile)
:config
(helm-projectile-on))
File explorer
(use-package dired-sidebar
:ensure t
:commands (dired-sidebar-toggle-sidebar))
Crux
(use-package crux
:ensure t
:bind (("C-a" . crux-move-beginning-of-line)
("C-c d" . crux-duplicate-current-line-or-region)
("C-c D" . crux-delete-file-and-buffer)
("C-c e" . crux-eval-and-replace)
("C-c I" . crux-find-user-init-file)
("C-c o o" . crux-open-with)
("C-c o r" . crux-sudo-edit)
("C-c r n" . crux-rename-file-and-buffer)
("C-c TAB" . crux-indent-defun)
("C-x K" . crux-kill-other-buffers)
("C-^" . crux-top-join-line)
("C-<BACKSPACE>" . crux-kill-line-backwards)
("C-S-<BACKSPACE>" . crux-kill-whole-line)))
Key map
which-key
(use-package which-key
:ensure t
:defer 10
:diminish which-key-mode
:config
(which-key-mode 1))
discover-my-major
(use-package discover-my-major
:ensure t
:commands (discover-my-major discover-my-mode)
:bind ("C-h m" . discover-my-major))
undo-tree
(use-package undo-tree
:ensure t
:diminish undo-tree-mode
:bind ("C-x u" . undo-tree-visualize)
:config
(global-undo-tree-mode t))
Move cursor
Avy
Jump to visible text using a char-based decision tree
(use-package avy
:ensure t
:bind (("C-c j" . avy-goto-char-timer)
("M-g g" . avy-goto-line))
:config
(setq avy-background t))
ace-window
select a window
(use-package ace-window
:ensure t
:bind ("C-x o" . ace-window)
:config
(setq aw-scope 'frame))
Multiple cursors
(use-package multiple-cursors
:ensure t
:bind (("C-|" . mc/edit-lines)
("C->" . mc/mark-next-like-this)
("C-<" . mc/mark-previous-like-this)
("C-S-<mouse-1>" . mc/add-cursor-on-click)))
Search
anzu-mode enhances isearch & query-replace by showing total matches and current match position in the mode-line
(use-package anzu
:ensure t
:diminish anzu-mode
:bind (("M-%" . anzu-query-replace)
("C-M-%" . anzu-query-replace-regexp))
:config
(global-anzu-mode t))
Alert
(use-package alert
:ensure t
:config
(setq alert-default-style 'libnotify))
Version control
Git
Magit
(use-package magit
:ensure t
:mode ("/\\(\
\\(\\(COMMIT\\|NOTES\\|PULLREQ\\|TAG\\)_EDIT\\|MERGE_\\|\\)MSG\
\\|\\(BRANCH\\|EDIT\\)_DESCRIPTION\\)\\'" . git-commit-mode)
:bind (("C-x g" . magit-status)
("C-x M-g" . magit-dispatch)
("C-c M-g" . magit-file-dispatch)))
Git modes
(use-package gitconfig-mode
:ensure t
:mode ("/\\.gitconfig\\'" "/\\.git/config\\'" "/git/config\\'"
"/\\.gitmodules\\'"))
(use-package gitignore-mode
:ensure t
:mode ("/\\.gitignore\\'" "/\\.git/info/exclude\\'" "/git/ignore\\'"))
diff-hl
(use-package diff-hl
:ensure t
:defer 10
:config
(global-diff-hl-mode t)
(add-hook 'dired-mode-hook 'diff-hl-dired-mode)
(add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh))
Completion
(use-package company
:ensure t
:defer 10
:diminish company-mode
:config
(global-company-mode 1))
Spell checking
flyspell
(use-package flyspell
:ensure t
:defer 10
:diminish flyspell-mode
:preface
(defvar my-enable-flyspell nil)
(cond
((executable-find "aspell")
(setq ispell-program-name "aspell")
(setq my-enable-flyspell t))
((executable-find "hunspell")
(setq ispell-program-name "hunspell")
(setq ispell-dictionary "en_US")
(setq my-enable-flyspell t))
(t
(message "Neither aspell nor hunspell found")))
:if my-enable-flyspell
:hook ((text-mode . flyspell-mode)
(prog-mode . flyspell-prog-mode)))
Programming
Flycheck
(use-package flycheck
:ensure t
:diminish flycheck-mode
:hook (prog-mode . flycheck-mode)
:config
(setq flycheck-display-errors-function
'flycheck-display-error-messages-unless-error-list))
Language server protocol
lsp-mode
(use-package lsp-mode
:ensure t
:commands (lsp lsp-mode))
lsp-ui
(use-package lsp-ui
:ensure t
:after lsp-mode
:commands (lsp-ui-mode)
:bind (:map lsp-ui-mode-map
("M-?" . lsp-ui-peek-find-references)
("M-." . lsp-ui-peek-find-definitions)
("C-M-." . lsp-ui-peek-find-implementation)))
company-lsp
(use-package company-lsp
:ensure t
:after (company lsp-mode)
:commands (company-lsp))
debug
(use-package dap-mode
:ensure t
:commands (dap-mode dap-ui-mode dap-tooltip-mode))
helm-lsp
(use-package helm-lsp
:ensure t
:commands (helm-lsp-workspace-symbol))
lsp-treemacs
(use-package lsp-treemacs
:ensure t
:commands (lsp-treemacs-errors-list))
Parenthesis
smart parens
(use-package smartparens
:ensure t
:defer 10
:diminish smartparens-mode
:hook (prog-mode . smartparens-strict-mode)
:config
(require 'smartparens-config)
(show-smartparens-global-mode 1))
colorful parens
(use-package rainbow-delimiters
:ensure t
:hook (prog-mode . rainbow-delimiters-mode))
Yasnippet
(use-package yasnippet
:ensure t
:defer 20
:diminish yas-minor-mode
:config
(add-to-list 'yas-snippet-dirs "~/Projects/guix/etc/snippets")
(yas-global-mode 1))
Lisp
Indentation
(use-package aggressive-indent
:ensure t
:diminish aggressive-indent-mode
:hook ((emacs-lisp-mode scheme-mode) . aggressive-indent-mode))
Scheme
geiser
(use-package geiser
:ensure t
:hook (scheme-mode . geiser-mode--maybe-activate)
:config
(setq geiser-active-implementations '(guile))
(setq geiser-mode-start-repl-p t)
(setq geiser-repl-history-filename
(expand-file-name "geiser_history" user-emacs-directory)))
Guix
(use-package guix
:ensure t
:defer 20
:hook (scheme-mode . guix-devel-mode))
Bash
Language server
(add-hook 'sh-mode-hook 'lsp)
Install bash language server
npm -i -g bash-language-server
C
(setq c-default-style "linux")
(setq-default c-basic-offset 4)
Language server
(add-hook 'c-mode-hook 'lsp)
(add-hook 'c++-mode-hook 'lsp)
Install C language server
# Install clangd
clang-format
(use-package clang-format
:ensure t
:commands (clang-format-buffer))
(defun clang-format-buffer-smart ()
"Reformat buffer if .clang-format exists in the projectile root."
(when (f-exists? (expand-file-name ".clang-format" (projectile-project-root)))
(clang-format-buffer)))
(defun my-c-mode-setup ()
(add-hook 'before-save-hook 'clang-format-buffer-smart nil t))
(add-hook 'c-mode-hook 'my-c-mode-setup)
(add-hook 'c++-mode-hook 'my-c-mode-setup)
C#
csharp-mode
(use-package csharp-mode
:ensure nil
:mode ("\\.cs\\'" . csharp-mode)
:config
(defun my-csharp-mode-setup ()
(c-set-style "c#"))
(add-hook 'csharp-mode-hook 'my-csharp-mode-setup))
Language server
(add-hook 'csharp-mode-hook 'lsp)
fish shell
(use-package fish-mode
:ensure t
:mode ("\\.fish\\'" . fish-mode)
:interpreter ("fish"))
Go
(use-package go-mode
:ensure t
:mode ("\\.go\\'" . go-mode))
(use-package go-eldoc
:ensure t
:after (go-mode)
:hook (go-mode . go-eldoc-setup))
(defun my-go-mode-setup ()
(add-hook 'before-save-hook 'gofmt-before-save nil t))
(add-hook 'go-mode-hook 'my-go-mode-setup)
Language server
(add-hook 'go-mode-hook 'lsp)
Install go language server
go get -u golang.org/x/tools/cmd/gopls
Java
java-mode
(defun my-java-mode-setup ()
(setq fill-column 120))
(add-hook 'java-mode-hook 'my-java-mode-setup)
(add-hook 'java-mode-hook 'subword-mode)
Language server
(use-package lsp-java
:ensure nil
:hook (java-mode . lsp))
JavaScript
Language server
(add-hook 'js-mode-hook 'lsp)
Install JavaScript language server
npm i -g typescript
npm i -g typescript-language-server
Kotlin
kotlin-mode
(use-package kotlin-mode
:ensure nil
:mode ("\\.kts?\\'" . kotlin-mode))
Nix
nix-mode
(use-package nix-mode
:ensure t
:mode ("\\.nix\\'" . nix-mode))
Powershell
powershell-mode
(use-package powershell
:ensure nil
:mode ("\\.ps[dm]?1\\'" . powershell-mode))
Language server
(add-hook 'powershell-mode-hook 'lsp)
Python
Prefer Python 3
(setq python-shell-interpreter "python3")
python-mode
(defun my-python-mode-setup ()
(add-hook 'post-self-insert-hook
'electric-layout-post-self-insert-function
nil t))
(add-hook 'python-mode-hook 'my-python-mode-setup)
Language server
(add-hook 'python-mode-hook 'lsp)
Install python language server
pip3 install python-language-server[all]
Rust
rust-mode
(use-package rust-mode
:ensure nil
:mode ("\\.rs\\'" . rust-mode)
:config
(setq rust-format-on-save t))
Language server
(add-hook 'rust-mode-hook 'lsp)
Install Rust language server
rustup component add rls rust-analysis rust-src rustfmt
Web
CSS
Language server
(add-hook 'css-mode-hook 'lsp)
Install CSS language server
npm install -g vscode-css-languageserver-bin
HTML
htmlize – Converting buffer text and decorations to HTML.
(use-package htmlize
:ensure t
:defer 20)
rainbow mode
(use-package rainbow-mode
:ensure t
:hook ((html-mode css-mode) . rainbow-mode))
Language server
(add-hook 'html-mode-hook 'lsp)
Install HTML language server
npm install -g vscode-html-languageserver-bin
Vue
vue-mode
(use-package vue-mode
:ensure nil
:mode ("\\.vue\\'" . vue-mode))
Language server
(add-hook 'vue-mode-hook 'lsp)
Install Vue language server
npm install -g vue-language-server
File formats
Org Mode
org
(use-package org
:ensure org-plus-contrib
:defer 10
:mode ("\\.org\\'" . org-mode)
:bind (("C-c a" . org-agenda)
("C-c c" . org-capture)
("C-c l" . org-store-link)
("C-c C-," . org-insert-structure-template))
:config
(setq org-directory "~/Sync/Org")
(setq org-agenda-files (list org-directory))
(setq org-default-notes-file
(expand-file-name "Organizer.org" org-directory))
(setq my-org-inbox-file (expand-file-name "Inbox.org" org-directory))
(setq org-catch-invisible-edits 'show)
(setq org-id-track-globally nil) ; Do not store org IDs on disk.
(setq org-use-sub-superscripts '{})
;;; org-agenda
(setq org-agenda-default-appointment-duration 60)
(setq org-agenda-compact-blocks t)
(setq org-agenda-span 'month)
(setq org-agenda-start-on-weekday nil)
;;; org-todo
(setq org-log-done 'time)
(setq org-log-into-drawer t)
(setq org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d!/!)" "CANCELLED([email protected]/!)")))
(setq org-todo-repeat-to-state "NEXT")
(setq org-todo-keyword-faces '(("NEXT" :inherit warning)))
;;; org-tag
(setq org-fast-tag-selection-single-key 'expert)
(setq org-tags-column -80) ; Align right edge to 80th column.
;;; org-capture
(setq org-capture-templates
`(("i" "inbox"
entry (file my-org-inbox-file)
"* %?\n")
("j" "journal"
entry (file+olp org-default-notes-file "Journal")
"* %u\n%?\n")
("t" "todo"
entry (file+olp org-default-notes-file "Agenda")
"* TODO %?\n :PROPERTIES:\n :Captured_at: %U\n :END:\n")))
(add-to-list 'org-structure-template-alist
'("semacs" . "src emacs-lisp") t)
(add-to-list 'org-structure-template-alist
'("sscheme" . "src scheme") t)
(add-to-list 'org-structure-template-alist
'("sshell" . "src sh") t)
;;; org-clock
;; Persist the running clock and all clock history
(org-clock-persistence-insinuate)
(setq org-clock-persist t)
(setq org-clock-in-resume t)
;; Save clock data and notes in drawer
(setq org-clock-into-drawer t)
;; Remove the clock line when result time is zero
(setq org-clock-out-remove-zero-time-clocks t)
;;; org-babel
(org-babel-do-load-languages
'org-babel-load-languages
'((dot . t)
(latex . t)
(ledger . t)
(python . t)
(scheme . t)
(shell . t)
(sql . t)))
;; Prefer Python 3
(setq org-babel-python-command "python3")
;; Disable emacs-lisp-checker for org-src-mode
(add-hook 'org-src-mode-hook
(lambda ()
(setq-local flycheck-disabled-checkers
'(emacs-lisp-checkdoc))))
;;; org-export
(setq org-export-exclude-tags '("noexport" "private"))
(setq org-export-with-section-numbers nil)
(setq org-export-with-sub-superscripts '{})
(setq org-export-with-toc nil)
;;; org-html
(setq org-html-doctype "html5")
(setq org-html-html5-fancy-p t)
(setq org-html-validation-link nil)
;;; org-latex
(add-to-list 'org-latex-packages-alist '("" "color"))
(add-to-list 'org-latex-packages-alist '("" "listings"))
(setq org-latex-listings t
org-latex-listings-options '(("basicstyle" "\\small")
("frame" "single")))
;;; org-icalendar
(setq org-icalendar-alarm-time 60) ; 60 minutes before the event.
(setq org-icalendar-combined-agenda-file
(expand-file-name "agenda.ics" org-directory))
(setq org-icalendar-exclude-tags
(append org-export-exclude-tags '("archive" "journal")))
;; Include tasks that are not in DONE state.
(setq org-icalendar-include-todo t)
;; Include scheduled and deadline events.
(setq org-icalendar-use-scheduled
'(event-if-todo event-if-not-todo todo-start))
(setq org-icalendar-use-deadline
'(event-if-todo event-if-not-todo todo-due))
;; Whether to make events from plain time stamps.
(setq org-icalendar-with-timestamps 'active))
org-alert
Notifications for org agenda items
(use-package org-alert
:ensure t
:defer 20
:config
(setq org-alert-interval 600)
(org-alert-enable))
ob-http
(use-package ob-http
:ensure t
:after (ob)
:mode ("\\.http\\'" . org-mode))
ox-hugo
(use-package ox-hugo
:ensure t
:after ox)
Add UUID to all org headlines
(defun my-org-add-uuid-to-headlines-in-buffer ()
"Add ID property to all headlines in the current buffer."
(interactive)
(org-map-entries 'org-id-get-create))
CSV
(use-package csv-mode
:ensure t
:mode ("\\.csv\\'" . csv-mode))
Ledger
(use-package ledger-mode
:ensure nil
:mode ("\\.ledger\\'" . ledger-mode)
:config
(use-package flycheck-ledger
:ensure t))
Markdown
(use-package markdown-mode
:ensure nil
:mode (("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode)
("README\\.md\\'" . gfm-mode)))
po-mode
po-mode is provided by “gettext”
(use-package po-mode
:ensure nil
:mode ("\\.pot?\\'" . po-mode)
:config
;; Do not wrap lines when editing msgstr.
(add-hook 'po-subedit-mode-hook
(lambda ()
(setq fill-column 1000))))
Wrap po file
;; https://www.emacswiki.org/emacs/PoMode
(defun po-wrap ()
"Filter current po-mode buffer through `msgcat' tool to wrap all lines."
(interactive)
(if (eq major-mode 'po-mode)
(let ((tmp-file (make-temp-file "po-wrap."))
(tmp-buf (generate-new-buffer "*temp*")))
(unwind-protect
(progn
(write-region (point-min) (point-max) tmp-file nil 1)
(if (zerop
(call-process
"msgcat" nil tmp-buf t (shell-quote-argument tmp-file)))
(let ((saved (point))
(inhibit-read-only t))
(delete-region (point-min) (point-max))
(insert-buffer tmp-buf)
(goto-char (min saved (point-max))))
(with-current-buffer tmp-buf
(error (buffer-string)))))
(kill-buffer tmp-buf)
(delete-file tmp-file)))))
TeX
AUCTeX
(use-package auctex
:ensure t
:mode ("\\.tex\\'" . LaTeX-mode)
:config
(setq TeX-auto-save t
TeX-parse-self t
TeX-PDF-mode t)
(setq-default TeX-master nil)
(use-package company-auctex
:ensure t
:after (company auctex)
:config (company-auctex-init)))
RefTeX
(setq reftex-plug-into-AUCTeX t)
(add-hook 'LaTeX-mode-hook 'turn-on-reftex)
Language server
(add-hook 'LaTeX-mode-hook 'lsp)
Install TeX language server
# Install digestif
YAML
(use-package yaml-mode
:ensure t
:mode ("\\.yaml\\'" "\\.yml\\'"))
XML
Language server
(add-hook 'nxml-mode-hook 'lsp)
(setq lsp-xml-jar-file
(expand-file-name "org.eclipse.lsp4xml.jar" user-emacs-directory))
Install XML language server
# Download from https://github.com/angelozerr/lsp4xml/releases
Internet
BBDB
(use-package bbdb
:ensure t
:config
(bbdb-initialize 'gnus 'message 'mu4e))
Gnus
(use-package gnus
:commands (gnus)
:config
;; Email servers
(setq my--gnus-local '(nnmaildir "local"
(directory "~/Mail")
(get-new-mail nil)))
;; Usenet servers
(setq my--gnus-gmane
'(nntp "gmane"
(nntp-address "news.gmane.org")
(nntp-port-number 563)
(nntp-open-connection-function nntp-open-tls-stream))
my--gnus-aioe
'(nntp "aioe"
(nntp-address "nntp.aioe.org")
(nntp-port-number 563)
(nntp-open-connection-function nntp-open-tls-stream)))
(setq gnus-select-method my--gnus-local)
(setq gnus-secondary-select-methods
(list my--gnus-gmane my--gnus-aioe)))
mu4e
(setq mail-user-agent 'mu4e-user-agent)
(use-package mu4e
:ensure nil
:bind (("C-c m" . mu4e))
:config
(setq mu4e-confirm-quit nil)
;; Don't save message to the "sent" folder if IMAP takes care of this.
;; (setq mu4e-sent-messages-behavior 'delete)
;; Fetch email.
(setq mu4e-get-mail-command "offlineimap")
;; Default context.
(setq mu4e-maildir "~/Mail")
(setq mu4e-drafts-folder "/drafts")
(setq mu4e-refile-folder "/archive")
(setq mu4e-sent-folder "/sent")
(setq mu4e-trash-folder "/trash")
(setq mu4e-maildir-shortcuts
'(("/archive" . ?a)
("/drafts" . ?d)
("/INBOX" . ?i)
("/sent" . ?s)
("/spam" . ?j)
("/trash" . ?t)))
(add-to-list 'mu4e-headers-actions
'("git apply mbox" . mu4e-action-git-apply-mbox) t)
(add-to-list 'mu4e-view-actions
'("git apply mbox" . mu4e-action-git-apply-mbox) t))
IRC
ERC
(use-package erc
:commands (erc my-erc-start-or-switch)
:config
(setq erc-nick my-irc-nick)
(setq erc-autojoin-channels-alist
'((".*\\.freenode.net" "#emacs")))
(erc-autojoin-mode t)
;; spell checking
(erc-spelling-mode 1)
;; logging
(setq erc-log-channels-directory
(expand-file-name "erc" (xdg-data-home)))
(setq erc-save-buffer-on-part t)
;; fallback to auth-source
(setq erc-prompt-for-password nil)
;; Kill buffers for channels after /part
(setq erc-kill-buffer-on-part t)
;; Kill buffers for private queries after quitting the server
(setq erc-kill-queries-on-quit t)
;; Kill buffers for server messages after quitting the server
(setq erc-kill-server-buffer-on-quit t)
;; open query buffers in the current window
(setq erc-query-display 'buffer)
(setq erc-auto-reconnect nil)
(erc-track-mode t)
(setq erc-track-exclude-types '("JOIN" "NICK" "PART" "QUIT" "MODE"
"324" "329" "332" "333" "353" "477"))
(defun my-erc-start-or-switch ()
"Connect to ERC, or switch to last active buffer."
(interactive)
(if (get-buffer "irc.freenode.net:6697")
(erc-track-switch-buffer 1)
(when (y-or-n-p "Start ERC? ")
(erc-tls :server "irc.freenode.net" :port 6697
:nick my-irc-nick)))))
RSS feed
elfeed
(use-package elfeed
:ensure t
:bind (("C-c n" . my-elfeed-open)
:map elfeed-search-mode-map
("q" . my-elfeed-quit))
:config
(setq elfeed-db-directory
(expand-file-name "elfeed" (xdg-data-home)))
(use-package elfeed-org
:ensure t
:after elfeed
:config
(elfeed-org))
(defun my-elfeed-open ()
(interactive)
(elfeed-db-load)
(elfeed))
(defun my-elfeed-quit ()
(interactive)
(elfeed-search-quit-window)
(elfeed-db-unload)))
Lookup Wikipedia
(require 'browse-url)
(defun my-lookup-wikipedia ()
"Look up the word under cursor in Wikipedia.
If there is a text selection, use that."
(interactive)
(let (word)
(setq word
(if (use-region-p)
(buffer-substring-no-properties (region-beginning) (region-end))
(current-word)))
(setq word (replace-regexp-in-string " " "_" word))
(browse-url (format "https://en.wikipedia.org/wiki/%s" word))))
Lookup Wiktionary
(autoload 'ispell-get-word "ispell")
(defun my-lookup-wiktionary (word)
"Look up the word under cursor in Wiktionary."
(interactive (list (save-excursion (car (ispell-get-word nil)))))
(browse-url (format "https://en.wiktionary.org/wiki/%s" word)))
(global-set-key (kbd "M-#") 'my-lookup-wiktionary)
Media
EPUB
(use-package nov
:ensure nil
:mode ("\\.epub\\'" . nov-mode))
(use-package pdf-tools
:ensure nil
:mode ("\\.pdf\\'" . pdf-view-mode)
:config
(pdf-loader-install))
Developer Tools
debbugs
(use-package debbugs
:ensure nil
:commands (debbugs-gnu
debbugs-org
debbugs-gnu-bugs
debbugs-org-bugs
debbugs-gnu-search
debbugs-org-search)
:config
(setq debbugs-gnu-default-packages '("guix")))
Chinese compatibility hack
Chinese Font
To align Chinese characters with English characters vertically, the Chinese font should be rescaled.
chinese_character_width = 2 * english_character_width
if english_font_size is in [12 14 17 19 20 22]:
chinese_font_size = english_font_zise * 1.2
if english_font_size is in [13 15 16 18 21 23]:
chinese_font_size = english_font_zise * 1.25
(defun my-set-chinese-font (font-list &optional font-size)
"Set Chinese font to the first available font in FONT-LIST."
(let ((chinese-font (cl-find-if #'my-font-available-p font-list)))
(if (null chinese-font)
(user-error "No font is available in FONT-LIST"))
(message "Set Chinese font to %s" chinese-font)
(dolist (charset '(han cjk-misc))
(set-fontset-font t charset
(font-spec :family chinese-font :size font-size)))
;; Rescale Chinese fonts
(let ((rescale-factor 1.25))
(setq face-font-rescale-alist
`((".* CJK .*" . ,rescale-factor)
(".* Han .*" . ,rescale-factor)
(".*[黑宋体體].*" . ,rescale-factor))))))
My preferred fonts
(when (display-graphic-p)
(my-set-chinese-font
'("WenQuanYi Micro Hei"
"Source Han Sans"
"Noto Sans CJK SC")))
Fix org-export
by zwz.github.io
(defun clear-single-linebreak-in-cjk-string (string)
"Clear single line-break between CJK characters that is usually soft
line-breaks"
(let* ((cjk-char "[\u3000-\u303F]\\|[\u4E00-\u9FFF]\\|[\uFF01-\uFF5E]")
(regexp (concat "\\(" cjk-char "\\)\n\\(" cjk-char "\\)"))
(start (string-match regexp string)))
(while start
(setq string (replace-match "\\1\\2" nil nil string)
start (string-match regexp string start))))
string)
(defun ox-html-clear-single-linebreak-for-cjk (string backend info)
(when (org-export-derived-backend-p backend 'html)
(clear-single-linebreak-in-cjk-string string)))
(eval-after-load "ox"
'(add-to-list 'org-export-filter-final-output-functions
'ox-html-clear-single-linebreak-for-cjk))
local.init.el
Load an optional local init file
(load (locate-user-emacs-file "local.init.el") 'noerror)