#+title: Emacs Config #+property: header-args:elisp :mkdirp yes :results silent :padline no #+property: header-args:elisp+ :tangle "~/.config/emacs/init.el" * General ** Settings Quality of life settings. #+begin_src elisp (setq-default help-window-select t delete-by-moving-to-trash t sentence-end-double-space nil confirm-kill-emacs 'yes-or-no-p scroll-conservatively 101 cursor-in-non-selected-windows nil) #+end_src ** Startup Screen Disable default startup screen. #+begin_src elisp (setq-default inhibit-startup-screen t) #+end_src ** GUI Bars Disable GUI bars. #+begin_src elisp (tool-bar-mode -1) (menu-bar-mode -1) (scroll-bar-mode -1) #+end_src ** Minibuffer Enable vertical minibuffer. #+begin_src elisp (fido-vertical-mode) #+end_src ** Behaviour Automatically move cursor point on window split. #+begin_src elisp (advice-add 'split-window-below :after (lambda (&rest r) (other-window 1))) (advice-add 'split-window-right :after (lambda (&rest r) (other-window 1))) #+end_src Add advice to copy line at cursor point or region. #+begin_src elisp (defadvice kill-ring-save (before dwim-copy activate compile) "Copy the current line at cursor point or region." (interactive (if mark-active (list (region-beginning) (region-end)) (message "Line copied") (list (line-beginning-position) (line-beginning-position 2))))) #+end_src Add advice to cut line or region at cursor point. #+begin_src elisp (defadvice kill-region (before dwim-cut activate compile) "Cut the current line at point or region." (interactive (if mark-active (list (region-beginning) (region-end)) (list (line-beginning-position) (line-beginning-position 2))))) #+end_src Add advice to enable ~query-replace~ to search the whole buffer or region if selected, as the original default behaviour would only begin to replace from the cursor point position. #+begin_src elisp (defun +advice-goto-top-no-region (func &rest args) "Move point to top of buffer when no region applying ARGS to FUNC." (save-excursion (when (not (use-region-p)) (goto-char (point-min))) (apply func args))) (advice-add 'query-replace :around #'+advice-goto-top-no-region) (advice-add 'query-replace-regexp :around #'+advice-goto-top-no-region) #+end_src ** Editor Tab behaviour settings. #+begin_src elisp (setq-default tab-width 4 tab-always-indent 'complete backward-delete-char-untabify-method 'hungry indent-tabs-mode nil) #+end_src Replace selected region when yanking text. #+begin_src elisp (delete-selection-mode) #+end_src Auto update buffer on any file changes. #+begin_src elisp (global-auto-revert-mode) #+end_src Automatically remove trailing whitespace on file-save. #+begin_src elisp (add-hook 'before-save-hook #'whitespace-cleanup) #+end_src Increase default column width. #+begin_src elisp (setq-default fill-column 80) #+end_src Set whitespace column length and symbols. #+begin_src elisp (with-eval-after-load 'whitespace (setq-default whitespace-line-column fill-column whitespace-display-mappings '((space-mark ?\s [?·] [?.]) (newline-mark ?\n [?↴ ?\n]) (tab-mark ?\t [?➔ ?\t] [?\\ ?\t])))) #+end_src Enable pairing brackets. #+begin_src elisp (electric-pair-mode) #+end_src ** Theme Enable theme. #+begin_src elisp (load-theme 'modus-vivendi) #+end_src ** Modeline Enable column number in modeline. #+begin_src elisp (column-number-mode) #+end_src ** Completion Enable completions previews. #+begin_src elisp (add-hook 'prog-mode-hook #'completion-preview-mode) #+end_src Set completion keybindings. #+begin_src elisp (with-eval-after-load 'completion-preview (let ((map completion-preview-active-mode-map)) (keymap-set map "M-n" #'completion-preview-next-candidate) (keymap-set map "M-p" #'completion-preview-prev-candidate))) #+end_src ** Which-key Enable which-key that displays a pop-up window of posisble keybindings sequences. #+begin_src elisp (which-key-mode) #+end_src ** History *** Minibuffer History Enable saving minibuffer history. #+begin_src elisp :noweb yes (savehist-mode) (with-eval-after-load 'savehist <>) #+end_src Automatically save history on exit. #+begin_example elisp :tangle no :noweb-ref savehist-settings (add-hook 'kill-emacs-hook #'savehist-save) #+end_example Ensure savehist file is loaded. #+begin_src elisp :tangle no :noweb-ref savehist-settings (unless savehist-loaded (load-file savehist-file)) #+end_src Add addtional variable lists to save. #+begin_src elisp :tangle no :noweb-ref savehist-settings (dolist (var '(command-history register-alist mark-ring kill-ring search-ring regexp-search-ring)) (cl-pushnew var savehist-additional-variables)) #+end_src *** Recent Files Enable saving history of recent opened files. #+begin_src elisp :noweb yes (recentf-mode) (with-eval-after-load 'recentf <>) #+end_src Recentf settings. #+begin_src elisp :tangle no :noweb-ref recentf-settings (setq-default recentf-max-saved-items 50 recentf-auto-cleanup 'never) #+end_src Save recent files and cleanup file list on exit. #+begin_src elisp :tangle no :noweb-ref recentf-settings (add-hook 'kill-emacs-hook #'recentf-cleanup) (add-hook 'kill-emacs-hook #'recentf-save-list) #+end_src Disallow duplicates in history. #+begin_src elisp :tangle no :noweb-ref recentf-settings (setq-default history-delete-duplicates t) #+end_src *** Cursor Point Position Enable saving cursor point positions. #+begin_src elisp :noweb yes (save-place-mode) (with-eval-after-load 'saveplace <>) #+end_src Save place settings. #+begin_src elisp :tangle no :noweb-ref saveplace-settings (setq-default save-place-abbreviate-file-names t save-place-limit 800) #+end_src *** Bookmarks Don't display bookmark icon in fringe. #+begin_src elisp (with-eval-after-load 'bookmark (setq-default bookmark-fringe-mark nil)) #+end_src Disable auto-bookmarking org-mode files. #+begin_src elisp (with-eval-after-load 'org (setq-default org-bookmark-names-plist nil)) #+end_src ** Spell Checking Set dictionary. #+begin_src elisp (setq-default ispell-dictionary "british") #+end_src Enable spell checking. #+begin_src elisp (add-hook 'text-mode-hook #'flyspell-mode) (add-hook 'prog-mode-hook #'flyspell-prog-mode) #+end_src Add add word to dictionary function. #+begin_src elisp (defun +flyspell-add-word-to-dictionary () "Save word at point to personal dictionary." (interactive) (when-let ((loc (point)) (word (flyspell-get-word))) (when (yes-or-no-p (format "Add '%s' to dictionary?" (car word))) (flyspell-do-correct 'save nil (car word) loc (cadr word) (caddr word) loc) (save-buffer) (revert-buffer-quick) (message "Added '%s' to personal dictionary" (car word))))) #+end_src Set custom spell checking keybindings. #+begin_src elisp (setq-default flyspell-mode-map (make-sparse-keymap)) (with-eval-after-load 'flyspell (keymap-set flyspell-mode-map "C-x c c" #'flyspell-auto-correct-word) (keymap-set flyspell-mode-map "C-x c u" #'flyspell-auto-correct-previous-word) (keymap-set flyspell-mode-map "C-x c l" #'flyspell-check-previous-highlighted-word) (keymap-set flyspell-mode-map "C-x c n" #'flyspell-goto-next-error) (keymap-set flyspell-mode-map "C-x c b" #'flyspell-buffer) (keymap-set flyspell-mode-map "C-x c d" #'ispell-change-dictionary) (keymap-set flyspell-mode-map "C-x c i" #'+flyspell-add-word-to-dictionary)) #+end_src ** Dired #+begin_src elisp :noweb yes (with-eval-after-load 'dired <>) #+end_src Dired settings. #+begin_src elisp :tangle no :noweb-ref dired-settings (setq-default dired-guess-shell-alist-user '(("\\.pdf$" "zathura")) dired-listing-switched "-AFhlv --group-directories-first") #+end_src Auto enable omit mode on Dired startup. #+begin_src elisp :tangle no :noweb-ref dired-settings (add-hook 'dired-mode-hook (lambda () (dired-omit-mode) (toggle-truncate-lines 1))) #+end_src Add shred function. #+begin_src elisp :tangle no :noweb-ref dired-settings (if (executable-find "shred") (defun +dired-shred-file () "Shred marked files or a file at point in Dired." (interactive) (mapc (lambda (file) "Run the shred shell command on FILE." (if (file-regular-p file) (when (yes-or-no-p (format "Shred %s?" file)) (shell-command (format "shred -u \"%s\"" file))) (error "Aborting shred procedure; \"%s\" is not a file!" file))) (dired-get-marked-files))) (error "Shred command not found!")) #+end_src Add additional dired keybindings. #+begin_src elisp :tangle no :noweb-ref dired-settings (keymap-set dired-mode-map "b" #'dired-up-directory) (keymap-set dired-mode-map "z" #'+dired-shred-file) #+end_src Allow changing file permissions when in a writable dired buffer. #+begin_src elisp :tangle no :noweb-ref dired-settings (with-eval-after-load 'wdired (setq-default wdired-allow-to-change-permissions t)) #+end_src ** Search & Replace Case-sensitive search by default and show match count in minibuffer. #+begin_src elisp (with-eval-after-load 'isearch (setq-default case-fold-search nil isearch-lazy-count t)) #+end_src ** Org #+begin_src elisp :noweb yes (with-eval-after-load 'org <> <>) #+end_src Hide emphasis markers and set ellipse symbol. #+begin_src elisp :tangle no :noweb-ref org-settings (setq-default org-ellipsis "⤵" org-hide-emphasis-markers t) #+end_src Enable checkbox dependency checking. #+begin_src elisp :tangle no :noweb-ref org-settings (setq-default org-enforce-todo-dependencies t org-enforce-todo-checkbox-dependencies t org-checkbox-hierarchical-statistics nil) #+end_src Try to get the width from attribute tags or fall-back to original image width. Also redisplay updated images on block execution. #+begin_src elisp (setq-default org-image-actual-width nil) (add-hook 'org-babel-after-execute-hook (lambda () "Redisplay Org inline images." (when org-inline-image-overlays (org-redisplay-inline-images)))) #+end_src Use custom IDs for links. #+begin_src elisp (with-eval-after-load 'org-id (setq-default org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)) #+end_src Set org column format. #+begin_src elisp (setq-default org-columns-default-format "%70ITEM %TODO %10CLOCKSUM %16DEADLINE") #+end_src Enable notes for various actions. #+begin_src elisp (setq-default org-log-done 'time org-log-reschedule 'note org-log-redeadline 'note org-log-into-drawer t org-log-note-clock-out t) #+end_src *** Org Speed Commands #+begin_src elisp :noweb yes (with-eval-after-load 'org-keys <>) #+end_src Enable org speed commands. #+begin_src elisp (setq-default org-use-speed-commands t) #+end_src Add advice to prompt for confirmation before commenting or cutting subtree. #+begin_src elisp (advice-add 'org-cut-subtree :before-while (lambda (&rest _) "Prompts for confirmation before cutting subtree." (let ((heading (org-entry-get nil "ITEM"))) (y-or-n-p (format "Cut \"%s\" subtree?" heading))))) (advice-add 'org-toggle-comment :before-while (lambda (&rest _) "Prompt for confirmation before commenting a subtree." (let ((heading (org-entry-get nil "ITEM"))) (y-or-n-p (format "Toggle comment on \"%s\" subtree?" heading))))) #+end_src Alternative comment key =;= added to be consistent with ~org-toggle-comment~ (=C-c ;=). #+begin_src elisp :tangle no :noweb-ref org-speed-command-settings (dolist (key '(("P" . org-set-property) ("$" . org-archive-subtree-default-with-confirmation) ("q" . org-set-tags-command) ("]" . org-shiftright) ("[" . org-shiftleft) (";" . org-toggle-comment) ("z" . org-add-note) ("y" . org-copy-subtree))) (cl-pushnew key org-speed-commands)) #+end_src Add ='= key to edit org source blocks, which is consistent with ~org-edit-special~ (=C-c '=). #+begin_src elisp (cl-pushnew '("'" . org-edit-src-code) org-babel-key-bindings) #+end_src *** Org Babel :PROPERTIES: :header-args:elisp: :tangle no :noweb-ref org-babel-settings :END: Add new structure template shortcuts when inserting a block. #+begin_src elisp (cl-pushnew '("se" . "src elisp") org-structure-template-alist) #+end_src Add advice to automatically open an edit buffer when inserting a structure template. #+begin_src elisp (defadvice org-insert-structure-template (after edit-src activate compile) "Auto enter an edit buffer when inserted an Org block." (call-interactively 'org-edit-special)) #+end_src Enable supported org babel languages. #+begin_src elisp (dolist (lang '(shell C latex plantuml)) (cl-pushnew `(,lang . t) org-babel-load-languages)) #+end_src * Keybindings Unlock previously unusable keybinding. #+begin_src elisp (define-key input-decode-map [?\C-\[] (kbd "")) #+end_src ** Global Keybindings To prevent other packages overriding any custom global keybinding, all custom global keybinding are stored in a global minor mode. #+begin_src elisp (define-minor-mode +global-keys "Minor to store my custom global keybindings." :global t :lighter " +GKEYS" :keymap (make-sparse-keymap)) (+global-keys) #+end_src File actions. #+begin_src elisp (keymap-set +global-keys-map "C-x x R" #'rename-visited-file) (keymap-set +global-keys-map "C-x x D" #'delete-file) #+end_src Window actions. #+begin_src elisp (keymap-set +global-keys-map "M-o" #'other-window) (keymap-set +global-keys-map "M-O" #'window-swap-states) (keymap-set +global-keys-map "M-V" #'scroll-other-window-down) (keymap-set +global-keys-map "C-S-V" #'scroll-other-window) (keymap-set +global-keys-map "C-M-<" #'beginning-of-buffer-other-window) (keymap-set +global-keys-map "C-M->" #'end-of-buffer-other-window) (keymap-set +global-keys-map "C-M-]" #'next-buffer) (keymap-set +global-keys-map "M-ESC" #'previous-buffer) ; C-M-[ translates to M-ESC #+end_src Set meta-key quick actions to mirror =C-x DIGIT= bindings, therefore reducing the need for additional keypresses. These bindings override their corresponding numerical argument, however these can be can be alternatively called with =C-u DIGIT= or =C-DIGIT=. #+begin_src elisp (keymap-set +global-keys-map "C-M-0" #'kill-buffer-and-window) (keymap-set +global-keys-map "C-M--" #'kill-this-buffer) (keymap-set +global-keys-map "M-0" #'delete-window) (keymap-set +global-keys-map "M-1" #'delete-other-windows) (keymap-set +global-keys-map "M-2" #'split-window-below) (keymap-set +global-keys-map "M-3" #'split-window-right) (keymap-set +global-keys-map "M-4" #'ctl-x-4-prefix) (keymap-set +global-keys-map "M-5" #'ctl-x-5-prefix) (keymap-set +global-keys-map "M-6" (keymap-global-lookup "C-x 6")) (keymap-set +global-keys-map "M-7" (keymap-global-lookup "C-x 7")) (keymap-set +global-keys-map "M-8" (keymap-global-lookup "C-x 8")) (keymap-set +global-keys-map "M-9" (keymap-global-lookup "C-x 9")) #+end_src Tab actions. #+begin_src elisp (keymap-set +global-keys-map "C-x C-" #'tab-new) (keymap-set +global-keys-map "C-x C-" #'tab-close) (keymap-set +global-keys-map "C-x t l" #'tab-list) (keymap-set +global-keys-map "C-x t " #'toggle-frame-tab-bar) #+end_src Improve region text manipulation. #+begin_src elisp (keymap-set +global-keys-map "M-c" #'capitalize-dwim) (keymap-set +global-keys-map "M-l" #'downcase-dwim) (keymap-set +global-keys-map "M-u" #'upcase-dwim) (keymap-set +global-keys-map "M-U" #'upcase-char) #+end_src Help actions. #+begin_src elisp (keymap-set +global-keys-map "C-h M" #'describe-keymap) (keymap-set +global-keys-map "C-h j" #'describe-char) #+end_src Display modes. #+begin_src elisp (keymap-set +global-keys-map "C-x x c" #'display-fill-column-indicator-mode) (keymap-set +global-keys-map "C-x x l" #'display-line-numbers-mode) (keymap-set +global-keys-map "C-x x h" #'hl-line-mode) (keymap-set +global-keys-map "C-x x o" #'overwrite-mode) (keymap-set +global-keys-map "C-x x s" #'prettify-symbols-mode) (keymap-set +global-keys-map "C-x x w" #'visual-line-mode) (keymap-set +global-keys-map "C-x x SPC" #'whitespace-mode) #+end_src ** Diff-mode Rebind diff-mode's ~diff-goto-source~ keybinding due to conflict with =M-o=. #+begin_src elisp (with-eval-after-load 'diff-mode (keymap-set diff-mode-map "M-j" #'diff-goto-source)) #+end_src