.emacs.d/init.el

3177 righe
128 KiB
EmacsLisp

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; Init.el --- Daniel's Emacs Configuration -*- lexical-binding: t -*-
;;; Commentary:
;; This is the main entry point for Emacs to load this configuration. The
;; structure is roughly as follows:
;; - first comes some preliminary setup, mostly setting up `package’;
;; - the main activation of the configuration is done in the function
;; `db/run-init’, which is installed in `after-init-hook’; it is thus run
;; after init.el has been read
;; - then comes setting up all the packages that can be used by this
;; configuration; most of these packages are not loaded however, and only
;; configuration hooks are installed (using `use-package’); this way a user
;; can choose in `db/run-init’ which configuration to activate without
;; changing much of the rest of the file.
;; - this file also introduces a new customization group `personal-settings’
;; containing variables (mostly file paths) that must be set to enable some
;; of the provided functionality.
;;; Code:
;; * Preliminaries (constants and path settings)
(defconst emacs-d (file-name-directory
(file-chase-links load-file-name))
"The giant turtle on which the world rests.")
(add-to-list 'load-path (expand-file-name "site-lisp" emacs-d))
(defvar emacs-d-userdata (expand-file-name "private/" emacs-d)
"Directory to store user data in.")
;; Ensure that `emacs-d-userdata' exists as a directory, because we want to
;; store data there.
(unless (file-directory-p emacs-d-userdata)
(make-directory emacs-d-userdata))
(setq custom-file (expand-file-name "custom.el" emacs-d-userdata)
custom-theme-directory (expand-file-name "themes/" emacs-d))
(defconst on-windows (memq system-type '(windows-nt cygwin))
"Non-nil if and only if this instance of Emacs runs on Windows.")
;; * Packages
(setq package-archives
'(("melpa" . "https://melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/")
("melpa-stable" . "https://stable.melpa.org/packages/")
("org" . "https://orgmode.org/elpa/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
(when (< emacs-major-version 27)
;; Before Emacs 27.1, we had to do package initialization ourselves. In Emacs
;; 27.1 and later, it's done directly after loading early-init.el. See
;; https://www.gnu.org/software/emacs/news/NEWS.27.1
(load-file (expand-file-name "early-init.el" emacs-d))
(package-initialize))
(defvar use-package-enable-imenu-support t)
(eval-when-compile
(dolist (package '(bind-key use-package))
(unless (package-installed-p package)
(package-install package))
(require package)))
(setq use-package-always-defer t
use-package-verbose t
use-package-minimum-reported-time 0.01)
(add-to-list 'package-pinned-packages '(use-package . "melpa-stable"))
(add-to-list 'package-pinned-packages '(bind-key . "melpa-stable"))
(put 'use-package 'lisp-indent-function 1)
(put 'use-package 'common-lisp-indent-function 1)
;; * Personal Customization Variables
(use-package db-customize
:demand t
:defines (db/jabber-id
db/important-documents-path
db/path-to-onenote
db/path-to-outlook
db/cert-file-directory
org-working-task-id
org-break-task-id
org-home-task-id
db/org-clock-current-task-file
db/org-default-org-file
db/org-default-work-file
db/org-default-home-file
db/org-default-notes-file
db/org-default-refile-file
db/after-init-load-files))
;; * Core Configuration
(use-package cl-lib
:demand t)
(use-package subr-x
:demand t)
;; Encoding: use UTF-8 everywhere
(set-default-coding-systems 'utf-8)
(prefer-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(setq buffer-file-coding-system 'utf-8)
(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
;; Startup configuration
(setq inhibit-startup-message t
inhibit-default-init t
frame-inhibit-implied-resize t
initial-scratch-message nil
initial-major-mode 'fundamental-mode
ring-bell-function #'ignore
load-prefer-newer nil ; t breaks `org-reload'
)
(fset 'yes-or-no-p 'y-or-n-p)
(setq-default fill-column 80)
(setq-default indent-tabs-mode nil)
(setq frame-title-format "emacs")
(use-package help-fns
:defines (help-enable-symbol-autoload))
(setq select-enable-clipboard t
select-enable-primary t
save-interprogram-paste-before-kill t
mouse-yank-at-point t
scroll-conservatively 10
scroll-preserve-screen-position 'always ; Make M-v undo C-v
message-log-max t
inhibit-eol-conversion nil
tab-always-indent 'complete
enable-recursive-minibuffers t
set-mark-command-repeat-pop t
echo-keystrokes 0.1
delete-trailing-lines nil
x-underline-at-descent-line t
visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow)
history-delete-duplicates t
track-eol t
garbage-collection-messages nil
read-process-output-max (* 1024 1024) ; 1mb
next-error-message-highlight t
help-enable-symbol-autoload t
describe-bindings-outline t
redisplay-skip-fontification-on-input t
undo-limit 80000000
async-shell-command-buffer 'new-buffer
byte-compile-warnings '(not docstrings))
(put 'set-goal-column 'disabled nil)
(when (memq system-type '(gnu gnu/linux gnu/kfreebsd))
(setq x-wait-for-event-timeout nil))
(when on-windows
;; Fix: treat memory for display time ... hey, this is Windows, memory doesn’t
;; matter!
(setq inhibit-compacting-font-caches t))
;; don't let the cursor go into minibuffer prompt
(setq minibuffer-prompt-properties '(read-only t
face minibuffer-prompt
cursor-intangible t))
;; Fix: disable gconf settings, as it might interfere with ours, see
;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25228 and
;; https://emacs.stackexchange.com/questions/32641/something-changes-the-default-face-in-my-emacs.
(define-key special-event-map [config-changed-event] 'ignore)
;; Individual package configuration from here on.
(use-package appt
:commands (appt-activate)
:init (setq appt-display-mode-line nil))
(use-package auth-source
:init (setq auth-sources '("~/.authinfo.gpg")
auth-source-save-behavior nil))
(use-package browser-url
:init (setq browse-url-browser-function 'browse-url-generic
browse-url-generic-program "firefox"))
(use-package calc
;; https://florian.adamsky.it/2016/03/31/emacs-calc-for-programmers-and-cs.html
:defines (math-additional-units
math-units-table)
:init (setq math-additional-units
'((bit nil "Bit")
(byte "8 * bit" "Byte")
(bps "bit / s" "Bit per second"))
math-units-table nil))
(use-package calender
:init (setq calendar-date-style 'iso
calendar-time-zone-style 'numeric
calendar-week-start-day 1 ; Monday
calendar-bahai-all-holidays-flag nil
calendar-chinese-all-holidays-flag nil
calendar-christian-all-holidays-flag t
calendar-islamic-all-holidays-flag nil
calendar-hebrew-all-holidays-flag nil
holiday-general-holidays '((holiday-fixed 1 1 "New Year's Day")
(holiday-fixed 2 14 "Valentine's Day")
(holiday-fixed 4 1 "April Fools' Day")
(holiday-fixed 5 1 "Labour Day")
(holiday-fixed 10 3 "German Unity Day")
(holiday-fixed 10 31 "Reformation Day")
(holiday-float 11 3 -1 "Day of Repentance and Prayer" 22)
(holiday-float 11 4 4 "Thanksgiving"))
holiday-other-holidays '((holiday-fixed 2 13 "Jahrestag Zerstörung Dresden 1945")
(holiday-fixed 5 25 "Towel Day")
(holiday-fixed 6 4 "Tiananmen Massacre 1989")
(holiday-fixed 6 5 "Snowden-Veröffentlichungen 2013")
(holiday-fixed 6 6 "D-Day 1944")
(holiday-fixed 6 8 "Yoneda Appreciation Day")
(holiday-fixed 6 10 "Jahrestag Zerstörung von Oradour-sur-Glane 1944")
(holiday-fixed 6 10 "Jahrestag Massaker von Distomo 1944")
(holiday-fixed 6 16 "Bloomsday")
(holiday-fixed 7 20 "Jahrestag Attentat auf Hitler 1944")
(holiday-fixed 7 21 "Jahrestag der 1. Mondlandung 1969")
(holiday-fixed 7 21 "Jahrestag Massaker von Vassieux-en-Vercors 1944")
(holiday-fixed 7 28 "Start WWI 1914")
(holiday-fixed 11 11 "End WWI 1918"))
diary-show-holidays-flag t
calendar-view-holidays-initially-flag nil))
(use-package grep
:commands (rgrep zrgrep)
:bind (:map grep-mode-map
("C-x C-q" . wgrep-change-to-wgrep-mode)
("C-c C-c" . wgrep-finish-edit))
:config (progn
;; I am not quite sure why `grep-read-files' is prompting for file
;; names when asking for a file pattern, so let's just hook it up
;; and replace it with something more straightforward.
(advice-add 'grep-read-files :around #'db/grep-read-files)))
(use-package multisession
:init (setq multisession-directory (expand-file-name "multisession/" emacs-d-userdata)))
(use-package proced
:custom ((proced-tree-flag t)
(proced-auto-update-flag t)
(proced-format 'medium)
(proced-auto-update-interval 1)
(proced-goal-attribute nil)
(proced-enable-color-flag t)))
(use-package quail
:init (setq default-input-method "TeX")
:config (add-hook 'input-method-activate-hook
#'db/add-symbols-to-TeX-input-method))
(use-package savehist
:commands (savehist-mode)
:init (setq savehist-file (expand-file-name "savehist" emacs-d-userdata)))
(use-package server
:commands (server-running-p server-start)
:init (setq server-log t))
(use-package tab-bar
:init (setq tab-bar-show t
tab-bar-format '(tab-bar-format-tabs
tab-bar-separator
;; tab-bar-format-align-right
;; current-time-string
))
:config (progn
(tab-bar-history-mode +1)))
(use-package url
:init (setq url-configuration-directory (expand-file-name "url" emacs-d-userdata)))
(use-package window
:init (setq switch-to-buffer-obey-display-actions t
switch-to-buffer-in-dedicated-window 'pop
recenter-positions '(top middle bottom)
display-buffer-base-action '(display-buffer-reuse-window))
:config (progn
(add-to-list 'display-buffer-alist
'("^\\*Async Shell Command*"
(display-buffer-in-side-window)
(side . right)
(slot . 0)
(window-width . 0.33)))
(add-to-list 'display-buffer-alist
'("^\\*Warnings\\*"
(display-buffer-in-side-window)
(side . right)
(slot . 1)
(window-width . 0.33)))
;; Inspired by masteringemacs
(add-to-list 'display-buffer-alist
'("^\\*Help\\*"
(display-buffer-reuse-window
display-buffer-pop-up-window)))
;; Inspired by masteringemacs and
;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Frame-Layouts-with-Side-Windows.html
(add-to-list 'display-buffer-alist
'("^\\*eshell\\*"
display-buffer-in-side-window
(side . bottom)
(slot . -1)
(window-height . 0.33)
(window-parameters . ((no-other-window . t)))))
(add-to-list 'display-buffer-alist
'("^\\*shell\\*"
display-buffer-in-side-window
(side . bottom)
(slot . 1)
(window-height . 0.33)
(window-parameters . ((no-other-window . t)))))))
(use-package winner
:commands (winner-mode winner-undo winner-redo))
;; * Basic External Packages
(use-package crux
:ensure t
:commands (crux-eval-and-replace
crux-smart-open-line-above
crux-kill-whole-line
crux-cleanup-buffer-or-region
crux-delete-buffer-and-file))
(use-package dash
:defer nil
:autoload (-difference)
:config (progn
(global-dash-fontify-mode)
(dash-register-info-lookup)))
(use-package db-utils
:commands (endless/fill-or-unfill
db/delete-trailing-whitespace-maybe
db/run-or-hide-shell
db/gnus
db/org-agenda
db/scratch
db/find-user-init-file
db/run-or-hide-ansi-term
db/hex-to-ascii
db/text-to-hex
turn-on-lispy-when-available
turn-on-flycheck-when-file
db/sort-nsm-permanent-settings
endless/colorize-compilation
db/turn-off-local-electric-pair-mode
db/add-symbols-to-TeX-input-method
db/two-monitors-xrandr
db/one-monitor-xrandr
db/pretty-print-xml
db/bookmark-add-external
db/bookmark-add-url
db/lookup-smime-key
db/dired-from-shell-command
db/system-open
db/switch-to-dark-theme
db/switch-to-light-theme
keyboard-quit-context+
db/convert-lf-to-crlf-in-buffer
db/convert-crlf-to-lf-in-buffer
db/replace-variables-in-string
db/dired-ediff-files
db/grep-read-files
db/make-selector-from-table-header
db/get-library-version
db/dired-back-to-top
db/dired-jump-to-bottom
db/dired-get-size))
(use-package db-hydras
:commands (hydra-toggle/body
hydra-zoom/body
hydra-rectangle/body
db/define-feature-shortcuts-hydra
hydra-feature-shortcuts/body))
(use-package exec-path-from-shell
:pin "melpa-stable"
:commands (exec-path-from-shell-copy-envs))
(use-package hydra
:pin "melpa-stable")
;; `lv' is a dependency of `hydra'
(add-to-list 'package-pinned-packages '(lv . "melpa-stable"))
(use-package projectile
:ensure t
:commands (projectile-mode)
:defines (projectile-known-projects)
:bind (:map projectile-mode-map ("C-c p" . projectile-command-map))
:init (setq projectile-switch-project-action 'projectile-dired
projectile-completion-system 'helm
projectile-ignored-project-function #'file-remote-p
projectile-create-missing-test-files t
projectile-known-projects-file (expand-file-name "projectile-bookmarks.eld"
emacs-d-userdata)
projectile-cache-file (expand-file-name "projectile.cache"
emacs-d-userdata))
:diminish projectile-mode)
;; * Text editing
(setq sentence-end-double-space t)
(put 'upcase-region 'disabled nil)
(put 'downcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'scroll-left 'disabled nil)
(use-package abbrev
:init (setq save-abbrevs 'silently
abbrev-file-name (expand-file-name "abbrev_defs" emacs-d-userdata))
:diminish abbrev-mode)
(use-package dictionary
:init (setq dictionary-server "dict.org"))
(use-package electric
:commands (electric-quote-mode))
(use-package elec-pair
:commands (electric-pair-mode)
:config (progn
(add-to-list 'electric-pair-pairs '(?“ . ?”))
(add-to-list 'electric-pair-text-pairs '(?“ . ?”))
(add-to-list 'electric-pair-pairs '(?„ . ?“))
(add-to-list 'electric-pair-text-pairs '(?„ . ?“))))
(use-package flyspell
:commands (flyspell-mode turn-on-flyspell)
:config (progn
(unbind-key "C-M-i" flyspell-mode-map)
(unbind-key "C-c $" flyspell-mode-map)))
(use-package key-chord
:ensure t
:commands (key-chord-mode)
:config (progn
(key-chord-define-global "``" "")
(key-chord-define-global "''" "")
(key-chord-define-global ",," "")))
(use-package multiple-cursors
:pin "melpa-stable"
:ensure t
:commands (mc/edit-lines
mc/mark-next-like-this
mc/mark-previous-like-this
mc/mark-all-like-this))
(use-package olivetti
:ensure t
:commands (olivetti-mode)
:preface (progn
(defun turn-on-olivetti-mode ()
"Turn on `olivetti-mode'."
(interactive)
(olivetti-mode 1))
(defun turn-off-olivetti-mode ()
"Turn off `olivetti-mode'."
(interactive)
(olivetti-mode -1)))
:init (setq-default olivetti-body-width 0.618034))
(use-package undo-tree
:ensure t
:commands (global-undo-tree-mode
undo
undo-tree-redo)
:init (setq undo-tree-visualizer-timestamps t
undo-tree-visualizer-diff t
undo-tree-auto-save-history nil
undo-tree-history-directory-alist `(("." . ,(expand-file-name "ebackup/" emacs-d))))
:diminish undo-tree-mode)
(use-package wgrep
:ensure t
:commands (wgrep-finish-edit
wgrep-change-to-wgrep-mode))
(use-package yasnippet
:commands (yas-minor-mode-on
yas-minor-mode
yas-global-mode
yas-reload-all)
:ensure t
:diminish yas-minor-mode
:config (yas-reload-all))
;; * Org
(use-package db-org
:commands (db/check-special-org-files-in-agenda
db/verify-refile-target
db/find-parent-task
db/ensure-running-clock
db/save-current-org-task-to-file
db/org-update-frame-title-with-current-clock
db/org-clock-out
db/org-clock-in-break-task
db/org-clock-in-home-task
db/org-clock-in-work-task
db/show-current-org-task
db/org-remaining-effort-of-current-item
db/org-cmp-remaining-effort
org-dblock-write:db/org-workload-report
endless/org-ispell
db/org-agenda-list-deadlines
db/org-agenda-insert-active-filters
hydra-org-agenda-view/body
db/org-agenda-insert-efforts
db/org-eval-subtree-no-confirm
db/org-timestamp-difference
db/org-capture-code-snippet
hydra-org-clock/body
hydra-org-jump/body
hydra-org-custom/body
db/make-org-capture-frame
db/org-onenote-open
db/org-outlook-open
db/org-rfc-open
db/org-cleanup-continuous-clocks
db/find-csv-in-org
db/org-mark-current-default-task
db/export-diary
db/org-insert-checklist
db/org-copy-body-from-item-to-point
db/org-find-links-to-current-item
db/org-add-link-to-other-item
db/org-add-link-to-current-clock
hydra-org-linking/body
org-dblock-write:db/org-backlinks
db/org-clock-goto-first-open-checkbox
org-password-manager-get-password-by-id))
;; This is to make the byte-compiler happy about setting some variables later on
;; that are defined in those packages.
(use-package org-attach)
(use-package org-id)
(use-package org-goto)
(use-package org
:pin "gnu"
:bind (:map org-mode-map
([remap org-return] . (lambda () (interactive) (org-return :indent)))
([remap org-clock-goto] . db/org-clock-goto-first-open-checkbox))
:autoload (org-get-todo-state
org-entry-get)
:commands (org-return)
:init (progn
(setq org-deadline-warning-days 14
org-read-date-popup-calendar t
org-insert-heading-respect-content t
org-adapt-indentation nil
org-edit-timestamp-down-means-later t
org-archive-location "%s_archive.gpg::"
org-image-actual-width nil
org-imenu-depth 10
org-footnote-section nil
org-log-into-drawer "LOGBOOK"
org-log-reschedule 'note
org-log-redeadline 'note
org-log-refile 'note
org-log-note-clock-out nil
org-log-done 'note
org-clone-delete-id t
org-fold-catch-invisible-edits 'show-and-error
org-M-RET-may-split-line '((default . nil))
org-highlight-latex-and-related '(latex)
org-use-sub-superscripts '{}
org-src-fontify-natively t
org-src-preserve-indentation t
org-src-tab-acts-natively nil
org-ellipsis ""
org-fontify-done-headline nil
org-cycle-separator-lines 0
org-special-ctrl-a/e t
org-highlight-latex-and-related nil
org-attach-store-link-p 'attached
org-attach-auto-tag nil
org-bookmark-names-plist nil
org-goto-interface 'outline-path-completion
org-id-link-to-org-use-id t
org-blank-before-new-entry '((heading . t)
(plain-list-item . t))
org-duration-format '(("y") ("w") ("d") (special . h:mm))
org-treat-S-cursor-todo-selection-as-state-change nil
org-global-properties
'(("Effort_ALL" . "0:00 0:05 0:10 0:15 0:30 0:45 1:00 2:00 3:00 4:00 6:00 8:00"))
org-columns-default-format
"%80ITEM(Task) %10Effort(Effort) %10CLOCKSUM")
;; Keywords and Tags
(setq org-todo-keywords
'((sequence "TODO(t@)" "CONT(n!)" "REFINE(f!)" "|" "DONE(d@)" "CANC(c@)" "MRGD(m@)")
(sequence "GOTO(g@)" "ATTN(a!)" "|" "DONE(d@)")
(sequence "READ(r@)" "CONT(n!)" "|" "DONE(d@)")
(sequence "DELG(e@)" "WAIT(w@)" "HOLD(h@)" "|" "CANC(c@)"))
org-todo-state-tags-triggers
'(("WAIT" ("HOLD") ("WAIT" . t))
("DELG" ("HOLD") ("WAIT" . t))
("HOLD" ("HOLD" . t) ("WAIT"))
(done ("HOLD") ("WAIT"))
("TODO" ("HOLD") ("WAIT") ("DATE") ("READ"))
("READ" ("READ" . t) ("DATE") ("HOLD") ("WAIT"))
("GOTO" ("DATE" . t) ("READ") ("HOLD") ("WAIT"))
("CONT" ("DATE") ("HOLD") ("WAIT") ("READ"))
("REFINE" ("DATE") ("HOLD") ("WAIT") ("READ"))
("ATTN" ("DATE" . t) ("READ") ("HOLD") ("WAIT"))
("" ("HOLD") ("WAIT") ("DATE") ("READ")))
org-tag-alist
'((:startgroup)
("WORK" . ?w)
("HOME" . ?h)
("FUN" . ?f)
("UNTAGGED" . ?u)
(:endgroup)
("NOTE" . ?n)
(:startgroup)
("PERIODIC" . ?p)
("NOP" . ?o)
("TOPIC" . ?t)
("TEMPLATE" . ?l)
("GOAL" . ?g)
(:endgroup))
org-tags-exclude-from-inheritance
'("NOP" "TOPIC" "GOAL")
org-fast-tag-selection-single-key 'expert)
;; Faces
(setq org-todo-keyword-faces
'(("TODO" :foreground "red" :weight normal)
("GOTO" :foreground "red" :weight normal)
("READ" :foreground "red" :weight normal)
("CONT" :foreground "DeepSkyBlue" :weight normal)
("ATTN" :foreground "DeepSkyBlue" :weight normal)
("DONE" :foreground "forest green" :weight normal)
("MRGD" :foreground "dark cyan" :weight normal)
("DELG" :foreground "dark orange" :weight normal)
("REFINE" :foreground "tomato" :weight normal)
("WAIT" :foreground "orange" :weight normal)
("HOLD" :foreground "magenta" :weight normal)
("CANC" :foreground "lime green" :weight normal))
org-priority-faces
'((?A . (:foreground "Red" :weight bold))
(?B . (:foreground "firebrick"))
(?C . (:foreground "tomato"))))
;; Refiling
(setq org-refile-targets '((org-agenda-files . (:maxlevel . 9))
(nil . (:maxlevel . 9))
(db/org-default-notes-file . (:maxlevel . 9)))
org-refile-use-outline-path 'buffer-name
org-refile-use-cache nil
org-refile-allow-creating-parent-nodes 'confirm
org-indirect-buffer-display 'current-window
org-outline-path-complete-in-steps nil
org-refile-target-verify-function 'db/verify-refile-target)
;; Babel
(setq org-babel-load-languages '((shell . t)
(emacs-lisp . t)
(sql . t)))
;; Link shortcuts (some taken from the documentation)
(setq org-link-abbrev-alist
'(("wpen" . "https://en.wikipedia.org/wiki/")
("wpde" . "https://de.wikipedia.org/wiki/")
("ddg" . "https://duckduckgo.com/?q=%s")
("omap" . "https://nominatim.openstreetmap.org/search?q=%s&polygon=1")
("github" . "https://github.com/")
("gitlab" . "https://gitlab.com/"))))
:config (progn
;; Some hooks from text-mode-hook, Org mode does not seem to run those?
(add-hook 'org-mode-hook 'page-break-lines-mode)
(add-hook 'org-mode-hook 'turn-on-auto-fill)
;; Hide drawers by default when cycling items
(add-hook 'org-cycle-hook #'org-cycle-hide-drawers)
;; Statically color links indepedently of whether the file exists or
;; not (we could add different faces to highlight non-existing
;; files, but this turns out to be slow on Windows, and we can use
;; `org-lint' to check this when necessary)
(org-link-set-parameters "file" :face 'org-link)
;; File Apps
(add-to-list 'org-file-apps '(directory . emacs))
(add-to-list 'org-file-apps '("\\.docx?\\'" . default))
(add-to-list 'org-file-apps '("\\.pptx?\\'" . default))
(add-to-list 'org-file-apps '("\\.xlsx?\\'" . default))
(when (eq system-type 'cygwin)
(add-to-list 'org-file-apps '(t . "cygstart %s") t))
;; Custom link types for Windows
(when (eq system-type 'windows-nt)
(org-link-set-parameters "onenote" :follow #'db/org-onenote-open)
(org-link-set-parameters "outlook" :follow #'db/org-outlook-open))
;; Mark some org mode regions to be skipped by ispell
(add-hook 'org-mode-hook #'endless/org-ispell)
;; Link type for RFCs
(org-link-set-parameters "rfc" :follow #'db/org-rfc-open)
;; Some timers
(unless (memq #'org-clock-save
(mapcar #'timer--function timer-list))
(run-with-timer 0 3600 #'org-clock-save))
;; Hack: The default implementation is too slow, because it is
;; parsing all properties of an entry by default. Let’s simplify
;; this to only parse what we are looking for. This makes tag
;; search *much* faster!
(when (version<= org-version "9.5.5")
(with-eval-after-load 'org
(define-advice org-cached-entry-get (:around
(orig-fun pom property)
speed-up-cache-lookup)
(ignore orig-fun)
(if (or (eq t org-use-property-inheritance)
(and (stringp org-use-property-inheritance)
(let ((case-fold-search t))
(string-match-p org-use-property-inheritance property)))
(and (listp org-use-property-inheritance)
(member-ignore-case property org-use-property-inheritance)))
;; Caching is not possible, check it directly.
(org-entry-get pom property 'inherit)
;; This is different in the original implementation
(org-entry-get pom property)))))
;; Fix (Org 9.5.5): the variable `org-time-was-given' dynamically
;; bound to signify when `org-read-date-analyze' has found a hh:mm
;; part; it's only considered, however, when it has previously been
;; bound, resulting in some prior calls of `org-read-date' to ignore
;; the hh:mm part. So let's bind this variable now to make things
;; work.
(defvar org-time-was-given nil)))
(use-package org-cycle
:autoload (org-cycle-hide-drawers)
:init (setq org-cycle-include-plain-lists 'integrate))
(use-package org-lint
:autoload (org-lint-checker-name)
:config (progn
;; Yes, this is ugly, but I would like to get rid of checking for
;; obsolete percentage encoding in URLs without loosing the other
;; checkers.
(setq org-lint--checkers (cl-remove-if #'(lambda (c)
(eq (org-lint-checker-name c)
'percent-encoding-link-escape))
org-lint--checkers))))
;; Drag-and-Drop images into org-mode buffer
(use-package org-download
:commands (org-download-yank
org-download-screenshot
org-download-clipboard)
:init (setq org-download-method 'attach))
;; Extended query language and dynamic blocks
(use-package org-ql
:pin "melpa-stable"
;; XXX: Remove the following as soon as this is fixed in upstream, see
;; https://github.com/alphapapa/org-ql/pull/237
:init (setq org-ql-regexp-part-ts-time
(rx " " (repeat 1 2 digit) ":" (repeat 2 digit)
(optional "-" (repeat 1 2 digit) ":" (repeat 2 digit))))
:commands (org-ql-view
org-ql-search
org-dblock-write:org-ql)
;; `org-ql-search' defines the dblock features of `org-ql' but is not loaded
;; by default, so let's do this here; note that this implicitly loads
;; `org-ql-view' as well.
:config (require 'org-ql-search))
(use-package ol
:init (setq org-link-keep-stored-after-insertion t)
:commands (org-store-link)
:autoload (org-link-set-parameters)
:config (progn
(define-advice org-store-link (:around
(orig-func &rest args)
db/org--push-new-links-to-beginning)
"Ensure that new links in `org-store-link' are always at the beginning."
;; The idea here is to store the list of already known links,
;; stored in `org-stored-links', and call `org-store-link' with an
;; empty value of `org-stored-links'. This way, the link to the
;; current item is store in any case, and we prepend these new
;; values (can be more than one if CUSTOM_ID is set) to the old
;; list of links. We also remove the newly prepended links from
;; the list of already known links.
(let ((org-stored-links--original org-stored-links)
(org-stored-links--new nil)
(org-store-link--return-value nil))
(let ((org-stored-links nil))
;; This is the actual call to `org-store-link', which may (and
;; usually will) update `org-stored-links'. Note that the new
;; value of `org-stored-links' is only available after
;; `org-store-link' as finished, which is why we make two
;; separate calls to `setq' here instead of only one.
(setq org-store-link--return-value (apply orig-func args))
(setq org-stored-links--new org-stored-links))
;; Note: `org-stored-links--new' might still be nil after
;; calling `org-store-link', as under some circumstances (and
;; only when the `interactive?' argument to `org-store-link' is
;; non-nil), `org-store-link' may only return a link and not
;; update `org-stored-links'; in this case, we do not have to
;; touch the original value of `org-stored-links' at all.
(unless (null org-stored-links--new)
(setq org-stored-links (nconc org-stored-links--new
(cl-remove-if #'(lambda (x)
(member x org-stored-links--new))
org-stored-links--original))))
org-store-link--return-value))))
(use-package ol-bbdb
:config (add-to-list 'org-bbdb-anniversary-format-alist
(cons "day-of-death"
#'(lambda (name years suffix)
(format "Day of Death: [[bbdb:%s][%s (%s%s)]]"
name name years suffix)))))
(use-package org-clock
:commands (org-clock-save)
:init (progn
(setq org-clock-history-length 35
org-clock-in-resume t
org-clock-into-drawer t
org-clock-idle-time nil
org-clock-out-remove-zero-time-clocks t
org-clock-out-when-done '("DONE" "CANC" "MRGD" "WAIT" "HOLD")
org-clock-auto-clock-resolution 'when-no-clock-is-running
org-clock-mode-line-total 'auto
org-clock-report-include-clocking-task t
org-clock-in-switch-to-state #'(lambda (_)
(when (not
(and (boundp 'org-capture-mode)
org-capture-mode))
(cond
((member (org-get-todo-state)
(list "TODO" "READ"))
"CONT")
((member (org-get-todo-state)
(list "GOTO"))
"ATTN"))))
org-clock-persist t
org-clock-persist-file (expand-file-name "org-clock-save.el" emacs-d-userdata)
org-clock-persist-query-resume nil
org-clock-ask-before-exiting nil
org-time-stamp-rounding-minutes '(1 1))
;; On Windows, we don't have dbus to show notifications; default to
;; `message' instead
(when on-windows
(setq org-show-notification-handler #'message)))
:config (progn
(org-clock-persistence-insinuate)
(add-hook 'org-clock-in-hook #'db/org-mark-current-default-task)
(add-hook 'org-clock-in-hook #'db/org-update-frame-title-with-current-clock)
;; Clock in default task if no other task is given
(add-hook 'org-clock-out-hook #'db/ensure-running-clock 'append)
;; Communicate the currently clocked in task to the outside world
(add-hook 'org-clock-in-hook #'db/save-current-org-task-to-file)))
;; Agenda
(use-package org-agenda
:commands (org-agenda
org-agenda-redo-all)
:bind (:map org-agenda-mode-map
("i" . org-agenda-clock-in)
("v" . hydra-org-agenda-view/body)
([remap org-clock-goto] . db/org-clock-goto-first-open-checkbox)
([remap org-agenda-redo-all] . org-agenda-redo))
:init (setq org-agenda-include-diary t
org-agenda-span 1
org-agenda-insert-diary-strategy 'top-level
org-agenda-sorting-strategy '((agenda time-up priority-down user-defined-up category-keep)
(todo priority-down user-defined-up category-keep)
(tags priority-down user-defined-up category-keep)
(search priority-down user-defined-up category-keep))
org-agenda-cmp-user-defined #'db/org-cmp-remaining-effort
org-agenda-window-setup 'current-window
org-agenda-restore-windows-after-quit t
org-agenda-compact-blocks nil
org-agenda-todo-ignore-with-date nil
org-agenda-todo-ignore-deadlines nil
org-agenda-todo-ignore-scheduled nil
org-agenda-todo-ignore-timestamp nil
org-agenda-skip-deadline-if-done t
org-agenda-skip-scheduled-if-done t
org-agenda-skip-timestamp-if-done t
org-agenda-skip-scheduled-if-deadline-is-shown 'not-today
org-agenda-tags-todo-honor-ignore-options t
org-agenda-start-with-log-mode nil
org-agenda-log-mode-items '(closed)
org-agenda-remove-tags nil
org-agenda-sticky nil
org-agenda-inhibit-startup nil
org-agenda-tags-todo-honor-ignore-options t
org-agenda-dim-blocked-tasks nil
org-enforce-todo-checkbox-dependencies t
org-enforce-todo-dependencies t
org-agenda-use-time-grid t
org-agenda-persistent-filter t
org-agenda-search-headline-for-time nil
org-agenda-search-view-always-boolean t
org-agenda-clock-consistency-checks
'(:max-duration 9999999
:min-duration 0
:max-gap 0
:gap-ok-around nil
:default-face ((:background "DarkRed") (:foreground "white"))
:overlap-face nil :gap-face nil :no-end-time-face nil
:long-face nil :short-face nil)
org-agenda-clockreport-parameter-plist
'(:link t :maxlevel 4 :compact t :narrow 60 :fileskip0 t)
;; Note: projects scheduled in the future are not considered
;; stuck, even though they are projects by itself; the rationale
;; here is that projects that have an explicit SCHEDULED entry
;; should not be considered before this date is due.
org-stuck-projects
'("+TODO=\"\"-DATE-HOLD-NOTE-WAIT-NOP-TOPIC-SOMEWHEN-TEMPLATE-SCHEDULED>=\"<+0d>\""
("CONT" "TODO" "READ" "WAIT" "GOTO" "DELG" "ATTN")
()
"")
org-agenda-prefix-format
'((agenda . " %11s%?-12t%-4(db/org-remaining-effort-of-current-item) ")
(todo . " %-8c%-4(db/org-remaining-effort-of-current-item) ")
(tags . " %-8c%-4(db/org-remaining-effort-of-current-item) ")
(search . " %-8c%-4(db/org-remaining-effort-of-current-item) "))
org-agenda-custom-commands
`(("A" "Main Agenda"
((agenda
""
((org-agenda-entry-types '(:timestamp :sexp :scheduled :deadline))
(org-deadline-warning-days 0)))
(db/org-agenda-list-deadlines
""
((org-agenda-overriding-header "Deadlines")
(org-agenda-sorting-strategy '(deadline-up priority-down))
(org-deadline-warning-days 30)))
(tags-todo "TODO={CONT\\|ATTN}-HOLD"
((org-agenda-overriding-header "Things to do next (Task shortlist and WIP, TODO ∈ {CONT,ATTN}, not scheduled)")
(org-agenda-todo-ignore-scheduled 'all)
(org-agenda-todo-ignore-timestamp 0)))
(tags-todo "TODO<>\"CONT\"-HOLD-SOMEWHEN-DATE-WAIT-TEMPLATE"
((org-agenda-overriding-header "Task Backlog (not WIP, not scheduled)")
(org-agenda-todo-ignore-scheduled 'all)
(org-tags-match-list-sublevels t)))))
("B" "Backlog"
((tags-todo "-HOLD-SOMEWHEN-DATE-PERIODIC-TEMPLATE"
((org-agenda-overriding-header "Backlog: Actionable items (no periodic tasks; includes waiting-fors)")
(org-tags-match-list-sublevels t)))
(tags "TODO=\"\"-HOLD-SOMEWHEN-DATE-PERIODIC-NOTE-NOP-TOPIC-TEMPLATE"
((org-agenda-overriding-header "Backlog: Complex tasks (i.e., goals)")
(org-tags-match-list-sublevels t)))))
("C" "Checks"
((tags "TODO=\"\"-HOLD-SOMEWHEN-DATE-PERIODIC-NOTE-NOP-TOPIC-TEMPLATE-GOAL"
((org-agenda-overriding-header "Goals (i.e., complex tasks) not marked with GOAL")))
;; Note: checking whether any SCHEDULED is behind a
;; DEADLINE is not necessary, as items will appear on the
;; deadline view anyway.
))
("U" "Unsupervised (Waiting, Missed Appointments, Hold)"
((tags "WAIT-TODO={DONE\\|CANC\\|MRGD}-HOLD-SOMEWHEN-SCHEDULED<>\"\""
((org-agenda-overriding-header "Waiting For List")))
(tags-todo "DATE-TIMESTAMP>=\"<today>\""
((org-agenda-overriding-header "Missed appointments (DATEs with timestamp in the past)")))
(tags "HOLD-TODO={DONE\\|CANC\\|MRGD}-SOMEWHEN-SCHEDULED<>\"\""
((org-agenda-overriding-header "Tasks on Hold")))))
("S" "Somewhen (Do if nothing else to do, i.e., personal backlog)"
((tags "TAGS={SOMEWHEN}+TODO=\"\"-NOP-TOPIC-PERIODIC-DATE-SCHEDULED>=\"<today>\""
((org-agenda-overriding-header "Open Tasks to do SOMEWHEN (no TODO keyword, no PERIODIC, no DATE, no now or future SCHEDULED)")))
(tags-todo "SOMEWHEN-HOLD"
((org-agenda-overriding-header "Things To Do SOMEWHEN")
(org-agenda-todo-ignore-with-date t)
(org-tags-match-list-sublevels nil)))))
("P" "Current Projects and Topics"
((stuck ""
((org-agenda-overriding-header "Stuck Complex Tasks")))
(tags "TAGS={NOTE}-TODO={CANC\\|DONE\\|MRGD}-HOLD-NOP-SOMEWHEN-SCHEDULED>=\"<today>\""
((org-agenda-overriding-header "Project Notes (items explicitly tagged with NOTE but not NOP)")))
(tags "TAGS={TOPIC}-TODO={DONE\\|CANC\\|MRGD}-SOMEWHEN-SCHEDULED>=\"<today>\"-HOLD-WAIT"
((org-agenda-overriding-header "Topics (items directly tagged with TOPIC)")))
(tags "TAGS={PERIODIC}-TODO={DONE\\|CANC\\|MRGD}-HOLD-SCHEDULED>=\"<today>\"-HOLD-WAIT"
((org-agenda-overriding-header "Periodic Projects (PERIODIC, not scheduled in the future, not done, not on hold)")))))
("W" "Weekly Review"
((agenda ""
((org-agenda-span 7)
(org-agenda-dim-blocked-tasks nil)
(org-agenda-skip-deadline-prewarning-if-scheduled t)))))))
:config (progn
;; Avoid important buffers to end up in `org-agenda-new-buffers’ by
;; opening them manually
(mapc #'find-file-noselect org-agenda-files)
;; Check that all expected agenda files are being displayed.
(advice-add 'org-agenda
:before #'db/check-special-org-files-in-agenda)
(add-hook 'org-agenda-mode-hook #'hl-line-mode 'append)
;; Show daily efforts directly in the agenda
(add-hook 'org-agenda-finalize-hook #'db/org-agenda-insert-efforts)
;; Prominently display active filters on top of agenda view; also
;; update current agenda view when updating filters to make sure our
;; display is always correct.
(add-hook 'org-agenda-finalize-hook #'db/org-agenda-insert-active-filters)
(add-hook 'org-agenda-filter-hook #'org-agenda-redo-all)
(define-advice org-agenda-redo (:around
(actual-agenda-redo &rest r)
inhibit-recentering)
"Try to avoid recentering the window when redoing the Org agenda buffer."
(let ((old-recenter (symbol-function 'recenter)))
(fset 'recenter 'identity)
(unwind-protect
(apply actual-agenda-redo r)
(fset 'recenter old-recenter))))))
;; Capturing
(use-package org-capture
:commands (org-capture)
:init (setq org-capture-use-agenda-date nil
org-capture-bookmark nil
org-capture-templates
`(("t" "Simple Task"
entry
(file db/org-default-refile-file)
,(concat "* TODO [#B] %^{What}\n"
":PROPERTIES:\n:CREATED: %U\n:END:\n"
"\nVia %a.\n\n"
"%?")
:empty-lines-before 1
:empty-lines-after 0)
("g" "Record new goal"
entry
(file db/org-default-refile-file)
,(concat "* %^{Description} (%^{Ticket Number}) :GOAL:\n"
":PROPERTIES:\n:CREATED: %U\n:END:\n"
"\n%?"))
("h" "Headline (generic Org item)"
entry
(file db/org-default-refile-file)
,(concat "* [#B] %^{What}\n"
":PROPERTIES:\n:CREATED: %U\n:END:\n"
"%a\n"
"%?")
:empty-lines-before 1
:empty-lines-after 0)
("n" "Note"
entry
(file db/org-default-refile-file)
"* Note: %^{About} :NOTE:\n:PROPERTIES:\n:CREATED: %U\n:END:\n\n%?"
:empty-lines-before 1
:empty-lines-after 0)
("d" "Date"
entry
(file db/org-default-refile-file)
"* GOTO [#B] %^{What} :DATE:\n%^{When}t\n%a%?"
:empty-lines-before 1
:empty-lines-after 0)
("i" "Interruptions"
entry
(file db/org-default-refile-file)
,(concat "* DONE [#B] %^{What}\nCLOSED: %U\n"
":PROPERTIES:\n:CREATED: %U\n:END:\n"
"\nInterrupted %(with-temp-buffer (db/org-add-link-to-current-clock) (string-trim (buffer-string)))\n\n%?")
:clock-in t
:clock-resume t
:empty-lines-before 1
:empty-lines-after 0)
("r" "Respond"
entry
(file db/org-default-refile-file)
,(concat "* TODO [#B] Reply: %:subject (%:from) :EMAIL:\n"
":PROPERTIES:\n:CREATED: %U\n:END:\n"
"\n%a\n%?")
:empty-lines-before 1
:empty-lines-after 0)))
:config (progn
;; disable usage of helm for `org-capture'
(with-eval-after-load 'helm-mode
(defvar helm-completing-read-handlers-alist) ; for the byte compiler
(add-to-list 'helm-completing-read-handlers-alist
'(org-capture . nil)))))
;; Babel
(use-package ob-core
:init (setq org-export-use-babel t
org-babel-default-header-args '((:session . "none")
(:results . "output code replace")
(:exports . "both")
(:cache . "no")
(:noweb . "no")
(:hlines . "no")
(:tangle . "no")
(:eval . "never-export"))))
(use-package ob-sql
:config (progn
;; XXX: maybe merge this into the advice for `org-babel-execute:sql'?
(define-advice org-babel-sql-dbstring-oracle (:around
(orig-fun host port user password database)
ask-for-password)
"Ask for PASSWORD if not given, and call ORIG-FUN with arguments afterwards."
(cond
((not (or (and user database host port)
(and user database)))
(user-error "Insufficient login credentials given, aborting"))
(password
(funcall orig-fun host port user password database))
(t
(funcall orig-fun
host port user
(password-read (format "Password for %s@%s: " user database))
database))))
(define-advice org-babel-execute:sql (:around
(orig-fun body params)
retrieve-password-by-function)
"Allow to set :dbpassword by an ID of an Org items having the PASSWORD property."
(let* ((dbpassword-id (cdr (assq :dbpassword-by-id params)))
(params params))
(when dbpassword-id
(setq params (cons (cons :dbpassword
(let* ((pom (or (org-id-find dbpassword-id 'marker)
(user-error "Cannot find ID %s" dbpassword-id))))
(or (org-entry-get pom "PASSWORD")
(user-error "No PASSWORD property at ID %s" dbpassword-id))))
params)))
(funcall orig-fun body params)))))
;; Exporting
(use-package ox-icalendar
:commands (org-icalendar-combine-agenda-files)
:init (setq org-icalendar-include-body nil
org-icalendar-store-UID t
org-icalendar-use-deadline nil
org-icalendar-use-scheduled nil
org-icalendar-include-todo nil
org-icalendar-exclude-tags '("NO_EXPORT")))
(use-package ox
:init (setq org-export-with-broken-links 'mark
org-export-with-sub-superscripts '{}
org-export-with-author nil
org-export-with-date nil
org-export-with-toc nil
org-export-with-archived-trees nil
org-export-with-tags t
org-export-with-priority nil
org-export-with-creator nil
org-export-with-entities t
org-export-with-special-strings t
org-export-with-todo-keywords nil
org-export-exclude-tags '("NO_EXPORT"))
:config (with-demoted-errors "Cannot load package: %s"
(require 'ox-md)
(require 'ox-pandoc)))
(use-package ox-html
:init (setq org-html-postamble nil))
(use-package ox-pandoc
:init (setq org-pandoc-options-for-docx '((standalone . t))))
;; Extra Org packages
(use-package org-tree-slide
:commands (org-tree-slide-mode)
;; Configuration from https://protesilaos.com/dotemacs/
:init (setq org-tree-slide-breadcrumbs ""
org-tree-slide-header t
org-tree-slide-slide-in-effect nil
org-tree-slide-heading-emphasis nil
org-tree-slide-cursor-init t
org-tree-slide-modeline-display nil
org-tree-slide-skip-done nil
org-tree-slide-skip-comments t
org-tree-slide-fold-subtrees-skipped t
org-tree-slide-skip-outline-level 8
org-tree-slide-never-touch-face t
org-tree-slide-activate-message (propertize "Presentation mode ON" 'face 'success)
org-tree-slide-deactivate-message (propertize "Presentation mode OFF" 'face 'error))
:bind (:map org-tree-slide-mode-map
("<C-down>" . org-tree-slide-display-header-toggle)
("<C-right>" . org-tree-slide-move-next-tree)
("<C-left>" . org-tree-slide-move-previous-tree))
:hook ((org-tree-slide-play . turn-on-olivetti-mode)
(org-tree-slide-stop . turn-off-olivetti-mode)))
(use-package org-roam
:init (progn
(add-to-list 'display-buffer-alist
'("\\*org-roam\\*"
(display-buffer-in-side-window)
(side . right)
(slot . 0)
(window-width . 0.33)
(window-parameters . ((no-other-window . t)
(no-delete-other-windows . t))))))
:commands (org-roam-node-insert
org-roam-node-find
org-roam-capture
org-roam-buffer-toggle)
:custom ((org-roam-directory "~/Documents/zettelkasten/")
(org-roam-db-location "~/Documents/zettelkasten/org-roam.db")
(org-roam-completion-everywhere t)
(org-roam-mode-sections (list #'org-roam-backlinks-section
#'org-roam-reflinks-section
#'org-roam-unlinked-references-section)))
:config (progn
(org-roam-db-autosync-mode)
(define-advice org-roam-buffer-render-contents
(:around (orig-func) show-buffer-in-selected-window)
"Show Org roam buffer in selected window before updating its content.
See https://github.com/org-roam/org-roam/issues/1586, and in particular
https://github.com/org-roam/org-roam/issues/1586#issuecomment-1412250226.
Note that this workaround is incomplete, as explained in this comment."
(let ((org-roam-buffer-window (display-buffer (current-buffer))))
;; When we cannot display the buffer, there is also no need to
;; update it, no?
(when org-roam-buffer-window
(with-selected-window org-roam-buffer-window
(funcall orig-func)))))))
(with-eval-after-load 'org
;; I would like to include this keybinding in the :bind definition of `org'
;; above, but doing so seems to attach an autoload to `org-roam-buffer-toggle'
;; on org.el; the latter inclusion of `org-roam-buffer-toggle' in the
;; :commands list of `org-roam' does not seem to overwrite this.
(bind-key "C-c n l" #'org-roam-buffer-toggle org-mode-map))
;; * General Programming
;; Configuration that pertains to programming in general, without referring to
;; any programming language in particular.
(use-package ediff
:init (setq ediff-diff-options "-w"
ediff-window-setup-function 'ediff-setup-windows-plain
ediff-split-window-function 'split-window-horizontally
ediff-show-clashes-only t)
:config (add-hook 'ediff-after-quit-hook-internal 'winner-undo))
;; Inspired by
;; https://andreyor.st/posts/2023-09-09-migrating-from-lsp-mode-to-eglot.
(use-package eglot
:ensure t
:init (setq eglot-autoshutdown t
eglot-extend-to-xref nil
eglot-stay-out-of '(yasnippet)))
(use-package flycheck
:ensure t
:commands (global-flycheck-mode flycheck-mode)
:init (setq flycheck-emacs-lisp-load-path 'inherit
;; Hack: inherit `byte-compile-warnings' setting in Emacs
;; subprocess; this value is only set once upon starting Emacs, so
;; make sure to manually update this setting when updating
;; `byte-compile-warnings'.
flycheck-emacs-args `("-Q"
"--batch"
"--eval" ,(message "(setq byte-compile-warnings (quote %s))"
byte-compile-warnings))))
(use-package git-commit
:commands (global-git-commit-mode)
:ensure t
:init (setq git-commit-style-convention-checks '(non-empty-second-line
overlong-summary-line)
git-commit-known-pseudo-headers '("Signed-off-by"
"Acked-by"
"Modified-by"
"Cc"
"Suggested-by"
"Reported-by"
"Tested-by"
"Reviewed-by"))
:config (add-hook 'git-commit-mode-hook
#'(lambda ()
(setq fill-column 72))))
(use-package highlight-indentation
:commands highlight-indentation-mode)
(use-package iedit
:ensure t
:commands (iedit-mode))
;; See https://andreyor.st/posts/2023-09-09-migrating-from-lsp-mode-to-eglot/
;; for where some of the configuration for `lsp-mode' is coming from.
(use-package lsp-mode
:ensure t
:init (setq lsp-keymap-prefix "C-c C-l"
lsp-session-file (expand-file-name ".lsp-session" emacs-d-userdata)
lsp-use-plists t ; not quite sure whether this is a good
; idea, but plists are easier to read …
lsp-log-io t
lsp-keep-workspace-alive nil
lsp-idle-delay 0.5
lsp-auto-configure t
lsp-headerline-breadcrumb-enable nil
lsp-signature-doc-lines 1)
:hook ((lsp-mode . lsp-diagnostics-mode)
(lsp-mode . lsp-completion-mode))
:commands (lsp lsp-deferred))
(use-package lsp-ui
:ensure t
:commands (lsp-ui-mode))
(use-package magit
:ensure t
:commands (magit-status
magit-list-repositories
db/sync-magit-repos-from-projectile)
:init (progn
(setq magit-diff-refine-hunk nil
magit-commit-show-diff nil)
(when on-windows
;; Experimental: on Windows, do not refresh magit-status-buffers
;; that are not selected, to increase performance.
(setq magit-refresh-status-buffer nil)))
:config (progn
(when (fboundp 'global-magit-file-mode)
(global-magit-file-mode -1))
(global-git-commit-mode 1)
(defun db/sync-magit-repos-from-projectile ()
"Update repositories known to magit from projectile's."
(interactive)
(eval-when-compile ; to silence the byte compiler
(require 'projectile))
(setq magit-repository-directories
(->> projectile-known-projects
(--filter (and (not (file-remote-p it))
(file-exists-p (concat it "/.git"))))
(--map (cons it 0)))))
(db/sync-magit-repos-from-projectile)))
(use-package page-break-lines
:pin "melpa-stable"
:commands (page-break-lines-mode)
:diminish page-break-lines-mode)
(use-package transient
:init (setq transient-levels-file (expand-file-name "transient/levels.el" emacs-d-userdata)
transient-values-file (expand-file-name "transient/values.el" emacs-d-userdata)
transient-history-file (expand-file-name "transient/history.el" emacs-d-userdata)))
;; * Mail
(use-package db-mail
:commands (db/smtpmail-send-it
db/public-key
db/encryption-possible-p
db/message-recipients
db/signencrypt-message-when-possible
db/gnus-save-newsrc-with-whitespace-1
db/gnus-summary-open-Link
db/gnus-html-mime-part-to-org
db/gnus-demon-scan-news-on-level-2))
(use-package bbdb
:ensure t
:commands (bbdb-search-name
bbdb-initialize
bbdb-mua-auto-update-init
bbdb-save)
:init (setq bbdb-completion-display-record nil
bbdb-complete-mail-allow-cycling t
bbdb-mua-auto-action 'query
bbdb-default-country "Germany")
:config (progn
(add-hook 'message-setup-hook 'bbdb-mail-aliases)
(add-hook 'mail-setup-hook 'bbdb-mail-aliases)
(run-with-timer 0 3600 #'bbdb-save)))
;; Gnus package configuration
(use-package gnus
:commands (gnus)
:config (progn
(require 'db-mail)
(with-demoted-errors "Setting up BBDB failed: %s"
(bbdb-initialize 'gnus 'message)
(bbdb-mua-auto-update-init 'message))
;; Ensure that whenever we compose new mail, it will use the correct
;; posting style. This is ensured by setting ARG of
;; `gnus-group-mail’ to 1 to let it query the user for a group.
(defadvice gnus-group-mail (before inhibit-no-argument activate)
(unless (ad-get-arg 0)
(ad-set-arg 0 1)))
(remove-hook 'gnus-mark-article-hook
'gnus-summary-mark-read-and-unread-as-read)
(add-hook 'gnus-mark-article-hook 'gnus-summary-mark-unread-as-read)
;; Quit Gnus gracefully when exiting Emacs
(add-hook 'kill-emacs-hook #'(lambda ()
(interactive)
(when (get-buffer "*Group*")
(gnus-group-exit))))
;; Don’t quit summary buffer when pressing `q’
(bind-key "q" #'gnus-summary-expand-window gnus-article-mode-map)
;; Show topics in group buffer
(add-hook 'gnus-group-mode-hook 'gnus-topic-mode)
;; We need to do some magic as otherwise the agent does not delete
;; articles from its .overview when we move them around. Thus we
;; mark articles as expireable when they have been moved to another
;; group.
(defadvice gnus-summary-move-article (around
no-cancel-mark
(&optional n to-newsgroup
select-method action)
activate)
(let ((articles (gnus-summary-work-articles n))
(return ad-do-it))
(when (or (null action)
(eq action 'move))
(dolist (article articles)
(gnus-summary-mark-article article gnus-expirable-mark)))
return))
;; Increase score of group after reading it
(add-hook 'gnus-summary-exit-hook
'gnus-summary-bubble-group)
;; Use Gnus’ registry; doing this too early conflicts with `gnus'
;; calling `gnus-shutdown', which in turn calls
;; `gnus-registry-clear', leaving us with an empty registry upon
;; startup. So let's call this initialization right before startup,
;; that should be fine.
(add-hook 'gnus-before-startup-hook
#'gnus-registry-initialize)
;; Automatic encryption if all necessary keys are present
(add-hook 'gnus-message-setup-hook
#'db/signencrypt-message-when-possible)
;; Do some pretty printing before saving the newsrc file
(add-hook 'gnus-save-quick-newsrc-hook
#'db/gnus-save-newsrc-with-whitespace-1)
;; Automatically scan for new news
(gnus-demon-add-handler 'db/gnus-demon-scan-news-on-level-2 5 5)
;; Automatically expire groups on idle
(gnus-demon-add-handler 'gnus-group-expire-all-groups 10 5)
;; Visit group under point and immediately close it; this updates
;; gnus’ registry as a side-effect
(bind-key "v u"
#'(lambda ()
(interactive)
(save-mark-and-excursion
(when (gnus-topic-select-group)
(gnus-summary-exit))))
gnus-group-mode-map)
;; Toggle visibility of News group
(bind-key "v c"
#'(lambda ()
(interactive)
(save-mark-and-excursion
(gnus-topic-jump-to-topic "News")
(gnus-topic-read-group)))
gnus-group-mode-map)
(bind-key "C-<return>" #'db/gnus-summary-open-Link gnus-summary-mode-map)
(bind-key "C-<return>" #'db/gnus-summary-open-Link gnus-article-mode-map)))
(use-package gnus-topic
:commands (gnus-topic-select-group
gnus-topic-jump-to-topic
gnus-topic-read-group))
(use-package gnus-demon
:autoload (gnus-demon-add-handler))
(use-package gnus-group
:commands (gnus-group-exit))
;; General Gnus configuration
(use-package nntp)
(use-package nnml)
(use-package message)
(use-package gnus-msg)
(use-package gnus-async)
(setq gnus-init-file (expand-file-name "gnus.el" emacs-d)
gnus-home-directory (expand-file-name "~/.config/gnus-news")
gnus-directory gnus-home-directory
gnus-kill-files-directory gnus-directory
gnus-startup-file (expand-file-name "gnus-newsrc" emacs-d-userdata)
gnus-cache-directory (expand-file-name "cache/" gnus-directory)
gnus-verbose 6
gnus-dbus-close-on-sleep t
message-directory (expand-file-name "mail/" gnus-directory)
nnmail-message-id-cache-file (expand-file-name ".nnmail-cache" gnus-directory)
nnml-directory message-directory
mail-sources '((file))
mail-source-delete-incoming t
nntp-nov-is-evil t
nntp-connection-timeout nil
gnus-asynchronous t
gnus-save-killed-list nil
gnus-save-newsrc-file nil
gnus-read-newsrc-file nil
gnus-check-new-newsgroups nil
gnus-use-cache 'passive
gnus-read-active-file 'some
gnus-subscribe-newsgroup-method 'gnus-subscribe-killed
gnus-group-list-inactive-groups t
gnus-suppress-duplicates nil
gnus-large-newsgroup 500
nnmail-expiry-wait 7
nnmail-cache-accepted-message-ids t
gnus-use-full-window nil
gnus-always-force-window-configuration t
gnus-select-method '(nnnil "")
gnus-refer-article-method 'current
message-citation-line-function
#'(lambda ()
(when message-reply-headers
(insert "ghItlhpu' "
(mail-header-from message-reply-headers)
":")
(newline))))
;; Gnus Appearence
(use-package gnus-sum
:commands (gnus-summary-exit
gnus-summary-expand-window)
:init (setq gnus-group-line-format "%S%p%P%5y(%2i):%B%(%s:%G%)\n"
gnus-build-sparse-threads 'some
gnus-summary-next-group-on-exit nil
gnus-fetch-old-headers nil
gnus-auto-select-first nil
gnus-auto-select-next nil
gnus-summary-line-format "%U%O%R%6k %(%&user-date; %-13,13f %B%s%)\n"
gnus-thread-sort-functions '(gnus-thread-sort-by-most-recent-date)
gnus-subthread-sort-functions '(gnus-thread-sort-by-date)
gnus-thread-hide-subtree t
gnus-user-date-format-alist '((t . "%Y-%m-%d %H:%M"))
gnus-sum-thread-tree-indent " "
gnus-sum-thread-tree-root ""
gnus-sum-thread-tree-false-root ""
gnus-sum-thread-tree-single-indent ""
gnus-sum-thread-tree-single-leaf "╰► "
gnus-sum-thread-tree-leaf-with-other "├► "
gnus-sum-thread-tree-vertical ""
gnus-summary-thread-gathering-function 'gnus-gather-threads-by-references
;; New mark symbols (seen here:
;; `https://github.com/cofi/dotfiles/blob/master/gnus.el')
gnus-ancient-mark ?✓
;; gnus-cached-mark ?☍
gnus-canceled-mark ?↗
gnus-del-mark ?✗
;; gnus-dormant-mark ?⚐
gnus-expirable-mark ?♻
gnus-forwarded-mark ?↪
;; gnus-killed-mark ?☠
;; gnus-process-mark ?⚙
gnus-read-mark ?✓
gnus-recent-mark ?✩
gnus-replied-mark ?↺
gnus-unread-mark ?✉
;; gnus-unseen-mark ?★
;; gnus-ticked-mark ?⚑
))
(use-package gnus-art
:init (setq gnus-ignored-mime-types '("text/x-vcard")
gnus-inhibit-mime-unbuttonizing nil
gnus-buttonized-mime-types '("multipart/signed" "multipart/encrypted")
gnus-inhibit-images t
gnus-blocked-images ".*"
gnus-visible-headers (regexp-opt '("From:"
"Newsgroups:"
"Subject:"
"Date:"
"Followup-To:"
"Reply-To:"
"Organization:"
"Summary:"
"Keywords:"
"Mail-Copies-To:"
"To:"
"Cc:"
"BCC:"
"X-Newsreader:"
"X-Mailer:"
"X-Sent:"
"Posted-To:"
"Mail-Copies-To:"
"Apparently-To:"
"Gnus-Warning:"
"Resent-From:"
"gpg-key-ID:"
"fingerprint:"
"X-Jabber-ID:"
"User-Agent:"))
gnus-treat-hide-boring-headers 'head
gnus-treat-strip-multiple-blank-lines nil
gnus-treat-display-smileys t
gnus-treat-emphasize 'head
gnus-treat-unsplit-urls t))
;; Adaptive Scoring
(use-package gnus-score
:init (setq gnus-use-scoring t
gnus-use-adaptive-scoring '(word line)
gnus-summary-mark-below nil
gnus-adaptive-word-length-limit 5
gnus-adaptive-word-no-group-words t
gnus-default-adaptive-score-alist
'((gnus-unread-mark)
(gnus-ticked-mark (from 4))
(gnus-dormant-mark (from 5))
(gnus-del-mark (from -4) (subject -1))
(gnus-read-mark (from 4) (subject 2))
(gnus-expirable-mark (from -1) (subject -1))
(gnus-killed-mark (from -1) (subject -3))
(gnus-kill-file-mark)
(gnus-ancient-mark)
(gnus-low-score-mark)
(gnus-catchup-mark (from -1) (subject -1)))
gnus-summary-mark-below nil))
;; Gnus Registry
(use-package gnus-registry
:commands (gnus-registry-split-fancy-with-parent
gnus-registry-initialize)
:init (setq gnus-registry-split-strategy 'majority
gnus-registry-ignored-groups '(("^nntp" t)
("^nnfolder" t)
("^nnir" t)
("^nnmaildir" t)
("INBOX$" t))
gnus-registry-max-entries 5000
gnus-registry-track-extra '(sender subject recipient)
gnus-registry-cache-file (expand-file-name "gnus.registry.eioioi"
emacs-d)))
;; MIME decoding
(use-package mm-decode
:init (setq mm-text-html-renderer 'shr
mm-discouraged-alternatives '("text/richtext")
mm-decrypt-option 'known
mm-verify-option 'known)
:config (progn
(setq mm-automatic-display (-difference mm-automatic-display
'("text/enriched"
"text/richtext")))
;; Automatically show PGP data inline
(add-to-list 'mm-inlined-types "application/pgp$")
(add-to-list 'mm-inline-media-tests
'("application/pgp$" mm-inline-text identity))
(add-to-list 'mm-automatic-display "application/pgp$")
;; When copying MIME buffers, we are looking for the start of the
;; header by searching ^\n; however, if we received mail from
;; Outlook, there's an ^\r\n seperating header and body, which is
;; not found by `mm-copy-to-buffer', leaving the target buffer empty
;; and the mail as well. Redefining `mm-copy-to-buffer' to also
;; search for ^\r\n might help.
(define-advice mm-copy-to-buffer (:around (orig-fun) also-consider-crlf)
"Overwrite `mm-copy-to-buffer' to allow for CRLF in addition to LF."
(ignore orig-fun)
(let ((obuf (current-buffer))
(mb enable-multibyte-characters)
beg)
(goto-char (point-min))
;; The following regex has been extended by \r? .
(search-forward-regexp "^\r?\n" nil 'move) ;; There might be no body.
(setq beg (point))
(with-current-buffer
(generate-new-buffer " *mm*")
;; Preserve the data's unibyteness (for url-insert-file-contents).
(set-buffer-multibyte mb)
(insert-buffer-substring obuf beg)
(current-buffer))))))
(setq message-forward-as-mime nil)
;; MIME creation; signing, and encryption
(setq mml-attach-file-at-the-end t)
(use-package mm-encode
:init (setq mm-encrypt-option nil
mm-sign-option nil))
(setq mml-secure-openpgp-encrypt-to-self t
mml-secure-smime-encrypt-to-self t
mml2015-display-key-image nil
gnus-message-replysign t
gnus-message-replyencrypt t
gnus-message-replysignencrypted t
mml-secure-cache-passphrase nil
mml-secure-openpgp-sign-with-sender t
mml-secure-smime-sign-with-sender t)
(use-package mml-smime
:init (setq mml-smime-use 'epg)
:config (progn
;; Outlook seems to expect \r\n in PKCS#7 encrypted containers, but
;; Gnus is only sending \n; so let's artificially replace \n by \r\n
;; after, well, signing? But only if we are encrypting with S/MIME,
;; as otherwise the search for \n\n in `message-encode-message-body'
;; will break. However, I think this is not the right way to do it
;; … isn't there some way to specify the encoding of the the
;; relevant MML buffers to be some nasty cp1252 or something?
;; A previous version of this worked, but the following has not been
;; tested with Outlook proper.
(define-advice mml-smime-epg-sign (:after (cont) add-crlf-when-pkcs7)
"If CONT signifies encryption with smime, replace all \n with \r\n."
(when (and (eq (car cont) 'part)
(string= "smime" (or (cdr (assq 'encrypt cont)) "")))
(db/convert-lf-to-crlf-in-buffer)))))
;; Archiving
;; We store messages in the current group, so there is
;; no need to use Gnus’ archiving method
(setq gnus-message-archive-method nil
gnus-update-message-archive-method t
gnus-message-archive-group nil
gnus-gcc-mark-as-read t)
;; Searching
(use-package gnus-search
:init (setq gnus-search-default-engines '((nnimap . gnus-search-imap))
gnus-search-use-parsed-queries t))
;; Agents
(use-package gnus-agent
:init (setq gnus-agent-mark-unread-after-downloaded nil
gnus-agent-synchronize-flags t
gnus-agent-go-online t))
;; Sending mail
(setq message-send-mail-function #'db/smtpmail-send-it
send-mail-function #'db/smtpmail-send-it
mail-user-agent 'gnus-user-agent)
(use-package smtpmail
:init (setq smtpmail-stream-type 'starttls
smtpmail-smtp-service 587
smtpmail-debug-info t))
;; * Crypto
(use-package nsm
:init (setq network-security-level 'high
nsm-save-host-names t
nsm-settings-file (expand-file-name "network-security.data" emacs-d-userdata))
:config (advice-add 'nsm-write-settings
:before #'db/sort-nsm-permanent-settings))
(use-package gnutls
:init (setq gnutls-log-level 0 ; too noisy otherwise
gnutls-min-prime-bits 1024
gnutls-verify-error t))
(use-package epg
:init (setq epg-debug t
epg-gpg-program "gpg"))
(use-package org-password-manager
:commands (org-password-manager-get-username
org-password-manager-get-password))
;; * Appearance
(setq-default cursor-type 'bar
cursor-in-non-selected-windows nil
font-lock-maximum-decoration '((t . t)))
(setq mode-line-format '((ace-window-display-mode
(:eval
(window-parameter
(selected-window)
'ace-window-path)))
"%e"
mode-line-front-space
mode-line-position
mode-line-mule-info
mode-line-client
mode-line-modified
mode-line-remote
mode-line-buffer-identification
mode-line-modes
(vc-mode vc-mode)
mode-line-misc-info
mode-line-end-spaces))
(use-package solarized-theme
:ensure t
:init (setq solarized-use-less-bold t
solarized-emphasize-indicators t
solarized-use-variable-pitch nil))
(use-package solarized-dark-theme
:defer t
:config (progn
(setq custom--inhibit-theme-enable nil)))
(use-package smart-mode-line
:ensure t
:init (setq sml/mode-width 'full
sml/name-width 30)
:commands (sml/setup))
(use-package minions
:ensure t
:commands (minions-mode)
:init (setq minions-mode-line-lighter ""))
(use-package moody
:ensure t
:commands (moody-replace-mode-line-buffer-identification
moody-replace-vc-mode))
(use-package smiley
:init (setq smiley-style 'emoji))
;; * File Handling
(setq large-file-warning-threshold 10000000
delete-by-moving-to-trash t)
;; Backups and Autosave
(defvar backup-dir (expand-file-name "ebackup/" emacs-d))
(defvar autosave-dir (expand-file-name "eautosave/" emacs-d))
(setq make-backup-files t
backup-directory-alist (list (cons ".*" backup-dir))
auto-save-list-file-prefix autosave-dir
auto-save-file-name-transforms `((".*" ,autosave-dir t))
version-control t
kept-old-versions 2
kept-new-versions 4
delete-old-versions t
vc-make-backup-files t)
(use-package dired-aux
:defines (dired-create-destination-dirs
dired-vc-rename-file))
(use-package dired-x
:defines (dired-omit-files)
:init (setq dired-x-hands-off-my-keys t))
(use-package wdired
:defines (wdired-create-parent-directories
wdired-allow-to-change-permissions))
(use-package dired
:commands (dired-jump
dired-jump-other-window)
:bind (:map dired-mode-map
("e" . db/dired-ediff-files)
("z" . db/dired-get-size)
([remap beginning-of-buffer] . db/dired-back-to-top)
([remap end-of-buffer] . db/dired-jump-to-bottom)
("<f1>" . nil)
("<tab>" . dired-subtree-toggle)
("<C-tab>" . dired-subtree-cycle))
:init (progn
(setq dired-dwim-target t
dired-listing-switches "-ahlvF"
dired-ls-F-marks-symlinks t
dired-hide-details-hide-information-lines t
dired-hide-details-hide-symlink-targets t
dired-recursive-copies 'top
dired-recursive-deletes 'top
dired-create-destination-dirs 'ask
dired-vc-rename-file t
dired-kill-when-opening-new-dired-buffer nil
dired-maybe-use-globstar t
dired-omit-files "^\\.?#\\|^\\.$\\|^\\.\\.$\\|^\\..*$"
wdired-create-parent-directories t
wdired-allow-to-change-permissions t
dired-isearch-filenames 'dwim
dired-auto-revert-buffer t
dired-clean-confirm-killing-deleted-buffers t
dired-clean-up-buffers-too t)
(setq dired-guess-shell-alist-user
'(("\\.pdf\\'" "evince")
("\\.ps\\'" "evince")
("\\.\\(?:djvu\\|eps\\)\\'" "evince")
("\\.\\(?:jpg\\|jpeg\\|png\\|gif\\|xpm\\)\\'" "eog")
("\\.\\(?:xcf\\)\\'" "gimp")
("\\.\\(?:mp4\\|mkv\\|avi\\|flv\\|ogv\\|webm\\)\\(?:\\.part\\)?\\'"
"vlc")
("\\.\\(?:mp3\\|flac\\|ogg\\)\\'" "mplayer")
("\\.docx?\\'" "loffice"))))
:config (progn
(put 'dired-find-alternate-file 'disabled nil)
(require 'dired-x)
(require 'dired-aux)
(require 'wdired)
(with-demoted-errors "Non-Fatal Errors (dired-open): %s"
(require 'dired-open))
(if (eq system-type 'windows-nt)
(with-demoted-errors "Non-Fatal Error (w32-browser): %s"
(bind-key "M-RET" #'dired-w32-browser dired-mode-map)
(bind-key "<C-return>" #'dired-w32explore dired-mode-map))
(bind-key "M-RET" #'dired-open-xdg dired-mode-map))
(with-demoted-errors "Non-Fatal Errors (dired-recent): %s"
(dired-recent-mode +1))
;; Gnus support in dired
(add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode)
;; Highlight current line more prominently
(add-hook 'dired-mode-hook 'hl-line-mode)
;; omitting files
(add-hook 'dired-mode-hook 'dired-omit-mode)
(add-hook 'dired-mode-hook 'dired-hide-details-mode)
(dolist (extension '(".out" ".synctex.gz" ".thm"))
(add-to-list 'dired-latex-unclean-extensions extension))))
(use-package dired-open
:ensure t
:commands (dired-open-xdg
dired-open-guess-shell-alist
dired-open-call-function-by-extension)
:init (progn
(unless (eq system-type 'gnu/linux)
(setq dired-open-use-nohup nil))
(setq dired-open-extensions-elisp '(("html" . eww-open-file))))
:config (progn
(add-to-list 'dired-open-functions
#'dired-open-guess-shell-alist)
(add-to-list 'dired-open-functions
#'dired-open-call-function-by-extension)))
(use-package dired-recent
:ensure t
:init (setq dired-recent-max-directories nil)
:commands (dired-recent-mode
dired-recent-open))
(use-package dired-subtree
:commands (dired-subtree-toggle))
(use-package ffap
;; Inhibit Emacs from pinging hostnames; see
;; https://www.n16f.net/blog/investigating-a-ffap-issue-in-emacs/
:init (setq ffap-machine-p-local 'accept
ffap-machine-p-known 'accept
ffap-machine-p-unknown 'reject))
(use-package find-dired
:commands (find-dired)
:init (setq find-ls-option '("-print0 | xargs -0 ls -ld" . "-ld")))
(use-package mailcap
:config (progn
;; Remove doc-view so pdf will open with default viewer
(setcdr
(assoc "application" mailcap-mime-data)
(remove '("pdf"
(viewer . doc-view-mode)
(type . "application/pdf")
(test eq window-system 'x))
(cdr (assoc "application" mailcap-mime-data))))))
(use-package gnus-dired
:commands (turn-on-gnus-dired-mode))
(use-package tramp
:init (setq tramp-default-method (if on-windows "pscp" "scp")
tramp-completion-use-auth-sources nil))
(use-package trashed
;; A simple dired-like interface to the system trash bin
;; Configuration taken from https://protesilaos.com/dotemacs
:init (setq trashed-action-confirmer 'y-or-n-p
trashed-use-header-line t
trashed-sort-key '("Date deleted" . t)
trashed-date-format "%Y-%m-%d %H:%M:%S"))
(use-package w32-browser
:commands (dired-w32-browser
dired-w32explore))
;; * Completion
(setq suggest-key-bindings t
extended-command-suggest-shorter t
completions-detailed t
completion-cycle-threshold nil
completion-styles '(basic orderless)
completion-category-defaults nil
;; Via https://protesilaos.com/emacs/dotemacs, with additional changes
completion-category-overrides '((file (styles . (basic partial-completion orderless)))
(buffer (styles . (orderless)))
(bookmark (styles . (orderless)))
(imenu (styles . (basic substring orderless)))
(kill-ring (styles . (emacs22 orderless)))))
(use-package helm
:ensure t
:diminish helm-mode
:autoload (helm-execute-persistent-action
helm-select-action
helm-make-source)
:commands (helm-show-kill-ring)
:defines (helm-source-bookmarks) ; via helm-bookmarks.el
:init (setq helm-command-prefix-key "C-c h" ; see `db/run-init' for explicit binding
helm-input-idle-delay 0.0
helm-buffers-fuzzy-matching t
helm-autoresize-min-height 20
helm-ff-auto-update-initial-value t
helm-ff-file-name-history-use-recentf t
helm-ff-search-library-in-sexp t
helm-ff-skip-boring-files nil
helm-split-window-inside-p t
helm-move-to-line-cycle-in-source nil
helm-scroll-amount nil
helm-locate-command nil
helm-candidate-number-limit 100
helm-follow-mode-persistent t
helm-buffer-details-flag t
helm-buffer-skip-remote-checking t
helm-mode-no-completion-in-region-in-modes '(eshell-mode)
helm-kill-ring-threshold 0 ; include all yanks in the kill ring
)
:config (progn
(eval-when-compile
(require 'helm-mode)
(require 'helm-buffers)
(require 'helm-ring)
(require 'helm-source)
(require 'helm-bookmark))
(if (require 'helm-global-bindings nil :no-error)
(progn
(bind-key "#" #'helm-emms helm-command-map)
(bind-key "P" #'helm-pages helm-command-map))
(warn (concat
"Cannot load `helm-global-bindings', please check your helm installation for completeness. "
"(Have you installed it from melpa?)")))
(bind-key "<tab>" #'helm-execute-persistent-action helm-map)
(bind-key "C-i" #'helm-execute-persistent-action helm-map)
(bind-key "C-z" #'helm-select-action helm-map)))
(use-package ivy
:diminish ivy-mode
:init (setq ivy-use-virtual-buffers t
ivy-magic-tilde nil
ivy-count-format "(%d/%d) "
ivy-initial-inputs-alist '((counsel-describe-function . "^")
(counsel-describe-variable . "^")
(man . "^")
(woman . "^"))
ivy-use-selectable-prompt t
ivy-do-completion-in-region t
ivy-re-builders-alist '((t . ivy--regex-ignore-order)))
:config (progn
;; Since we are using `ivy--regex-ignore-order' for completion
;; anyway, providing an individual restriction in the ivy buffer is
;; not necessary anymore. Since I often mistype S-SPC for SPC,
;; loosing the current candidate and annoying myself, removing this
;; shortcut is both helpful and not removing necessary
;; functionality.
(define-key ivy-minibuffer-map (kbd "S-SPC") nil)))
(use-package swiper
:ensure t
:commands (swiper
swiper-from-isearch))
(use-package recentf
:commands (recentf-mode recentf-save-list)
:init (setq recentf-max-saved-items 1000
recentf-save-file (expand-file-name "recentf" emacs-d-userdata))
:config (run-with-timer 0 3600 #'recentf-save-list))
(use-package company
:commands (company-mode global-company-mode)
:init (setq company-show-quick-access t))
(use-package marginalia
:ensure t
:commands (marginalia-mode)
:init (setq marginalia-max-relative-age 0))
(use-package vertico
:ensure t
:commands (vertico-mode))
(use-package orderless
:ensure t
:init (setq orderless-match-faces ; Some of the default faces are hard to
; read with when solarized is enabled;
; only keep those that are readable
[orderless-match-face-1 orderless-match-face-2]))
(use-package consult
:commands (consult-completion-in-region
consult-goto-line
consult-keep-lines
consult-focus-lines
consult-buffer
consult-imenu
consult-line
consult-mark
consult-yank-pop
consult-outline)
:init (progn
;; Settings taken from https://protesilaos.com/emacs/dotemacs#h:22e97b4c-d88d-4deb-9ab3-f80631f9ff1d
(setq consult-line-numbers-widen t
consult-async-min-input 3
consult-async-input-debounce 0.5
consult-async-input-throttle 0.8
consult-narrow-key nil
consult-preview-key nil))
:config (require 'consult-imenu))
;; * Navigation
(defun db/helm-shortcuts (arg)
"Open helm completion on common locations.
With universal argument ARG, inhibit display of files in
`db/important-document-path’. This might be helpful when loading
is too slow (in this case, `db/important-document-path' should
eventuelly be set to nil, however)."
(interactive "P")
(helm :sources (list
(helm-make-source "Frequently Used" 'helm-source-sync
:candidates (mapcar #'(lambda (entry)
(cons (car entry)
(caddr entry)))
db/frequently-used-features)
:action '(("Open" . call-interactively)))
;; Taken from `helm-buffers-list'
(helm-make-source "Buffers" 'helm-source-buffers)
;; If no prefix arg is given, extract files from
;; `db/important-documents-path’ and list them as well
(when (and (not arg)
(file-directory-p db/important-documents-path))
(let ((search-path (expand-file-name db/important-documents-path)))
(helm-make-source "Important files" 'helm-source-sync
:candidates (mapcar #'(lambda (file)
;; Display only relative path,
;; but keep absolute path for
;; actions
(cons (string-remove-prefix search-path file)
file))
(directory-files-recursively search-path ""))
:action '(("Open externally" . db/system-open)
("Find file" . find-file)))))
helm-source-bookmarks)))
(use-package ace-window
:ensure t
:commands (ace-window ace-window-display-mode)
:init (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)
aw-background t
aw-leading-char-style 'char
aw-scope 'frame))
(use-package avy
:ensure t
:commands (avy-goto-char-timer
avy-goto-word-or-subword-1
avy-goto-line))
(use-package bm
;; Taken from https://protesilaos.com/dotemacs/ and adapted slightly
:ensure t
:commands (bm-toggle bm-next bm-previous bm-toggle-buffer-persistence)
:init (setq bm-restore-repository-on-load t
bm-annotate-on-create nil
bm-buffer-persistence t
bm-cycle-all-buffers t
bm-goto-position nil
bm-highlight-style 'bm-highlight-line-and-fringe
bm-marker 'bm-marker-right
bm-in-lifo-order nil
bm-recenter t
bm-repository-file "~/.emacs.d/bm-bookmarks"
bm-repository-size 100
bm-show-annotations t
bm-wrap-immediately t
bm-wrap-search t))
(use-package bookmark
:init (setq bookmark-default-file (expand-file-name "bookmarks" emacs-d-userdata)
bookmark-menu-confirm-deletion t))
(use-package dumb-jump
:commands (dumb-jump-xref-activate)
:init (progn
(setq dumb-jump-selector 'helm)
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate)))
(use-package helm-pages
:ensure t
:commands (helm-pages))
(use-package imenu
:init (setq imenu-use-markers t
imenu-auto-rescan t
imenu-auto-rescan-maxout 600000
imenu-max-item-length 100
imenu-use-popup-menu nil
imenu-eager-completion-buffer t))
(use-package isearch
:init (setq isearch-allow-scroll t
isearch-lazy-count t
isearch-lax-whitespace nil
isearch-regexp-lax-whitespace nil))
(use-package goto-last-change
:commands goto-last-change)
;; * Media
(use-package db-music
:init (setq db/auto-playlist-file-function
#'(lambda ()
(db/playlist-files-from-git-annex-find
"--metadata db-playlist=include")))
:commands (db/play-auto-playlist
db/playlist-files-from-git-annex-find
db/play-auto-playlist-from-git-annex-find
music-control/body
db/update-playlist-files))
(use-package emms
:pin "melpa-stable"
:commands (emms
emms-pause
emms-stop
emms-next
emms-previous)
:bind (:map emms-playlist-mode-map
("S s" . emms-shuffle))
:init (setq emms-source-file-default-directory "~/Documents/media/audio/"
emms-player-list '(emms-player-mplayer emms-player-mplayer-playlist
emms-player-vlc emms-player-vlc-playlist)
emms-show-format "NP: %s"
emms-stream-default-action "play"
emms-track-description-function 'db/emms-track-description
emms-playlist-default-major-mode 'emms-playlist-mode
emms-cache-file (expand-file-name "emms/cache" emacs-d-userdata)
emms-history-file (expand-file-name "emms/history" emacs-d-userdata)
emms-score-file (expand-file-name "emms/scores" emacs-d-userdata)
emms-stream-bookmarks-file (expand-file-name "emms/streams" emacs-d-userdata))
:config (progn
;; Initialization copied and adapted from `emms-setup’
(require 'emms-source-file)
(require 'emms-source-playlist)
(require 'emms-player-simple)
(require 'emms-player-mplayer)
(require 'emms-player-vlc)
(require 'emms-playlist-mode)
(require 'emms-info)
(require 'emms-info-mp3info)
(require 'emms-info-ogginfo)
(require 'emms-info-opusinfo)
(require 'emms-cache)
(require 'emms-mode-line)
(require 'emms-mark)
(require 'emms-tag-editor)
(require 'emms-show-all)
(require 'emms-streams)
(require 'emms-playing-time)
(require 'emms-player-mpd)
(require 'emms-playlist-sort)
(require 'emms-browser)
(require 'emms-mode-line-icon)
(require 'emms-cue)
(require 'emms-bookmarks)
(require 'emms-last-played)
(require 'emms-metaplaylist-mode)
(require 'emms-stream-info)
(require 'emms-score)
(require 'emms-history)
(require 'emms-i18n)
(require 'emms-volume)
(require 'emms-playlist-limit)
(add-to-list 'emms-track-initialize-functions
'emms-info-initialize-track)
(if (require 'emms-info-mediainfo nil 'no-error)
(setq emms-info-functions '(emms-info-mediainfo))
(when (executable-find emms-info-mp3info-program-name)
(add-to-list 'emms-info-functions 'emms-info-mp3info))
(when (executable-find emms-info-ogginfo-program-name)
(add-to-list 'emms-info-functions 'emms-info-ogginfo))
(when (executable-find emms-info-opusinfo-program-name)
(add-to-list 'emms-info-functions 'emms-info-opusinfo))
(add-to-list 'emms-info-functions 'emms-info-cueinfo))
(when (fboundp 'emms-cache) ; work around compiler warning
(emms-cache 1))
(emms-mode-line -1)
(emms-playing-time 1)
(emms-score 1)
(add-hook 'emms-player-started-hook 'emms-last-played-update-current)
(add-hook 'emms-player-started-hook 'emms-show)
(advice-add 'emms-tag-editor-submit
:after #'(lambda (&rest r)
(ignore r)
(delete-window)))
(unless (eq system-type 'windows-nt)
(setq emms-source-file-directory-tree-function
#'db/emms-source-file-directory-tree-find))
;; `emms-playlist-mode’ sets `emms-playlist-insert-track-function’,
;; no matter what previous values or customization may say otherwise
;; … so we need to employ a hook to change its value
(add-hook 'emms-playlist-mode-hook
#'(lambda ()
(setq emms-playlist-insert-track-function
#'db/emms-playlist-mode-insert-track)))
(run-with-timer 0 3600 #'emms-cache-save)))
(use-package emms-source-file
:config (progn
;; Make sure emms is up and running when we call functions such as
;; `emms-play-dired’ etc.
(require 'emms)))
(use-package db-emms
:commands (db/emms-source-file-directory-tree-find
db/emms-track-description
db/emms-playlist-mode-insert-track))
(use-package helm-emms
:commands (helm-emms)
:init (setq helm-emms-use-track-description-function t
helm-emms-default-sources '(helm-source-emms-streams
helm-source-emms-dired
helm-source-emms-files)))
(use-package image
:init (setq image-use-external-converter t))
;; * Shells and such
(use-package comint
:init (setq comint-scroll-to-bottom-on-input t
comint-scroll-to-bottom-on-output nil
comint-scroll-show-maximum-output t
comint-completion-addsuffix t
comint-buffer-maximum-size 100000
comint-input-ring-size 5000)
:config (progn
;; Never let bash know that we are inside Emacs;
;; cf. https://coredumped.dev/2020/01/04/native-shell-completion-in-emacs/
(advice-add 'comint-term-environment
:filter-return #'(lambda (env) (cons "INSIDE_EMACS" env)))))
(use-package term
:commands (term-send-string
term-send-raw)
:init (setq explicit-shell-file-name "/bin/bash")
:config (progn
(add-hook 'term-exec-hook ; oremacs.com
#'(lambda ()
(let* ((buff (current-buffer))
(proc (get-buffer-process buff)))
(set-process-sentinel
proc
`(lambda (process event)
(if (string= event "finished\n")
(kill-buffer ,buff)))))))
;; does not work; C-c is shadowed by some minor modes like semantic,
;; projectile, and winner
(bind-key "C-c" #'term-send-raw term-raw-map)
;; unbind some keys to allow the global keymap to handle them
(unbind-key "M-:" term-raw-map)
(unbind-key "C-h" term-raw-map)
(unbind-key "M-x" term-raw-map)
(unbind-key "M-o" term-raw-map)
;; we need to set keys starting with C-x after `ansi-term' has been
;; called, as it resets the escape character to C-x.
(define-advice ansi-term (:after
(&rest _)
db/ansi-term--unbind-annoying-keys)
(unbind-key "C-x C-j" term-raw-map)
(unbind-key "C-x g" term-raw-map))
(add-hook 'term-mode-hook #'(lambda () (yas-minor-mode -1)))))
(use-package ansi-color
:commands (ansi-color-for-comint-mode-on
ansi-color-apply-on-region)
:config (progn
(add-hook 'compilation-filter-hook 'endless/colorize-compilation)))
(use-package shell
:commands (shell)
:config (progn
(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)
(add-hook 'comint-output-filter-functions 'comint-strip-ctrl-m)
(add-hook 'shell-mode-hook 'with-editor-export-editor)
;; We may want to use readline support in bash, don't inhibit this
;; with explicit command line arguments;
;; cf. https://coredumped.dev/2020/01/04/native-shell-completion-in-emacs/
(setq explicit-bash-args
(delete "--noediting" explicit-bash-args))
;; When doing completion, ivy seems to add an extra space, much like
;; in the case for eshell. However, here the space seems to come
;; out of nowhere. Since the builtin completion using
;; `completion--in-region' is good enough for the shell mode, let's
;; stick to that.
(add-hook 'shell-mode-hook
#'(lambda ()
(setq-local completion-in-region-function
#'completion--in-region)))))
(use-package db-eshell
:commands (db/run-or-hide-eshell
eshell-clear-buffer
eshell/default-prompt-function
eshell-insert-history
pcomplete/git))
(use-package eshell
:commands (eshell)
:init (setq eshell-cmpl-cycle-completions nil
eshell-scroll-to-bottom-on-input t
eshell-prefer-lisp-functions nil
eshell-error-if-no-glob nil
eshell-hist-ignoredups t
eshell-save-history-on-exit t
eshell-history-size 5000
eshell-history-file-name (expand-file-name "eshell/history" emacs-d-userdata)
eshell-last-dir-ring-file-name (expand-file-name "eshell/lastdir" emacs-d-userdata)
eshell-destroy-buffer-when-process-dies t
eshell-prompt-function #'eshell/default-prompt-function
eshell-prompt-regexp "└─[$#] "
eshell-highlight-prompt nil
eshell-cd-on-directory t
eshell-expand-input-functions '(eshell-expand-history-references))
:config (progn
(eval-when-compile
(require 'em-prompt)
(require 'em-term)
(require 'em-cmpl)
(require 'em-hist)
(require 'em-glob))
(setenv "PAGER" "cat")
(add-to-list 'eshell-command-completions-alist
'("gunzip" "gz\\'"))
(add-to-list 'eshell-command-completions-alist
'("tar" "\\(\\.tar|\\.tgz\\|\\.tar\\.gz\\)\\'"))
(add-hook 'eshell-mode-hook
'with-editor-export-editor)
(if (<= emacs-major-version 27)
(add-hook 'eshell-mode-hook
#'(lambda ()
(bind-key "C-a" #'eshell-bol eshell-mode-map)
(bind-key "M-r" #'eshell-insert-history eshell-mode-map)
(bind-key "M-P" #'eshell-previous-prompt eshell-mode-map)
(bind-key "M-N" #'eshell-next-prompt eshell-mode-map)))
;; In Emacs 28.1, eshell's mode maps have been refactored to
;; follow standard extensibility. There's thus no need
;; anymore to use the special hook construction.
(progn
(bind-key "C-a" #'eshell-bol eshell-mode-map)
(bind-key "M-r" #'eshell-insert-history eshell-hist-mode-map)
(bind-key "M-P" #'eshell-previous-prompt eshell-mode-map)
(bind-key "M-N" #'eshell-next-prompt eshell-mode-map)))
;; Ignoring case when completing file names seems to have a
;; bug: when a ~ is encountered, it is implicitly expaned by
;; `pcomplete-insert-entry’, overwriting the prompt as a side
;; effect. Keeping the case as it is does not seem to have
;; this issue. This problem occurs by default only on Windows
;; systems (in all flavors), because this is the only time
;; `pcomplete-ignore-case’ is non-nil by default.
(when on-windows
(add-to-list 'eshell-mode-hook
#'(lambda ()
(setq completion-ignore-case nil))))
;; Sometimes, when completing path names and immediately
;; hitting RET, `completion-in-region-mode' still seems to be
;; active; this often happens when calling cd. Then,
;; `post-command-hook' still contains
;; `completion-in-region--postch', which calls some `pcomplete'
;; function to determine whether completion mode should exit.
;; However, after RET (i.e., `eshell-send-input'), we only have
;; an empty prompt, and `pcomplete' just computes all possible
;; functions for completion (doesn't show it, though). This
;; introduces a noticeable amount of delay, which is annoying.
;; Expliticly disabling `completion-in-region-mode' before
;; sending input seems to alleviate the problem.
(advice-add 'eshell-send-input
:before #'(lambda (&rest _)
"Disable completion-in-region-mode before sending input."
(when completion-in-region-mode
(completion-in-region-mode -1))))
;; After completing partial inputs, pcomplete wants to add an
;; extra character stored in `pcomplete-termination-string',
;; which is a space by default. When completing paths, this
;; leads to spaces being inserted after every directory within
;; the path, which is annoying. We thus set the value of this
;; variable locally to an empty string, thus silencing this
;; bug. A better way to handle this would be to correctly
;; determine whether the completion is not done yet, by passing
;; `exact' instead of `finished' to the handlers stored in
;; `completion-extra-properties'.
(add-hook 'eshell-mode-hook
#'(lambda ()
(setq-local pcomplete-termination-string "")))
(require 'db-eshell)))
(use-package em-prompt ; Why is this extra declaration necessary?
:commands (eshell-previous-prompt
eshell-next-prompt))
(use-package esh-mode ; Why is this extra declaration necessary?
:commands (eshell-bol))
(use-package with-editor
:commands (with-editor-export-editor))
;; * Lisp
;; General Stuff first
(use-package lisp-mode
:mode (("\\.cl\\'" . lisp-mode)
("\\.lisp\\'" . lisp-mode))
:init (setq lisp-indent-function #'common-lisp-indent-function))
(use-package lispy
:ensure t
:commands (lispy-mode)
:diminish lispy-mode)
(use-package eldoc
:commands (global-eldoc-mode
turn-on-eldoc-mode)
:diminish eldoc-mode)
;; Lisp Dialects
(use-package elisp-mode
:config (progn
(add-hook 'emacs-lisp-mode-hook 'turn-on-lispy-when-available)
(add-hook 'emacs-lisp-mode-hook 'turn-on-flycheck-when-file)
(add-hook 'lisp-mode-hook 'turn-on-lispy-when-available)))
(use-package geiser
:commands (geiser-mode))
(use-package cider
:commands (cider-jack-in)
:init (setq nrepl-hide-special-buffers t
cider-auto-select-error-buffer t
cider-stacktrace-default-filters '(tooling dup)
cider-stacktrace-fill-column 80
cider-save-file-on-load nil
cider-repl-result-prefix ";; => "
cider-repl-use-clojure-font-lock t
cider-repl-wrap-history t
cider-repl-history-size 1000
;;cider-lein-parameters "trampoline repl :headless"
cider-lein-parameters "repl :headless"
cider-repl-history-file (expand-file-name ".cider-history" emacs-d)
cider-repl-display-help-banner nil)
:config (progn
(add-hook 'cider-repl-mode-hook 'subword-mode)
(add-hook 'cider-repl-mode-hook 'turn-on-lispy-when-available)
(add-hook 'cider-repl-mode-hook 'cider-repl-toggle-pretty-printing)
(add-hook 'cider-repl-mode-hook 'company-mode)))
(use-package clojure-mode
:mode (("\\.clj\\'" . clojure-mode))
:config (progn
(define-clojure-indent
(forall 'defun)
(exists 'defun)
(dopar 'defun))
(add-hook 'clojure-mode-hook 'turn-on-lispy-when-available)
(add-hook 'clojure-mode-hook 'clj-refactor-mode)
(add-hook 'clojure-mode-hook 'company-mode)))
(use-package clj-refactor
:commands (clj-refactor-mode)
:config (progn
(cljr-add-keybindings-with-prefix "C-c C-m")
(setq cljr-eagerly-build-asts-on-startup nil
cljr-warn-on-eval nil)))
(use-package slime
:commands (slime slime-mode slime-connect)
:init (progn
(setq inferior-lisp-program "sbcl --noinform --no-linedit"
slime-compile-file-options '(:fasl-directory "/tmp/slime-fasls/")
slime-net-coding-system 'utf-8-unix
slime-completion-at-point-functions 'slime-fuzzy-complete-symbol
slime-lisp-implementations '((sbcl ("sbcl") :coding-system utf-8-unix)
(cmucl ("cmucl") :coding-system utf-8-unix)
(ccl ("ccl") :coding-system utf-8-unix)))
(add-hook 'lisp-mode-hook #'(lambda () (slime-mode +1)) t))
:config (progn
(make-directory "/tmp/slime-fasls/" t)
(slime-setup '(slime-repl slime-fancy slime-autodoc))
(add-hook 'slime-mode-hook 'slime-redirect-inferior-output)))
(use-package hy-mode
:commands (hy-mode)
:config (progn
(add-hook 'hy-mode-hook 'turn-on-lispy-when-available)
(add-hook 'hy-mode-hook 'inferior-lisp)))
;; * Other Mode Configurations
(use-package cperl-mode
:ensure t
:commands (cperl-mode)
:mode ("\\.plx\\'" . cperl-mode)
:init (progn
(add-to-list 'major-mode-remap-alist '(perl-mode . cperl-mode))
(setq cperl-hairy nil
cperl-invalid-face 'default
cperl-electric-keywords nil
cperl-lazy-help-time 2
cperl-highlight-variables-indiscriminately t
cperl-indent-parens-as-block t))
:config (progn
(add-hook 'cperl-mode-hook 'flycheck-mode)
(add-hook 'cperl-mode-hook 'prettify-symbols-mode)
;; enable display of help messages after a short period of time, as
;; controlled by the value of `cperl-lazy-help-time'
(add-hook 'cperl-mode-hook 'cperl-lazy-install)))
(use-package define-word
:ensure t
:commands (define-word-at-point define-word))
(use-package dictcc
:ensure t
:commands (dictcc)
:config (require 'gnutls))
(when (package-installed-p "auctex")
(add-to-list 'major-mode-remap-alist '(latex-mode . LaTeX-mode))
(require 'db-latex))
(use-package edit-list
:ensure t
:commands edit-list)
(use-package enriched
:defer t
:config (progn
;; https://www.opencve.io/cve/CVE-2017-14482 for Emacs before 25.3
(when (version< emacs-version "25.3")
(defun enriched-decode-display-prop (start end &optional params)
(ignore params)
(list start end)))))
(use-package expand-region
:ensure t
:commands (er/expand-region))
(use-package eww
:init (setq eww-bookmarks-directory emacs-d-userdata))
(use-package haskell-mode
:config (progn
(add-hook 'haskell-mode-hook 'haskell-doc-mode)
(add-hook 'haskell-mode-hook 'turn-on-haskell-indent)
(add-hook 'haskell-mode-hook
#'(lambda ()
(company-mode 1)
(set (make-local-variable 'company-backends)
(append '((company-capf company-dabbrev-code))
company-backends))))
(add-hook 'haskell-mode-hook 'flycheck-mode)
(add-hook 'haskle--mode-hook 'subword-mode)
(with-demoted-errors "Non-Fatal Error: %s"
(require 'haskell-indentation)
(add-hook 'haskell-mode-hook
'haskell-indentation-mode))
(add-hook 'haskell-mode-hook
'interactive-haskell-mode)))
(use-package markdown-mode
:ensure t
:commands (markdown-mode)
:init (progn
(setq markdown-use-pandoc-style-yaml-metadata t
markdown-command "pandoc --standalone")
(fset 'markdown-output-standalone-p #'(lambda () t))))
(use-package plantuml-mode
:load-path "site-lisp"
:mode ("\\.plantuml\\'" . plantuml-mode)
:commands (plantuml-mode)
:init (setq plantuml-output-type "svg"
plantuml-default-exec-mode 'jar
plantuml-jar-path "/usr/share/plantuml/plantuml.jar"
plantuml-indent-level 2))
(use-package project
:init (setq project-switch-commands 'project-dired))
(use-package python
:config (progn
(unless (require 'lsp-pyright nil :no-error)
(message "`lsp-pyright' not available, using defaults from `lsp'"))
(add-hook 'python-mode-hook #'highlight-indentation-mode)
(add-hook 'python-mode-hook #'company-mode)
(add-hook 'python-mode-hook #'lsp-deferred)
(add-hook 'python-mode-hook #'subword-mode)))
;; https://ddavis.io/posts/emacs-python-lsp/
(use-package pyvenv
:ensure t
:commands (pyvenv-workon
pyvenv-activate
pyvenv-create)
:init (unless (getenv "WORKON_HOME")
(setenv "WORKON_HOME" (expand-file-name "~/.pyenv/versions")))
:config (progn
;; Restart python inferior processes when switching virtual
;; environments; this does not work when only calling
;; `pyvenv-deactivate', though.
(add-hook 'pyvenv-post-activate-hooks #'pyvenv-restart-python)))
(use-package re-builder
:commands (re-builder)
:init (setq reb-re-syntax 'string))
(use-package sh-script
:init (setq sh-basic-offset 2))
(use-package shr
:init (setq shr-use-fonts nil
shr-use-colors nil
shr-max-image-proportion 0.7
shr-image-animate nil
shr-width (current-fill-column)))
(use-package textile-mode
:config (progn
;; Do not wrap lines automatically in textile mode, as text produced
;; in this mode is usually copied to Redmine Wiki pages later on.
(add-hook 'textile-mode-hook #'turn-off-auto-fill)
(add-hook 'textile-mode-hook #'turn-on-visual-line-mode)))
(use-package timeline-tools
:load-path "site-lisp"
:commands (timeline-tools-format-timeline
timeline-tools-format-timeline-of-day
timeline-tools-copy-clocklines
timeline-tools-clockline-no-org-agenda-conflicts))
(use-package vlf
:ensure t
:commands (vlf))
(use-package which-key
:ensure t
:commands (which-key-mode)
:diminish which-key-mode
:init (setq which-key-side-window-max-width 0.33
which-key-side-window-max-height 0.25)
:config (which-key-setup-side-window-bottom))
(use-package yaml-mode
:ensure t
:config (progn
(add-hook 'yaml-mode-hook #'highlight-indentation-mode)))
;; * Load customizations
(when (file-exists-p custom-file)
(load-file custom-file))
;; * Actual Mode Activation
(defun db/run-init ()
"Run main initialization after everything is set up."
(message "Running main initialization ...")
;; Activate modes (builtin)
(show-paren-mode 1)
(transient-mark-mode 1)
(global-font-lock-mode 1)
(column-number-mode 1)
(delete-selection-mode -1)
(dolist (mode '(tool-bar-mode
scroll-bar-mode
menu-bar-mode
blink-cursor-mode
tooltip-mode))
(when (fboundp mode)
(funcall mode 0)))
(when (<= 24 emacs-major-version)
(electric-indent-mode -1))
(appt-activate +1)
(savehist-mode 1)
(size-indication-mode 1)
(display-battery-mode -1)
(electric-pair-mode +1)
(recentf-mode t)
(winner-mode 1)
(global-auto-revert-mode -1)
(which-function-mode +1)
(global-eldoc-mode +1)
;; Activate modes (packages)
(dolist (mode '(global-undo-tree-mode
minibuffer-depth-indicate-mode
ace-window-display-mode
key-chord-mode
minions-mode
which-key-mode
projectile-mode
yas-global-mode
global-git-commit-mode
;; global-company-mode
marginalia-mode
vertico-mode
))
(with-demoted-errors "Cannot activate mode: %s"
(funcall mode +1)))
;; This causes inacceptable lag when drawing buffers, so disable it for now.
;; Needs to be investigated further.
;; (with-demoted-errors "Cannot activate moody: %s"
;; (moody-replace-mode-line-buffer-identification)
;; (moody-replace-vc-mode))
(with-demoted-errors "Cannot activate `vlf': %s"
(require 'vlf-setup))
;; Global Hooks
(add-hook 'minibuffer-setup-hook 'cursor-intangible-mode)
(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)
(add-hook 'prog-mode-hook 'page-break-lines-mode)
(add-hook 'prog-mode-hook 'hl-line-mode)
(when (<= 24 emacs-major-version)
(add-hook 'prog-mode-hook 'electric-indent-local-mode))
(add-hook 'text-mode-hook 'page-break-lines-mode)
(add-hook 'text-mode-hook 'turn-on-auto-fill)
(add-hook 'text-mode-hook 'abbrev-mode)
(add-hook 'text-mode-hook 'hl-line-mode)
;; Top-Level Keybindings
(bind-key "<XF86Back>" #'winner-undo)
(bind-key "<XF86Forward>" #'winner-redo)
(bind-key "<Scroll_Lock>" 'scroll-lock-mode)
(bind-key "<f10>" #'magit-status)
(bind-key "<f1>" #'db/run-or-hide-eshell)
(bind-key "<f2>" #'hydra-feature-shortcuts/body)
(bind-key "<f5>" #'rgrep)
(bind-key "<f6>" #'hydra-zoom/body)
(bind-key "<f7>" #'dictcc)
(bind-key "<f8>" #'bm-toggle)
(bind-key "<f9>" #'hydra-org-linking/body)
(bind-key "<C-f8>" #'bm-next)
(bind-key "<C-S-f8>" #'bm-previous)
(bind-key "C-," #'mc/skip-to-previous-like-this)
(bind-key "C-." #'mc/skip-to-next-like-this)
(bind-key "C-;" #'iedit-mode)
(bind-key "C-<" #'mc/mark-previous-like-this)
(bind-key "C->" #'mc/mark-next-like-this)
(bind-key "C-@" #'er/expand-region)
(bind-key "C-M-\\" #'crux-cleanup-buffer-or-region)
(bind-key "C-S-c C-S-c" #'mc/edit-lines)
(bind-key "C-Z" #'undo-tree-redo)
(bind-key "C-c C-<" #'mc/mark-all-like-this)
(bind-key "C-c D" #'define-word)
(bind-key "C-c J" #'avy-goto-word-or-subword-1)
(bind-key "C-c a" #'org-agenda)
(bind-key "C-c c" #'org-capture)
(bind-key "C-c d" #'define-word-at-point)
(bind-key "C-c e" #'crux-eval-and-replace)
(bind-key "C-c i" #'ispell-change-dictionary)
(bind-key "C-c j" #'avy-goto-char-timer)
(bind-key "C-c l" #'org-store-link)
(bind-key "C-c m" #'music-control/body)
(bind-key "C-c n f" #'org-roam-node-find)
(bind-key "C-c n i" #'org-roam-node-insert)
(bind-key "C-c n c" #'org-roam-capture)
(bind-key "C-c o" #'hydra-org-custom/body)
(bind-key "C-c r" #'recentf)
(bind-key "C-c t" #'hydra-toggle/body)
(bind-key "C-h C-f" #'find-function)
(bind-key "C-h C-k" #'find-function-on-key)
(bind-key "C-h C-v" #'find-variable)
(bind-key "C-x 4 C-j" #'dired-jump-other-window)
(bind-key "C-x C-b" #'ibuffer)
(bind-key "C-x C-d" #'dired)
(bind-key "C-x C-j" #'dired-jump)
(bind-key "C-x C-r" #'revert-buffer)
(bind-key "C-x SPC" #'hydra-rectangle/body)
(bind-key "C-x g" #'db/helm-shortcuts)
(bind-key "C-x r E" #'db/bookmark-add-external)
(bind-key "C-x r M" #'db/bookmark-add-url)
(bind-key "C-x r v" #'list-registers)
(bind-key "C-z" #'goto-last-change)
(bind-key "M-/" #'hippie-expand)
(bind-key "M-:" #'pp-eval-expression)
(bind-key "M-=" #'count-words)
(bind-key "M-SPC" #'cycle-spacing) ; default since Emacs 29.1
(bind-key "M-Z" #'zap-to-char)
(bind-key "M-i" #'swiper-from-isearch isearch-mode-map)
(bind-key "M-j" #'(lambda () (interactive) (join-line -1)))
(bind-key "M-z" #'zap-up-to-char)
(bind-key [remap fill-paragraph] #'endless/fill-or-unfill)
(bind-key [remap keyboard-quit] #'keyboard-quit-context+)
(unbind-key "<insert>" global-map)
(unbind-key "<kp-insert>" global-map)
(unbind-key "C-x C-c" global-map)
(unbind-key "M-o" global-map)
;; Overwrite certain keybindings only if packages are avilable
(when (package-installed-p 'helm)
;; Explicitly require helm, because autoloading is difficult with helm's
;; separate `helm-command-prefix-key' mechanism.
(require 'helm)
(bind-key "M-y" #'helm-show-kill-ring)
(bind-key helm-command-prefix-key #'helm-command-prefix))
(when (package-installed-p 'crux)
(bind-key [remap kill-whole-line] #'crux-kill-whole-line)
(bind-key [remap open-line] #'crux-smart-open-line-above))
(when (package-installed-p 'ace-window)
(bind-key "C-x o" #'ace-window))
(when (package-installed-p 'avy)
(bind-key "M-g M-g" #'avy-goto-line)
(bind-key "M-g g" #'avy-goto-line))
(when (package-installed-p 'consult)
(bind-key "M-g i" #'consult-imenu)
(bind-key "C-x b" #'consult-buffer)
(setq completion-in-region-function #'consult-completion-in-region))
;; Environment Variables
(unless on-windows
(with-demoted-errors "Cannot import environment variables: %s"
(exec-path-from-shell-copy-envs '("SSH_AUTH_SOCK"
"SSH_AGENT_PID"
"PATH"
"TEXMFHOME"
"PERL5LIB"
"PERL_LOCAL_LIB_ROOT"
"PERL_MB_OPT"
"PERL_MM_OPT"))))
;; Start Server when not running already
;; The following condition should actually always be false, since we have
;; neither loaded the server package yet nor have explicitly started the
;; server process. Also the --daemon command line switches will start the
;; server only later, after initialization (and they do so unconditionally,
;; thus restarting the server we have started here). However, for robustness,
;; we keep the condition nevertheless, since when a server process is already
;; present, we really don't have to do anything. Furthermore, calling
;; `db/run-init' again in a running Emacs will not restart the server (but
;; then, why whould one want to do this?).
(if (and (boundp 'server-process) server-process)
(message "Server already running, not restarting.")
(require 'server)
(let ((server-file (expand-file-name server-name
(if server-use-tcp server-auth-dir server-socket-dir))))
(if (file-exists-p server-file)
(warn "Server file already exists, but no server process is running. Check %s and restart server manually."
server-file)
(server-start)
(cl-case (server-running-p)
((t) t) ; server is running
((nil) (warn "Server not running, check logs and restart manually."))
(t (warn "`server-running-p' returned neither nil nor t. Check and restart server manually if required."))))))
;; Load custom code
(dolist (file db/after-init-load-files)
(message "Loading %s" file)
(with-demoted-errors "Error loading file: %s"
(load-file file)))
;; Finish
(message "Running main initialization ... done")
t)
(add-hook 'after-init-hook #'db/run-init)
;;; init.el ends here