Files
opalfiles/Emacs.org

18 KiB

Emacs Configuration

Configuration

Package Management

I use straight.el for managing packages as it's fairly simple and robust. At some point I'd like to move to primarily using Guix for Emacs package management.

Bootstraping straight.el

(setq package-enable-at-startup nil)

;; * Package Management with straight.el
(unless (featurep 'straight)
  ;; Bootstrap straight.el
  (defvar bootstrap-version)
  (let ((bootstrap-file
         (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
        (bootstrap-version 5))
    (unless (file-exists-p bootstrap-file)
      (with-current-buffer
          (url-retrieve-synchronously
           "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
           'silent 'inhibit-cookies)
        (goto-char (point-max))
        (eval-print-last-sexp)))
    (load bootstrap-file nil 'nomessage)))

;; Use straight.el for use-package expressions
(straight-use-package 'use-package)

(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("org" . "https://orgmode.org/elpa/")
                         ("elpa" . "https://elpa.gnu.org/packages/")))

General

User Interface

(scroll-bar-mode -1)                 ;; Disable scroll bar
(tool-bar-mode -1)                   ;; Disable tool bar menu
(tooltip-mode -1)                    ;; Disable tooltips
(set-fringe-mode 10)                 ;; Disable fringe mode
(menu-bar-mode -1)                   ;; Disable menu bar
(global-display-line-numbers-mode t) ;; Display line numbers
(set-default 'truncate-lines nil)    ;; Wrap lines at end of screen
(setq visible-bell t)                ;; Visible flash to represent a bell
(setq x-select-enable-clipboard t)   ;; Enable clipboard
(global-tab-line-mode 1)
(setq inhibit-startup-message t)  

;; Disable line mode for specific major/minor modes.
(dolist (mode '(org-mode-hook))
    'term-mode-hook
    'shell-mode-hook
    'eshell-mode-hook
  (add-hook mode (lambda () (display-line-numbers-mode 0))))

Modeline

Doom's modeline is excellent, and much less cluttered than the default Emacs modeline.

(use-package doom-modeline
  :straight t
  :init (doom-modeline-mode 1)
  :custom ((doom-modeline-height 15)))

;; install all-the-icons when first loading the emacs conf
;; remember to run M-x all-the-icons-install-fonts
(when (display-graphic-p)
  (use-package all-the-icons :straight t))

Keybinding display

Which-key is a package that displays the available commands granted that it's incomplete. For instance, if I type "SPC", I will see all of the commands available to the leader key.

(use-package which-key
  :straight t
  :defer 0
  :diminish which-key-mode
  :config
  (which-key-mode)
  (setq which-key-idle-delay 0.3))

Notifications

Alerts

Minimal alerts so I can see notifications from various programs in Emacs.

(use-package alert
  :straight t
  :config
  (setq alert-default-style 'notification))
Disable Warnings
(setq large-file-warning-threshold nil) ;; Disables warnings for large files
(setq vc-follow-symlinks t)             ;; Disables warnings for symlinks

Helpful Describe Functionality

Helpful.el is a package that reformats the describe-X functions to be much more informative.

(use-package helpful
  :straight t
  :commands (helpful-callable helpful-variable helpful-command helpful-key)
  :custom
  (counsel-describe-function-function #'helpful-callable)
  (counsel-describe-variable-function #'helpful-variable)
  :bind
  ([remap describe-function] . counsel-describe-function)
  ([remap describe-command] . helpful-command)
  ([remap describe-variable] . counsel-describe-variable)
  ([remap describe-key] . helpful-key))

Autosaves

(use-package super-save
  :straight t
  :config 
  (super-save-mode +1))

;; Auto reverting Changed Files
(setq global-auto-revert-non-file-buffers nil)
(global-auto-revert-mode -1)

Theme

I really enjoy Modus Themes by Protesilaos Stavrou. They are minimal, high contrast, and easy on the eyes.

(use-package modus-themes
  :straight t
  :init
  (setq modus-themes-italic-constructs t
        modus-themes-bold-constructs nil
        modus-themes-region '(accented bg-only no-extend)
        modus-themes-org-blocks 'greyscale
        modus-themes-paren-match 'intense
        modus-themes-mixed-fonts t)
  (modus-themes-load-themes)
  :config
  (modus-themes-load-vivendi)
  :bind
  ("<f5>" . modus-themes-toggle))

;; ;; Doing some doom themes for a while.
;; (use-package doom-themes
;;   :straight t
;;   :init
;;   (load-theme 'doom-gruvbox))

Fonts

Mozilla's Fira fonts are pretty, and the Sans and Code types look excellent with the corresponding mode.

(set-face-attribute 'default nil :font "Fira Code" :height 125 :weight 'medium)
(set-face-attribute 'variable-pitch nil :font "Fira Sans" :height 1.0 :weight 'regular)
(set-face-attribute 'fixed-pitch nil :font "Fira Code" :height 1.0 :weight 'medium)

Keybinding

I came from vim, so naturally I use Evil keybindings. I also used to use Doom Emacs before rolling my own configuration so I've borrowed some ideas from my time spent there as well. Between the two paradigms I rarely leave the home row which is ideal for typing for extended periods of time.

General.el (leader-key keybindings)

I really enjoy having a leader key and eschewing the Control and Alt keys entirely. Doom had far too many leader keybindings that I never used so I've boiled them down to the essentials that I used the most.

(global-set-key (kbd "<escape>") 'keyboard-escape-quit) ;; Escape exits out of prompts

  ;; Define Leader Key
(use-package general
  :straight t
  :config
  (general-evil-setup t)
  (general-create-definer opal/leader-keys
    :keymaps '(normal insert visual emacs)
    :prefix "SPC"
    :global-prefix "C-SPC")
    ;; Define Keybindings (potentially move into own file)
  (opal/leader-keys
      ;; Leader-map
    ";" #'pp-eval-expression        ;; Eval expression
    ":" #'execute-extended-command  ;; Eq to M-x
    "u" #'universal-argument        ;; Universal argument
    "w" #'evil-window-map           ;; Window functions
    "." #'find-file                 ;; Find file (opens in mini-buffer)
    "," #'persp-switch-to-buffer    ;; Switch to perspective.el minibuffer
    "<" #'switch-to-buffer          ;; Switch to non-perspective.el minibuffer
    "s" #'swiper                    ;; Search in current buffer

    ;; Magit
    "gg" #'magit-status
    "gi" #'magit-init
    "gc" #'magit-clone

    ;; Buffers
    "bd" #'kill-buffer

    ;; Terminal/Shell
    "tt" #'term
    "te" #'eshell

    ;; Org
    ; Babel
    "obt" #'org-babel-tangle
    ; Gen
    "ol" #'org-insert-link
    "or" #'org-reload
    ; Agenda
    "oa" #'org-agenda

    ;; Eval
    "eb" #'eval-buffer
    "er" #'eval-region
    "ef" #'eval-defun
    "el" #'eval-last-sexp

    ;; Describe
    "df" #'describe-function
    "dv" #'describe-variable
    "ds" #'describe-symbol
    "dk" #'describe-key
    "dp" #'describe-package))

Evil

(use-package evil
  :straight t
  :init
  (setq evil-want-integration t)
  (setq evil-want-keybinding nil)
  (setq evil-want-C-u-scroll t)
  (setq evil-want-C-i-jump nil)
  (setq evil-respect-visual-line-mode t)
  :config
  (evil-mode 1)
  (define-key evil-insert-state-map (kbd "C-g") 'evil-normal-state)
  (define-key evil-insert-state-map (kbd "C-h") 'evil-delete-backward-char-and-join)

  ;; Use visual line motions even outside of visual-line-mode buffers
  (evil-global-set-key 'motion "j" 'evil-next-visual-line)
  (evil-global-set-key 'motion "k" 'evil-previous-visual-line)

  (evil-set-initial-state 'messages-buffer-mode 'normal)
  (evil-set-initial-state 'dashboard-mode 'normal))

(use-package evil-collection
  :straight t
  :after evil
  :config
  (evil-collection-init))

Completion Frameworks

At the moment, I'm trying out Ivy and counsel but I've used Vertico and liked it as well.

Ivy

(use-package ivy
  :straight t
  :diminish
  :bind (("C-s" . 'swiper)
         :map ivy-minibuffer-map
         ("TAB" . ivy-alt-done)
         ("C-l" . ivy-alt-done)
         ("C-j" . ivy-next-line)
         ("C-k" . ivy-previous-line)
         :map ivy-switch-buffer-map
         ("C-k" . ivy-previous-line)
         ("C-l" . ivy-done)
         ("C-d" . ivy-reverse-i-search-kill))
  :config
  (ivy-mode 1))

(use-package ivy-rich
  :straight t
  :after ivy
  :init (ivy-rich-mode 1))

Counsel

(use-package counsel
  :straight t
  :bind
  (("C-M-j" . 'counsel-switch-buffer)
   :map minibuffer-local-map
   ("C-r" . 'counsel-minibuffer-history))

  :custom
  (counsel-linux-app-format-function #'counsel-linux-app-format-function-name-only)
  :config
  (counsel-mode 1))

Org

General Configuration

;; Set org agenda dir
(setq org-directory "~/Org/")

;; Tell Org to stop indenting inside of org source blocks.
(setq org-edit-src-content-indentation 0)

;; Open links in browser
(setq browse-url-browser-function 'browse-url-generic
      browse-url-generic-program "firefox")

(defun opal/org-font-setup ()
  ;; Replace list hyphen with dot
  (font-lock-add-keywords 'org-mode
                          '(("^ *\\([-]\\) "
                             (0 (prog1 () (compose-region (match-beginning 1) (match-end 1)
                                                          "•"))))))

  ;; Set faces for heading levels
  (dolist (face '((org-level-1 . 1.2)
                  (org-level-2 . 1.1)
                  (org-level-3 . 1.05)
                  (org-level-4 . 1.0)
                  (org-level-5 . 1.1)
                  (org-level-6 . 1.1)
                  (org-level-7 . 1.1)
                  (org-level-8 . 1.1)))
    (set-face-attribute (car face) nil :font "Cantarell" :weight 'regular :height (cdr face)))

  ;; Ensure that anything that should be fixed-pitch in Org files appears that way
  (set-face-attribute 'org-block nil    :foreground nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-table nil    :inherit 'fixed-pitch)
  (set-face-attribute 'org-formula nil  :inherit 'fixed-pitch)
  (set-face-attribute 'org-code nil     :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-table nil    :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-meta-line nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-checkbox nil  :inherit 'fixed-pitch)
  (set-face-attribute 'line-number nil :inherit 'fixed-pitch)
  (set-face-attribute 'line-number-current-line nil :inherit 'fixed-pitch))


(defun opal/org-mode-setup ()
  (org-indent-mode)
  (variable-pitch-mode 1)
  (visual-line-mode 1)
  (setq org-startup-folded t))

(use-package org
  :straight t
  :commands (org-capture org-agenda)
  :hook (org-mode . opal/org-mode-setup)
  :config
  (setq org-ellipsis " ▾")
  (setq org-agenda-start-with-log-mode t)
  (setq org-log-done 'time)
  (setq org-log-into-drawer t)

  (setq org-agenda-files
        '("~/Org/projects/"
          "~/Org/tasks/"))
  

  (require 'org-habit)
  (add-to-list 'org-modules 'org-habit)
  (setq org-habit-graph-column 60)

  (setq org-todo-keywords
  '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d!)")
    (sequence "BACKLOG(b)" "PLAN(p)" "READY(r)" "ACTIVE(a)" "REVIEW(v)" "WAIT(w@/!)" "HOLD(h)" "|" "COMPLETED(c)" "CANC(k@)")))

  (setq org-refile-targets
  '(("archive.org" :maxlevel . 1)
    ("planner.org" :maxlevel . 1)))

  ;; Save Org buffers after refiling!
  (advice-add 'org-refile :after 'org-save-all-org-buffers)

  (setq org-tag-alist
  '((:startgroup)
					; Put mutually exclusive tags here
    (:endgroup)
    ("@errand" . ?E)
    ("@home" . ?H)
    ("@work" . ?W)
    ("agenda" . ?a)
    ("planning" . ?p)
    ("publish" . ?P)
    ("batch" . ?b)
    ("note" . ?n)
    ("idea" . ?i)))

  ;; Configure custom agenda views
  (setq org-agenda-custom-commands
	'(("d" "Dashboard"
	   ((agenda "" ((org-deadline-warning-days 7)))
	    (todo "NEXT"
		  ((org-agenda-overriding-header "Next Tasks")))
	    (tags-todo "agenda/ACTIVE" ((org-agenda-overriding-header "Active Projects")))))

	  ("n" "Next Tasks"
	   ((todo "NEXT"
		  ((org-agenda-overriding-header "Next Tasks")))))

	  ;; Low-effort next actions
	  ("e" tags-todo "+TODO=\"NEXT\"+Effort<15&+Effort>0"
	   ((org-agenda-overriding-header "Low Effort Tasks")
	    (org-agenda-max-todos 20)
	    (org-agenda-files org-agenda-files)))))

  ;; Create capture templates
  (setq org-capture-templates
	`(("t" "Tasks")
	  ("tt" "Task" entry (file+olp "~/org/planner/tasks.org" "Inbox")
	   "* TODO %?\n  %U\n  %a\n  %i" :empty-lines 1)

	  ("p" "Projects")
	  ("pp" "Project File" entry (file+olp "~/org/projects/auto-infra-overview.org" "Inbox")
	   "* TODO %?\n  %U\n  %a\n  %i" :empty-lines 1)))

  ;; Init org font setup
  (opal/org-font-setup))

Pretty Bullets

Creates nicer (-) bullets.

(use-package org-bullets
  :straight t
  :after org
  :hook (org-mode . org-bullets-mode)
  :custom
  (org-bullets-bullet-list '("◉" "○" "●" "○" "●" "○" "●")))

Visual Fill Mode

Creates margins in Org mode to make it more pleasant to look at.

(defun opal/org-mode-visual-fill ()
  (setq visual-fill-column-width 100
	visual-fill-column-center-text t)
  (visual-fill-column-mode 1))

(use-package visual-fill-column
  :straight t
  :hook (org-mode . opal/org-mode-visual-fill))

Org Babel

;; Load languages for babel code blocks.
(with-eval-after-load 'org
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((emacs-lisp . t)))

  (push '("conf-unix" . conf-unix) org-src-lang-modes))

;; Set geiser default language
(setq geiser-default-implementation '(guile))

(with-eval-after-load 'org
  (require 'org-tempo)

  (add-to-list 'org-structure-template-alist '("sh" . "src shell"))
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
  (add-to-list 'org-structure-template-alist '("py" . "src python"))
  (add-to-list 'org-structure-template-alist '("yml" . "src yaml"))
  (add-to-list 'org-structure-template-alist '("scm" . "src scheme"))
  (add-to-list 'org-structure-template-alist '("js" . "src javascript"))
  (add-to-list 'org-structure-template-alist '("html" . "src html"))
  (add-to-list 'org-structure-template-alist '("css" . "src css"))
  (add-to-list 'org-structure-template-alist '("cl" . "src lisp")))

Programming/Editor

General

Syntax (Flycheck)
(use-package flycheck :straight t)
Rainbow Delimiters
(use-package rainbow-delimiters
  :straight t
  :init
  (add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
  (add-hook 'org-mode-hook #'rainbow-delimiters-mode))
Rainbow Mode

Sets HTML strings to have the background of the color they represent.

(use-package rainbow-mode :straight t)

Lisp

(use-package parinfer
  :straight t
  :init
  (progn
    (setq parinfer-extensions
    '(defaults
       pretty-parens
       evil))
  (add-hook 'prog-mode-hook #'parinfer-mode)))

Common Lisp

(use-package sly :straight t)

Magit

(use-package magit
  :straight t)

E-Mail

I've been using IceDove/Thunderbird for e-mail until I get around to making this a comparable solutions.

;; (use-package mu4e
;;   :straight t
;;   :config
;;   ;; This is set to 't' to avoid mail syncing issues when using mbsync
;;   (setq mu4e-change-filenames-when-moving t)

;;   ;; Refresh mail using isync every 5 minutes
;;   (setq mu4e-update-interval (* 5 60))
;;   (setq mu4e-get-mail-command "mbsync -a -c ~/Dotfiles/.config/mbsync/mbsyncrc")
;;   (setq mu4e-maildir "~/Mail")

;;   (setq mu4e-contexts
;;         (list
;;          ;; Opal.sh
;;          (make-mu4e-context
;;           :name "Ry P."
;;           :match-func
;;           (lambda (msg)
;;             (when msg
;;               (string-prefix-p "/opal.sh" (mu4e-message-field msg :maildir))))

;;           :vars '((user-mail-address . "ry@opal.sh")
;;                   (user-full-name . "Ry P.")
;;                   (mu4e-drafts-folder . "/opal.sh/Drafts")
;;                   (mu4e-sent-folder . "/opal.sh/Sent")
;;                   (mu4e-trash-folder . "/opal.sh/Trash")))))

;;   (setq mu4e-maildir-shortcuts
;;         '(("/opal.sh/Inbox" . ?i)
;;           ("/opal.sh/Sent"  . ?s)
;;           ("/opal.sh/Trash" . ?t)
;;           ("/opal.sh/Drafts" . ?d))))

IRC

Right now I'm pretty happy with Matrix bridging into IRC rooms so this will stay commented out for now. I might try out the Emacs Matrix client at some point soon.

;; (use-package erc
;;   :straight t
;;   (setq erc-server "irc.libera.chat" ;sets default server
;; 	erc-nick "opalvaults" ; Sets nick
;; 	erc-user-full-name "opalvaults"
;; 	erc-track-shorten-start 8
;; 	erc-kill-buffer-on-part t
;; 	erc-auto-query 'bury
;; 	erc-fill-column 90
;; 	erc-fill-function 'erc-fill-static
;; 	erc-fill-static-center 20
;; 	erc-track-visibility nil
;; 	erc-interpret-mirc-color t
;; 	erc-rename-buffers t
;; 	erc-track-exclude-server-buffer t))