#+title: Aner's Emacs Base Configuration #+property: header-args :results silent * Base setup ** Bootstrapping Partially based on [[https://stackoverflow.com/questions/151945/how-do-i-control-how-emacs-makes-backup-files][this post on StackOverflow]]. Setting backups, external custom file. #+begin_src emacs-lisp (setq vc-follow-symlinks t ;Follow symlinks ;Backup location TODO FIND IF RELEVANT FOR NIX backup-directory-alist `(("." . "~/.cache/emacs-backups/")) auto-save-file-name-transforms '((".*" "~/.cache/emacs-backups/" t)) backup-by-copying t ;Always copy never link delete-old-versions t ;Auto delete old backups kept-new-versions 6 ;Number of versions to keep kept-old-versions 2 version-control t ;Backups for files gc-cons-threshold 100000000 ;Setting garbage collection to 100M. ;Set external cusotm file custom-file (expand-file-name "custom.el" user-emacs-directory) ) #+end_src Load custom file #+begin_src emacs-lisp (if (file-exists-p custom-file) (load custom-file)) #+end_src ** Utility functions *** CL CL is a base library that has a bunch of useful stuff, primarily for lists. We are using the built-in version, no need to pull from anywhere. This just makes sure the code is loaded early on for later use. #+begin_src emacs-lisp (require 'cl-lib) #+end_src *** Copy file name Defining a function to copy filename. #+begin_src emacs-lisp (defun azos/copy-file-name () (interactive) (let ((fpath buffer-file-name)) (if fpath (kill-new fpath) (message "No current file!")))) #+end_src *** Font candidate #+begin_src emacs-lisp (defun azos/font-candidate (&rest fonts) "Return existing font which first match." (cl-find-if (lambda (f) (find-font (font-spec :name f))) fonts)) #+end_src *** Regex match function #+begin_src emacs-lisp (defun azos/re-seq (regexp string) "Get a list of all regexp matches in a string" (save-match-data (let ((pos 0) matches) (while (string-match regexp string pos) (push (match-string 0 string) matches) (setq pos (match-end 0))) matches))) #+end_src ** Keymap setup In this section global keybindings are defined using a global minor mode. First, utility functions that will be bound are defined. The first, =azos/set-window-width= is a helper function that resizes a window. Used because I wanted a function that resizes a window to 85 cols easily. The second, =azos/open-conf-file=, opens the configuration file. #+begin_src emacs-lisp (defun azos/set-window-width (n) (adjust-window-trailing-edge (selected-window) ( - n (window-width)) t)) (defun azos/open-conf-file () (interactive) (find-file (concat user-emacs-directory "config.org"))) #+end_src Now let's define keybindings. To start, we'd like =M-o= to be available to us, so let's unbind it. #+begin_src emacs-lisp ;Unbind face menu map (define-key global-map (kbd "M-o") nil) #+end_src This creates an "open keymap", a bunch of keybindings we'll use to open basic applications and files. This will be mapped to =M-o=, and things will be opened form this sub-menu. We'll start it with a binding to open the conf file with =M-o o= (MOO!) Setting of keybindings based on [[https://stackoverflow.com/questions/49853494/the-best-way-to-set-a-key-to-do-nothing][this]] #+begin_src emacs-lisp ;We'll define a basic keymap and already load window-manip funcs (defvar azos/global-minor-mode/open-keymap (let ((map (make-sparse-keymap))) (define-key map (kbd "o") 'azos/open-conf-file) map) "global keymap for opening stuff on azos") #+end_src At this stage a minor-mode-map is defined with keybindings, and an accompanying minor-mode is added. #+begin_src emacs-lisp (defvar azos/global-minor-mode/keymap (let ((map (make-sparse-keymap))) ;Window movement and manipulation (define-key map (kbd "M-h") 'windmove-left) (define-key map (kbd "M-l") 'windmove-right) (define-key map (kbd "M-k") 'windmove-up) (define-key map (kbd "M-j") 'windmove-down) (define-key map (kbd "M-") 'windmove-left) (define-key map (kbd "M-") 'windmove-right) (define-key map (kbd "M-") 'windmove-up) (define-key map (kbd "M-") 'windmove-down) (define-key map (kbd "M-d M-d") 'delete-window) (define-key map (kbd "M-d D") 'kill-buffer-and-window) (define-key map (kbd "M-\\") 'split-window-horizontally) (define-key map (kbd "M-\-") 'split-window-vertically) (define-key map (kbd "M-d R") (lambda () (interactive) (set-window-width 85))) (define-key map (kbd "M-o") azos/global-minor-mode/open-keymap) map) "azos/global-minor-mode keymap.") (define-minor-mode azos/global-minor-mode "A minor mode for azos global keymaps." :init-value t :lighter "azos" :keymap azos/global-minor-mode/keymap) (azos/global-minor-mode 1) #+end_src This keymap will be referenced many times during this document at relevant points. Keymaps are included with relevant sections. ** EVIL mode This section binds keys for changing window size. Done here because can only do after evil loads. #+begin_src emacs-lisp (setq evil-want-keybinding nil) (use-package evil :init (setq evil-want-C-i-jump nil) :config (require 'evil ) (evil-mode 1) :bind (:map azos/global-minor-mode/keymap ("M-w h" . evil-window-decreace-width) ("M-w l" . evil-window-increase-width) ("M-w k" . evil-window-decrease-height) ("M-w j" . evil-window-increase-height)) ) #+end_src Loading evil collection. Functions from this package will be referenced many times later in the configuration. #+begin_src emacs-lisp (use-package evil-collection :config (setq evil-collection-setup-minibuffer t) ) #+end_src #+begin_src emacs-lisp (defvar azos/evil-color-normal "LightGoldenrod1") (defvar azos/evil-color-emacs "LightBlue1") (defvar azos/evil-color-insert "PaleGreen1") (defvar azos/evil-color-replace "LightPink") (defvar azos/evil-color-motion "LightCyan") (defvar azos/evil-color-visual "LightGray") (defvar azos/evil-color-operate "sandy brown") #+end_src ** IVY Enabling IVY. Taken from [[https://github.com/abo-abo/swiper][their website]]. Using ivy, hydra, counsel. #+begin_src emacs-lisp (use-package ivy :custom (ivy-use-virtual-buffers t) (enable-recursive-minibuffers t) (ivy-count-format "(%d/%d) ") :config (ivy-mode 1) ) (use-package ivy-hydra :after ivy) (use-package ivy-avy :after ivy) (use-package counsel :after ivy :bind (:map azos/global-minor-mode/keymap ("M-i" . counsel-imenu) ("M-b" . counsel-switch-buffer) ("C-x C-f" . counsel-find-file)) (:map azos/global-minor-mode/open-keymap ("l" . counsel-linux-app)) ) #+end_src Using swiper. Replacing evil search with swiper search. #+begin_src emacs-lisp (use-package swiper :after ivy evil :config (setq evil-search-module 'swiper-isearch) :bind (:map azos/global-minor-mode/keymap ("C-s" . swiper-isearch)) ) #+end_src Setting up keymaps #+begin_src emacs-lisp (evil-collection-ivy-setup) #+end_src ** Assorted utility functions * UI ** General *** Clean UI Disabling the toolbar, the splash-screen, the menu-bar and the scroll-bar #+begin_src emacs-lisp (menu-bar-mode -1) ; no menu bar (when (display-graphic-p) (tool-bar-mode -1) ; no tool bar with icons (scroll-bar-mode -1) ; no scroll bars (set-fringe-mode 0)) #+end_src *** Background color #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(background-color . "LightYellow")) #+end_src *** Fringe color While we don't actually want fringes (almost at all), some frames use them. #+begin_src emacs-lisp (set-face-attribute 'fringe nil :background "LemonChiffon1") #+end_src *** Window dividers #+begin_src emacs-lisp (setq window-divider-default-bottom-width 1 window-divider-default-places 'bottom-only) (window-divider-mode 1) #+end_src *** Easy Prompt #+begin_src emacs-lisp (defalias 'yes-or-no-p 'y-or-n-p) #+end_src *** Minibuff #+begin_src emacs-lisp (add-hook 'minibuffer-setup-hook (lambda () (make-local-variable 'face-remapping-alist) (add-to-list 'face-remapping-alist '(default (:background "WhiteSmoke"))))) #+end_src *** Bell Disable bell, who needs the bell? #+begin_src emacs-lisp (setq ring-bell-function (lambda () ())) #+end_src *** Olivetti Useful to have even if I rarely use it. #+begin_src emacs-lisp (use-package olivetti :init (setq olivetti-body-width 96)) #+end_src *** Modeline Setting colors #+begin_src emacs-lisp (set-face-attribute 'mode-line nil :box nil :background "AliceBlue") (set-face-attribute 'mode-line-inactive nil :box nil :background "LightYellow3") #+end_src We use [[https://emacs.stackexchange.com/questions/5529/how-to-right-align-some-items-in-the-modeline][this stackoverflow page]] to make left\right aligned stuff. We use [[https://www.reddit.com/r/emacs/comments/4mhphb/spacemacs_how_to_limit_the_length_of_displayed/][this article]] to try and limit the mode name length. #+begin_src emacs-lisp (setq evil-normal-state-tag (propertize " NORMAL " 'face (list :background azos/evil-color-normal)) evil-emacs-state-tag (propertize " EMACS " 'face (list :background azos/evil-color-emacs)) evil-insert-state-tag (propertize " INSERT " 'face (list :background azos/evil-color-insert)) evil-replace-state-tag (propertize " REPLACE " 'face (list :background azos/evil-color-replace)) evil-motion-state-tag (propertize " MOTION " 'face (list :background azos/evil-color-motion)) evil-visual-state-tag (propertize " VISUAL " 'face (list :background azos/evil-color-visual)) evil-operator-state-tag (propertize " OPERATE " 'face (list :background azos/evil-color-operate))) (defun azos/modeline/modeline-render (left right) "Return a string of `window-width' length containing LEFT, and RIGHT aligned respectively." (let* ((available-width (- (window-width) (length left) 2))) (format (format " %%s %%%ds " available-width) left right))) (setq-default mode-line-buffer-identification (list -80 (propertized-buffer-identification "%12b"))) (setq-default mode-line-format '((:eval (azos/modeline/modeline-render ;;Left (concat (propertize (format-mode-line "%b") 'face '((:foreground "maroon"))) (format-mode-line " (%m) ")) ;;Right (concat (format-mode-line "%5lL%4cC ") evil-mode-line-tag))))) #+end_src *** Notifications #+begin_src emacs-lisp (require 'notifications) #+end_src *** Which-Key #+begin_src emacs-lisp (use-package which-key :config (which-key-mode)) #+end_src ** Text *** YASnippet Loading yasnippet. Useful for snippeting. Mode-specific snippets defined in relevant sections. #+begin_src emacs-lisp (use-package yasnippet :config (yas-global-mode 1) ) #+end_src *** Text font This section configures the base fonts. We select fonts if available (have configurations for good defaults in Linux and Windows). Also setting default fixed-pitch and variable-pitch fonts. Setting font size to 10. The value to place is font-size * 10 Font size 12 for variable pitch. The function =font-candidate= is from https://www.gnu.org/software/emacs/manual/html_mono/cl.html. #+begin_src emacs-lisp (let ((variable-font (azos/font-candidate "Liberation Serif" "Microsoft Sans Serif"))) (if variable-font (set-face-attribute 'variable-pitch nil :font variable-font))) (let ((fixed-font (azos/font-candidate "Source Code Pro" "LiberationMono" "Consolas"))) (if fixed-font (progn (set-face-attribute 'default nil :font fixed-font) (set-face-attribute 'fixed-pitch nil :font fixed-font)))) (set-face-attribute 'default nil :height 100) (set-face-attribute 'variable-pitch nil :height 130 :weight 'normal :width 'normal) (set-face-attribute 'fixed-pitch nil :height 100 :weight 'normal :width 'normal) (defun azos/default-variable-pitch () (face-remap-add-relative 'default '(:inherit 'variable-pitch))) #+end_src *** Line numbering We want line numbering, but only in modes where it makes sense. To do this, a custom minor-mode, =azos/global-linum-mode=, is created. This mode selectively activates linum-mode if the mode is not one of a selected exempt modes. These exempt modes are defined in =display-line-numbers-exempt-modes=. Taken from [[https://www.emacswiki.org/emacs/LineNumbers][this wiki entry]]. #+begin_src emacs-lisp (use-package display-line-numbers :init (defcustom azos/display-line-numbers-exempt-modes '(vterm-mode eshell-mode shell-mode term-mode ansi-term-mode magit-mode magit-diff-mode notmuch-hello pdf-view-mode) "Major modes on which to disable the linum mode, exempts them." :group 'display-line-numbers :type 'list :version "green") (define-global-minor-mode azos/global-linum-mode display-line-numbers-mode (lambda () (if (and (not (apply 'derived-mode-p azos/display-line-numbers-exempt-modes)) (not (minibufferp))) (display-line-numbers-mode)))) (setq display-line-numbers-type 'visual display-line-numbers-grow-only 1 display-line-numbers-width-start 1) :config (azos/global-linum-mode 1) (set-face-attribute 'line-number nil :family (face-attribute 'fixed-pitch :family)) ) #+end_src *** Line highlight Highlighting line with cursor. Modification done to use EVIL colors on highlighted line. #+begin_src emacs-lisp (global-hl-line-mode) (set-face-attribute 'hl-line nil :background azos/evil-color-emacs) (defface hl-line-normal (list (list t (list :inherit 'hl-line :background azos/evil-color-normal :extend t))) "Highlight face for evil normal mode." :group 'hl-line) (defface hl-line-insert (list (list t (list :inherit 'hl-line :background azos/evil-color-insert :extend t))) "Highlight face for evil insert mode." :group 'hl-line) (defface hl-line-emacs (list (list t (list :inherit 'hl-line :background azos/evil-color-emacs :extend t))) "Highlight face for evil insert mode." :group 'hl-line) (defface hl-line-replace (list (list t (list :inherit 'hl-line :background azos/evil-color-replace :extend t))) "Highlight face for evil insert mode." :group 'hl-line) (defface hl-line-motion (list (list t (list :inherit 'hl-line :background azos/evil-color-motion :extend t))) "Highlight face for evil insert mode." :group 'hl-line) (defface hl-line-visual (list (list t (list :inherit 'hl-line :background azos/evil-color-visual :extend t))) "Highlight face for evil insert mode." :group 'hl-line) (defface hl-line-operate (list (list t (list :inherit 'hl-line :background azos/evil-color-operate :extend t))) "Highlight face for evil insert mode." :group 'hl-line) (defun azos/hl-line-evil/set-hl-state (state-face) "Refresh hl-line to be state-face" (progn (global-hl-line-unhighlight) (setq-local hl-line-face state-face) (global-hl-line-highlight))) #+end_src #+begin_src emacs-lisp (add-hook 'evil-insert-state-entry-hook (lambda () (azos/hl-line-evil/set-hl-state 'hl-line-insert))) (add-hook 'evil-normal-state-entry-hook (lambda () (azos/hl-line-evil/set-hl-state 'hl-line-normal))) (add-hook 'evil-emacs-state-entry-hook (lambda () (azos/hl-line-evil/set-hl-state 'hl-line-emacs))) (add-hook 'evil-replace-state-entry-hook (lambda () (azos/hl-line-evil/set-hl-state 'hl-line-replace))) (add-hook 'evil-motion-state-entry-hook (lambda () (azos/hl-line-evil/set-hl-state 'hl-line-motion))) (add-hook 'evil-visual-state-entry-hook (lambda () (azos/hl-line-evil/set-hl-state 'hl-line-visual))) (add-hook 'evil-operate-state-entry-hook (lambda () (azos/hl-line-evil/set-hl-state 'hl-line-operate))) #+end_src *** Line wrap Don't want to have to scroll to see more chars. #+begin_src emacs-lisp (global-visual-line-mode t) #+end_src *** Parenthesis Highlight matching parenthesis #+begin_src emacs-lisp (show-paren-mode 1) #+end_src *** Tabs Using spaces instead of tabs, default offset is 4. #+begin_src emacs-lisp (setq-default indent-tabs-mode nil tab-width 4 c-basic-offset 4 tab-always-indent 'complete) #+end_src *** BIDI and lang Setting up Hebrew as alternative input, using bidi mode so that every line is aligned left\right accordingly. #+begin_src emacs-lisp (setq-default default-input-method "hebrew" bidi-display-reordering t bidi-paragraph-direction 'nil) (defun azos/set-bidi-env () (setq bidi-paragraph-direction 'nil) ) (define-key azos/global-minor-mode/keymap (kbd "C-SPC") 'toggle-input-method) #+end_src *** Whitespace mode We define a custom global-whitespace-mode in order to enable it only on relevant modes. We check if the current mode doesn't derive from a set of blacklisted mode, the main culprit being terminal modes where whitespace occur naturally and are a pain to see all the time. #+begin_src emacs-lisp (setq-default whitespace-style '(face tabs trailing tab-mark lines-tail indentation)) (defun azos/whitespace-mode-func () (interactive) (if (derived-mode-p 'text-mode 'prog-mode 'org-mode) (whitespace-mode 1) (whitespace-mode -1))) (add-hook 'after-change-major-mode-hook 'azos/whitespace-mode-func) #+end_src *** Commenter Quick keybindings to comment out regions. #+begin_src emacs-lisp (use-package evil-nerd-commenter :config (define-key evil-normal-state-map (kbd "C-;") 'evilnc-comment-or-uncomment-lines)) #+end_src *** Company mode Auto completion framework. #+begin_src emacs-lisp (use-package company :ensure t :defer t :init (add-hook 'after-init-hook 'global-company-mode) :config ;; (use-package company-irony :ensure t :defer t) (setq company-minimum-prefix-length 2 company-show-numbers t company-tooltip-limit 20 company-idle-delay 0.2 ) :bind ("C-;" . company-complete-common) ;; :hook (irony-mode . company-mode) ) #+end_src *** Folding #+begin_src emacs-lisp (add-hook 'prog-mode-hook 'hs-minor-mode) #+end_src * Mode specific ** Undo tree Loading =undo-tree= for undo/redo functionality with evil. Redo taken from https://github.com/syl20bnr/spacemacs/issues/14036 #+begin_src emacs-lisp (use-package undo-tree :after evil :config (evil-set-undo-system 'undo-tree) (setq undo-tree-history-directory-alist (list (cons "." (concat user-emacs-directory "undo-tree")))) (global-undo-tree-mode 1) ) #+end_src ** Projectile Startup up projectile. A config line here disables modeline display because I don't want my modeline to be cluttered. Mapping modeline commands to =M-p= prefix. Also adding a shortcut to add project. #+begin_src emacs-lisp (use-package projectile :config (projectile-mode +1) (setq projectile-mode-line-function (lambda () "")) :bind (:map projectile-command-map ("a" . projectile-add-known-project) ) (:map azos/global-minor-mode/keymap ("M-p" . projectile-command-map)) ) #+end_src Ivy for projectile: Parts taken from [[https://emacs.stackexchange.com/questions/40787/display-corresponding-key-binding-of-command-during-m-x-completion][this post]] and [[https://emacs.stackexchange.com/questions/38841/counsel-m-x-always-shows][this post]] from StackOverflow. Helps with many functions to use counsel's/ivy's autocomplete with projectile. #+begin_src emacs-lisp (use-package counsel-projectile :after counsel projectile :config (counsel-projectile-mode +1) (setq projectile-completion-system 'ivy) ;Making counsel start with empty regex (when (commandp 'counsel-M-x) (global-set-key [remap execute-extended-command] 'counsel-M-x)) (setcdr (assoc 'counsel-M-x ivy-initial-inputs-alist) "") ) #+end_src ** Tramp Ensuring tramp is loaded, and loading counsel-tramp for easy tramping. #+begin_src emacs-lisp (use-package tramp :straight (:type built-in)) (use-package counsel-tramp) #+end_src ** Dired Need to autoload dired-x for dired-omit #+begin_src emacs-lisp ;; (autoload 'dired-omit-mode "dired-x") (setq dired-omit-files "^\\...+$") (add-hook 'dired-mode-hook 'dired-omit-mode) (add-hook 'dired-mode-hook 'dired-hide-details-mode) (evil-collection-dired-setup) #+end_src #+begin_src emacs-lisp (use-package dired-subtree :config (evil-collection-define-key 'normal 'dired-mode-map (kbd "SPC") 'dired-subtree-toggle (kbd "TAB") 'dired-subtree-cycle ) (setq dired-subtree-use-backgrounds nil) ;Evil collection binds these keys, we need them for window movement (evil-collection-define-key 'normal 'dired-mode-map (kbd "M-j") nil (kbd "M-k") nil) ) #+end_src ** Magit #+begin_src emacs-lisp (use-package magit :config (evil-collection-magit-setup) :bind (:map azos/global-minor-mode/open-keymap ("g" . 'magit-status)) ) #+end_src ** Org *** Base #+begin_src emacs-lisp (require 'org-faces) (defun azos/set-org-mode-fixed-pitch-faces () (mapc (lambda (face) (set-face-attribute face nil :font (face-attribute 'fixed-pitch :font) :height (face-attribute 'fixed-pitch :height))) `(line-number org-block org-special-keyword org-drawer org-todo org-done org-priority org-checkbox org-block-end-line org-block-begin-line org-table org-verbatim))) (use-package org :hook (org-mode . variable-pitch-mode) (org-mode . azos/set-bidi-env) (org-mode . (lambda () (setq-local whitespace-style '(face tabs trailing tab-mark indentation)))) :config (azos/set-org-mode-fixed-pitch-faces) (setq org-src-tab-acts-natively t org-adapt-indentation nil org-startup-folded t org-hide-emphasis-markers t) (set-face-attribute 'org-code nil :family (face-attribute 'fixed-pitch :family)) (set-face-attribute 'org-block nil :family (face-attribute 'fixed-pitch :family)) :bind ("C-a" . nil) ("C-a l" . org-toggle-latex-fragment) ) #+end_src #+end_src *** Code blocks The following displays the contents of code blocks in Org-mode files using the major-mode of the code. It also changes the behavior of TAB to as if it were used in the appropriate major mode. #+begin_src emacs-lisp (setq org-src-fontify-natively t org-src-tab-acts-natively t org-src-preserve-indentation t) (set-face-attribute 'org-block nil :background "LemonChiffon1") (set-face-attribute 'org-block-begin-line nil :background "LightYellow2") (set-face-attribute 'org-block-end-line nil :background "LightYellow2") #+end_src *** Babel Define languages to use #+begin_src emacs-lisp (require 'ob) (require 'ob-tangle) ;; TODO Not sure I like this here (org-babel-do-load-languages 'org-babel-load-languages '((shell . t) (emacs-lisp . t) (python . t) (org . t) (lilypond . t) (latex . t) (js . t) (java . t) (dot . t) (C . t))) ;; TODO move these around ;; (add-to-list 'org-src-lang-modes (quote ("dot". graphviz-dot))) ;; (add-to-list 'org-src-lang-modes (quote ("plantuml" . fundamental))) ;; (add-to-list 'org-babel-tangle-lang-exts '("clojure" . "clj")) #+end_src This section makes code-indentation correction work inside source blocks. Taken from: https://github.com/emacs-evil/evil/issues/1288 #+begin_src emacs-lisp (defun azos/org/evil-org-insert-state-in-edit-buffer (fun &rest args) "Bind `evil-default-state' to `insert' before calling FUN with ARGS." (let ((evil-default-state 'insert) ;; Force insert state evil-emacs-state-modes evil-normal-state-modes evil-motion-state-modes evil-visual-state-modes evil-operator-state-modes evil-replace-state-modes) (apply fun args) (evil-refresh-cursor))) (advice-add 'org-babel-do-key-sequence-in-edit-buffer :around #'azos/org/evil-org-insert-state-in-edit-buffer) #+end_src * Provide #+begin_src emacs-lisp (provide 'azos-emacs-base) #+end_src