dotfiles/user-config/emacs/.emacs.d/config.org

123 KiB

Emacs Configuration

Overview

This is a literate emacs configuration which leverages org-mode.

  • Sections marked with non_guix will only be enabled on machines using the guix system distribution.
  • Sections marked with not_tangled will not appear/execute (be tangled) as part of this emacs configuration or any related source file that is generated. Instead these code blocks are meant for reference purposes.
  • All emacs lisp code blocks are tangled unless their section is marked with not_tangled. Other language code blocks may or may not be tangled. See their code block header.
  • Works with emacs version 26+.

Usage   not_tangled

To use this configuration, it must be 'tangled' then evaluated by emacs upon startup. This can be facilitated by placing the following in .emacs.d/init.el.

  (let* ((filename (concat (file-name-directory load-file-name) "config"))
         (config (concat filename ".org")))
    (when (file-exists-p config)
      (org-babel-load-file config t)))

Because the source version of this file is tangled upon emacs startup, it must exist so that it can be tangled, leaving us with a bootstrapping problem. There are a few options to overcome this:

  1. Have a step that runs an initial tangle over this document, which results in the appropriate .emacs.d/init.el file. In this case, if this file is ever removed, it will need to be manually regenerated.
  2. Manage .emacs.d/init.el outside of this configuration. That is, expect the user has placed the above snippet in place at .emacs.d/init.el (which is the case in the repository this is distributed in).

Option 2 is what is currently used, but both could be supported in the future.

Test Drive

It is useful for testing, debugging an experimentation purposes to use this configuration without modifying an existing emacs configuration that is already in place. To achieve this, the following can be used.

  emacs -q --eval '(load-file "/path/to/dotfiles/clone/user-config/emacs/.emacs.d/init.el")'

On guix systems, all emacs packages should already be installed via a guix manifest, so the above will work without an issue. However, on non-guix systems, the emacs package manager can be used to fetch missing packages. Later in this configuration use-package is setup, and in the future the fetching of missing packages will be automated (even in this case, see: Automatically Fetch Packages). Until then, the above will result in an error when run on a non-guix system, as use-package will be missing (as well as all other required packages). Once use-package is installed in the instance of emacs launched by the above command, running the command again should result in a working 'test drive' instance of emacs. Alternatively, on non-guix distributions where the required packages have already been installed for an existing configuration, simply sym-link the existing package directory to ~/.emacs.d/elpa.

Conditions

This configuration must take differing forms depending on the system it is executed on. Here we set some variables that can be later used to modify what code blocks are executed at run time. An alternative to this would be to generate (or 'tangle' in org-babel speak) different versions of this file. Yet another alternative would be to use both methods interchangeably. However, for this configuration I have chosen to handles these conditions at emacs run time, instead of at org-babel tangle time (which occurs during emacs startup).

  (defvar is-guix-system-p (or (file-exists-p "/run/current-system/provenance")
                               (string-prefix-p (expand-file-name "~/.guix-home/profile")
                                                (locate-file "emacs" exec-path)))
    "Variable that is t if on a guix system, nil otherwise.")

  (defvar is-work-system-p (string-equal (system-name) "rekahsoft-work")
    "Variable that is t if on a work system, nil otherwise.")

Turn off GC for Startup

As a startup performance enhancement, turn off emacs garbage collection during initialization. Turn it back on once initialization has completed.

  (setq gc-cons-threshold 64000000)
  (add-hook 'after-init-hook
            #'(lambda ()
                ;; Restore default after startup
                (setq gc-cons-threshold 800000)))

Clean .emacs.d

Use no-littering to keep .emacs.d clean.

  (require 'no-littering)

Setup package.el   non_guix

Use the package.el emacs package manager on non-guix systems. It is not needed (or wanted) on guix systems as instead emacs packages should be installed via the package manager.

  (when (not is-guix-system-p)
    ;; Set repos for package.el
    (setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
                             ("melpa" . "https://melpa.org/packages/")
                             ("melpa-stable" . "https://stable.melpa.org/packages/")))

    ;; This needs to be a the start of ~/.emacs since package-initialize is run after the user
    ;; init file is read but before after-init-hook. It autoloads packages installed by
    ;; package.el including updating the load-path and running/loading/requiring
    ;; pkgname-autoload.el for all packages (where pkgname is replaced with the appropriate
    ;; package name). To disable package.el's autoloading functionality use:
    ;;    (setq package-enabled-at-startup nil)
    ;; The reason to use package-initialize here is so one can modify the installed modules
    ;; later in this .emacs file but still retain the autoloading functionality of package.el
    (package-initialize))

Leverage use-package

This configuration makes use of use-package.

  ;; Use use-package to autoload packages for a faster emacs start-up
  (require 'use-package)

Sometimes its useful to turn this on for debugging purposes.

  ;; Enable use-package verbosity
  ;; (setq use-package-verbose t)

Automatically Fetch Packages   non_guix

When on non-guix systems, that may or may not have the appropriate packages, it is useful to use the ensure option of use-package.

Note: This currently expects use-package to already be installed. This will be resolved as a TODO item.

  (when (not is-guix-system-p)
    (require 'use-package-ensure)
    (setq use-package-always-ensure t))

Activate modes with a set of hooks

This can likely be improved/removed - see TODOs

  ;; Variables and functions that will be used in files in config folder
  ;; Note: This should be put into another file (eg. lib.el) that could be loaded from here
  ;; adds the given function mode to each element of the given-hooks

  (defun activate-mode-with-hooks (mode given-hooks)
    (while given-hooks
      (add-hook (car given-hooks)
                mode)
      (setq given-hooks (cdr given-hooks))))

  ;; code-modes is a list of mode hooks (for programming langs only)
  (defvar code-modes '(yaml-mode-hook sql-mode-hook sml-mode-hook scheme-mode-hook emacs-lisp-mode-hook c-mode-hook c++-mode-hook python-mode-hook lua-mode-hook python-mode-hook haskell-mode-hook php-mode-hook perl-mode-hook lisp-mode-hook clojure-mode-hook ruby-mode-hook erlang-mode-hook sh-mode-hook java-mode-hook scala-mode-hook js-mode-hook vue-mode-hook yaml-mode vhdl-mode rust-mode-hook graphql-mode makefile-mode-hook terraform-mode-hook asm-mode-hook go-mode-hook))

Setup Custom

  ;; Load customization's made by customize
  (setq custom-file (no-littering-expand-var-file-name "custom.el"))
  (load custom-file)

Setup auth-source Backend

Use password store backend disabling netrc and gpg encrypted netrc options. See [[info:auth#Help for users][info:auth#Help for users]] for more details.

  (auth-source-pass-enable)
  (setq auth-sources '(password-store))

Theme

  ;; TODO: this may not be needed after emacs 28
  ;; Set emoji font
  (set-fontset-font t '(#x1f000 . #x1faff)
                    (font-spec :family "Noto Color Emoji"))

  ;; Turn off menu-bar when running in terminal
  ;; Note: When run as a Xwindow, .Xresources turns off the menu-bar and tool-bar
  (unless window-system (menu-bar-mode -1))

  ;; Stop startup screen
  (setq inhibit-startup-screen t)

  (defun doom|init-theme ()
    (load-theme 'doom-vibrant t)
    (doom-modeline-mode 1))

  (defun doom|init-theme-in-frame (frame)
    (with-selected-frame frame
      (doom|init-theme))

    ;; Unregister this hook once its run
    (remove-hook 'after-make-frame-functions
                 'doom|init-theme-in-frame))

  (use-package doom-themes
    :config (progn
              (setq doom-themes-enable-bold t    ; if nil, bold is universally disabled
                    doom-themes-enable-italic t) ; if nil, italics is universally disabled

              (if (daemonp)
                  (add-hook 'after-make-frame-functions
                            'doom|init-theme-in-frame)
                (doom|init-theme))

              ;; Enable flashing mode-line on errors
              (doom-themes-visual-bell-config)

              ;; Enable doom treemacs theme
              (setq doom-themes-treemacs-theme "all-the-icons")
              (doom-themes-treemacs-config)

              ;; Corrects (and improves) org-modes's native fontification
              (doom-themes-org-config)))

  (use-package doom-modeline
    :defer t)

EXWM

Define some interactive helper functions to launch various programs

  (defun launch-browser ()
    (interactive)
    (start-process "kitty" "browser-output" "icecat"))

  (defun launch-kitty ()
      (interactive)
      (start-process "kitty" "kitty-output" "kitty" "-1"))

  (global-set-key (kbd "C-s-f") 'launch-browser)
  (global-set-key (kbd "<C-s-return>") 'launch-kitty)

Define a function start-as-window-manager that allows a user to optionally start emacs as a window manager.

  (defun start-as-window-manager ()
    (use-package exwm
      :config (setq exwm-workspace-show-all-buffers t
                    exwm-layout-show-all-buffers t))

    (use-package exwm-config
      :after (exwm))

    (use-package exwm-randr
      :after (exwm exwm-config)
      :config
      (progn
        (exwm-randr-enable)
        (exwm-config-default))))

To start emacs as a window manager, add something like this to ~/.xinitrc.

  exec emacs -f start-as-window-manager

System Tray

  (use-package exwm-systemtray
    :config (exwm-systemtray-enable))

Window Management

  (use-package ace-window
    :bind
    (:map global-map
          ("M-o" . ace-window)))
  ;; Make C-x O cycle backwards a pane (oposite to C-x o)
  (global-set-key "\C-xO" #'(lambda ()
                              (interactive)
                              (other-window -1)))
  ;; Use this function to create a X11 emacs frame with a static name so it can
  ;; be treated specially by xmonad (as a scratch-pad in this case). This is expected
  ;; to be run from command-line like so:
  ;;    emacsclient --eval '(make-frame-with-static-name "emacs-scratch")'
  (defun make-frame-with-static-name (given-name)
    "Makes a (X11) frame with a unchanging name for the purposes of finding it with
  a window manager, and treating it specially."
    (make-frame-on-display ":0" `((name . ,given-name))))

  ;; Toggles windows split orientation of 2 adjecent windows
  ;; Thanks to http://www.emacswiki.org/cgi-bin/wiki?ToggleWindowSplit
  (defun toggle-window-split ()
    (interactive)
    (if (= (count-windows) 2)
        (let* ((this-win-buffer (window-buffer))
               (next-win-buffer (window-buffer (next-window)))
               (this-win-edges (window-edges (selected-window)))
               (next-win-edges (window-edges (next-window)))
               (this-win-2nd (not (and (<= (car this-win-edges)
                                           (car next-win-edges))
                                       (<= (cadr this-win-edges)
                                           (cadr next-win-edges)))))
               (splitter
                (if (= (car this-win-edges)
                       (car (window-edges (next-window))))
                    'split-window-horizontally
                  'split-window-vertically)))
          (delete-other-windows)
          (let ((first-win (selected-window)))
            (funcall splitter)
            (if this-win-2nd (other-window 1))
            (set-window-buffer (selected-window) this-win-buffer)
            (set-window-buffer (next-window) next-win-buffer)
            (select-window first-win)
            (if this-win-2nd (other-window 1))))))

  ;; TODO: this breaks treemacs when its the buffer thats rotated
  ;; Rotates windows
  ;; See: http://www.emacswiki.org/emacs/TransposeWindows
  (defun rotate-windows (arg)
    "Rotate your windows; use the prefix argument to rotate the other direction"
    (interactive "P")
    (if (not (> (count-windows) 1))
        (message "You can't rotate a single window!")
      (let* ((rotate-times (if (and (numberp arg) (not (= arg 0))) arg 1))
             (direction (if (or (< rotate-times 0) (equal arg '(4)))
                            'reverse
                          (lambda (x) x)))
             (i 0))
        (while (not (= rotate-times 0))
          (while  (< i (- (count-windows) 1))
            (let* ((w1 (elt (funcall direction (window-list)) i))
                   (w2 (elt (funcall direction (window-list)) (+ i 1)))
                   (b1 (window-buffer w1))
                   (b2 (window-buffer w2))
                   (s1 (window-start w1))
                   (s2 (window-start w2))
                   (p1 (window-point w1))
                   (p2 (window-point w2)))
              (set-window-buffer-start-and-point w1 b2 s2 p2)
              (set-window-buffer-start-and-point w2 b1 s1 p1)
              (setq i (1+ i))))

          (setq i 0
                rotate-times
                (if (< rotate-times 0) (1+ rotate-times) (1- rotate-times)))))))

  ;; Assign keybinding to toggle split orientation of 2 adjacent windows, and to rotate windows
  (define-key ctl-x-4-map "t" 'toggle-window-split)
  (global-set-key "\C-cr" 'rotate-windows)

Features

Scratches

  (defvar scratch-buffer-alist '((python-mode . "# This buffer is for notes you don't want to save, and for Lisp evaluation.\n# If you want to create a file, visit that file with C-x C-f,\n# then enter the text in that file's own buffer.")))

  (defun open-scratch-buffer (&optional buf-mode buf-name msg)
    "Opens a scratch buffer; if none exists creates one. When called with the universal argument (C-u) will ask what mode to use for the scratch buffer."
    (interactive
     (cond ((equal current-prefix-arg nil) ;; universal argument not called
            (list initial-major-mode "*scratch*" initial-scratch-message))
           ((equal current-prefix-arg '(4)) ;; Universal argument called (C-u)
            (let* ((buf-mode (read-command "Mode: " initial-major-mode))
                  (buf-name (if (equal buf-mode initial-major-mode)
                                "*scratch*"
                              (concat "*scratch:" (symbol-name buf-mode) "*")))
                  (msg ""))
              (list buf-mode buf-name msg)))))
    (let* ((scratch-buffer (get-buffer buf-name)))
      ;; check if the scratchpad is open. If not create it, change its mode and insert message text at the top of the buffer
      (if (null scratch-buffer)
          (with-current-buffer (get-buffer-create buf-name)
            (funcall buf-mode)
            (insert msg)))
      (switch-to-buffer buf-name)))

  ;; Bind a key to grab a scratchpad
  (define-key ctl-x-4-map "s" 'open-scratch-buffer)

Pastebins

Setup access to ix.io pastebin

  (use-package ix)

Flyspell

  ;; Thanks to: http://www.emacswiki.org/emacs/FlySpell#toc11
  (defun flyspell-emacs-popup-textual (event poss word)
    "A textual flyspell popup menu."
    (require 'popup)
    (let* ((corrects (if flyspell-sort-corrections
                         (sort (car (cdr (cdr poss))) 'string<)
                       (car (cdr (cdr poss)))))
           (cor-menu (if (consp corrects)
                         (mapcar (lambda (correct)
                                   (list correct correct))
                                 corrects)
                       '()))
           (affix (car (cdr (cdr (cdr poss)))))
           show-affix-info
           (base-menu  (let ((save (if (and (consp affix) show-affix-info)
                                       (list
                                        (list (concat "Save affix: " (car affix))
                                              'save)
                                        '("Accept (session)" session)
                                        '("Accept (buffer)" buffer))
                                     '(("Save word" save)
                                       ("Accept (session)" session)
                                       ("Accept (buffer)" buffer)))))
                         (if (consp cor-menu)
                             (append cor-menu (cons "" save))
                           save)))
           (menu (mapcar
                  (lambda (arg) (if (consp arg) (car arg) arg))
                  base-menu)))
      (cadr (assoc (popup-menu* menu :scroll-bar t) base-menu))))

  (use-package flyspell
    :after auto-complete auto-complete-config
    :config (progn
              (ac-flyspell-workaround)
              (activate-mode-with-hooks 'flyspell-prog-mode code-modes)
              (activate-mode-with-hooks 'flyspell-mode '(text-mode-hook markdown-mode-hook latex-mode-hook org-mode-hook magit-log-edit-mode-hook mu4e-compose-mode-hook))
              (fset 'flyspell-emacs-popup 'flyspell-emacs-popup-textual)))

Helm

  ;; Setup helm
  (use-package helm
    :bind (("C-c y"   . helm-show-kill-ring)
           ("C-x b"   . helm-mini)
           ("C-x C-f" . helm-find-files)
           ("M-x"     . helm-M-x)
           ("C-x c i" . helm-semantic-or-imenu)
           ("C-x c o" . helm-occur)
           ("C-x c t" . helm-top)
           ("C-x c r" . helm-register)
           ("C-x c g" . helm-do-grep)
           ("C-x c f" . helm-for-files)
           ("C-x c m" . helm-man-woman)
           ("C-x c a" . helm-apropos)
           ("C-x c u" . helm-surfraw) ;; TODO: this keybinding is unused
           ("C-x c c" . helm-colors)
           ("C-c h i" . helm-info)
           ("s-p" . helm-run-external-command)
           :map helm-map
           ("<tab>" . helm-execute-persistent-action) ;; Rebind tab to run persistent action
           ("C-i"   . helm-execute-persistent-action) ;; Make TAB works in terminal
           ("C-z"   . helm-select-action)             ;; List actions using C-z
           )
    :config (progn
              (setq helm-quick-update                     t
                    helm-buffers-fuzzy-matching           t
                    helm-split-window-inside-p            t ; open helm buffer inside current window, not occupy whole other window
                    helm-move-to-line-cycle-in-source     t ; move to end or beginning of source when reaching top or bottom of source.
                    helm-ff-search-library-in-sexp        t ; search for library in `require' and `declare-function' sexp.
                    helm-scroll-amount                    8 ; scroll 8 lines other window using M-<next>/M-<prior>
                    helm-ff-file-name-history-use-recentf t
                    helm-follow-mode-persistent           t)

              (helm-mode 1)
              (helm-adaptive-mode)))

  ;; See: https://github.com/emacs-helm/helm-org/issues/3
  (use-package helm-org
    :after helm
    :config (add-to-list 'helm-completing-read-handlers-alist '(org-set-tags-command . helm-org-completing-read-tags)))

  (use-package helm-rg
    :after helm
    :config (setq helm-rg-default-extra-args "--hidden"))

  ;; Setup helm-swoop
  ;; See: https://github.com/ShingoFukuyama/helm-swoop
  (use-package helm-swoop
    :after helm
    :bind (("C-x c s" . helm-swoop)))

  ;; Setup helm-ls-git to quickly select files from the current vc dir
  (use-package helm-ls-git
    :after helm
    :bind (("C-x C-d" . helm-browse-project)))

  (use-package helm-descbinds
    :after helm
    :commands helm-descbinds-mode
    :config (helm-descbinds-mode)
    :bind (("C-h k" . helm-descbinds)))

  (use-package helm-unicode
    :after helm
    :bind (("C-x 8 RET" . helm-unicode)))

  ;; TODO: this appears to be broken with the latest version of mu4e
  ;; Likely due to the fact that the packaged version installed via guix
  ;; is old compared to upstream: https://github.com/emacs-helm/helm-mu
  (use-package helm-mu
    :after helm mu4e
    :bind (("C-x c M" . helm-mu)
           ("C-x c C" . helm-mu-contacts)
           :map mu4e-main-mode-map
           ("s" . helm-mu)
           :map mu4e-headers-mode-map
           ("s" . helm-mu)
           :map mu4e-view-mode-map
           ("s" . helm-mu)))

  (use-package helm-tramp
    :after helm
    :bind (("C-x c d" . helm-tramp)))

  (use-package helm-exwm
    :after helm
    :bind (("C-x c e" . helm-exwm)))

TODO Projectile

  • helm-projectile is not available until explicitly run
  ;; Use projectile to manage projects (with helm completion)
  (use-package projectile
    :init (setq
           projectile-indexing-method 'native
           projectile-switch-project-action 'projectile-vc
           projectile-project-search-path '("~/Code/" "~/work/" "~/.scratch"))
    :bind (("C-c p" . 'projectile-command-map))
    :config (projectile-mode 1))

  (use-package helm-projectile
    :after helm projectile
    :config (helm-projectile-on))

TODO Fix (or remove/deprecate for the treemacs-projectile function) projectile->treemacs sync function

It is useful to sync projectile projects into treemacs if they are not there already. The sync-projectile-projects-to-treemacs below can be used interactively to achieve this.

  (defun sync-projectile-projects-to-treemacs ()
    "Sync all projectile projects into treemacs."
    (interactive)

    (with-current-buffer (find-file-noselect projectile-known-projects-file)
      (goto-char (point-min))
      (cl-loop for i in (read (current-buffer))
               for filepath = (expand-file-name i) do
               (letf ((treemacs-override-workspace (treemacs--find-workspace filepath)))
                     (unless (treemacs--find-project-for-path filepath)
                       (message "Adding %s to treemacs workspace %s" i (treemacs-workspace->name (treemacs-current-workspace)))
                       (treemacs-add-project-to-workspace i (file-name-sans-extension i)))))))

TODO Perspectives   not_tangled

This is currently disabled as it has not been used thoroughly and is still has usage issues that require investigation.

  ;; Taken and modified from spacemacs
  ;; See: https://github.com/syl20bnr/spacemacs/blob/master/layers/+spacemacs/spacemacs-layouts/funcs.el#L329
  (defun helm-persp-switch-project (arg)
    "Switch to a projectile perspective using helm.
  ARG is unused"
    (interactive "P")
    (helm
     :sources
     (helm-build-in-buffer-source "*Helm Switch Project Layout*"
       :data (lambda ()
               (if (projectile-project-p)
                   (cons (abbreviate-file-name (projectile-project-root))
                         (projectile-relevant-known-projects))
                 projectile-known-projects))
       :fuzzy-match helm-projectile-fuzzy-match
       :mode-line helm-read-file-name-mode-line-string
       :action '(("Switch to Project Perspective" .
                  (lambda (project)
                    (let ((persp-reset-windows-on-nil-window-conf t)
                          (project-name (file-name-nondirectory (directory-file-name project))))
                      (persp-switch project)
                      (persp-rename project-name)
                      (let ((projectile-completion-system 'helm))
                        (projectile-switch-project-by-name project)))))))
     :buffer "*Helm Projectile Layouts*"))

  ;; Taken and modified from spacemacs
  ;; See: https://github.com/syl20bnr/spacemacs/blob/master/layers/+spacemacs/spacemacs-layouts/funcs.el#L313
  (defun helm-persp-kill ()
    "Kill perspectives with all their buffers."
    (interactive)
    (helm
     :buffer "*Helm Kill Perspectives with all their buffers*"
     :sources (helm-build-in-buffer-source
                  (s-concat "Current Perspective: "
                            (persp-name (get-current-persp)))
                :data (seq-filter (lambda (n) (not (string-equal n "main"))) (persp-names))
                :fuzzy-match t
                :action
                '(("Kill perspective(s)" .
                   (lambda (candidate)
                     (mapcar 'persp-kill
                             (helm-marked-candidates))))))))

  (use-package persp-mode
    :diminish
    :init (progn
            (setq persp-nil-name "main")
            (add-hook 'after-init-hook (lambda () (persp-mode +1)))))

  (use-package persp-projectile
    :after persp-mode
    :bind (("C-x x P" . projectile-persp-switch-project)
           ("C-c p p" . helm-persp-switch-project)
           ("C-x x c" . helm-persp-kill)))

  (use-package persp-mode-projectile-bridge
    :disabled ;; TODO: BROKEN
    :after projectile persp-mode
    :commands (persp-mode-projectile-bridge-find-perspectives-for-all-buffers
               persp-mode-projectile-bridge-kill-perspectives)
    :hook ((persp-mode . persp-mode-projectile-bridge-mode)
           (persp-mode-projectile-bridge-mode
            . (lambda ()
                (if persp-mode-projectile-bridge-mode
                    (persp-mode-projectile-bridge-find-perspectives-for-all-buffers)
                  (persp-mode-projectile-bridge-kill-perspectives))))))

TODO Desktop Save

  ;; TODO: Disabled as this causes frames to no longer open in daemon-mode
  ;; ;; Enable desktop-save-mode.
  ;; ;; When running as a daemon, defer loading of saved desktop until first frame is created
  ;; (add-hook 'after-make-frame-functions
  ;;           (lambda ()
  ;;             (if (daemonp)
  ;;                 (progn
  ;;                   (desktop-save-mode 1)
  ;;                   (desktop-read))
  ;;               (desktop-save-mode 1))))

IBuffer

  (use-package ibuffer
    :bind ("C-x C-b" . ibuffer)
    :config (progn
              (use-package ibuf-ext)
              (use-package ibuffer-vc)
              (use-package ibuffer-projectile)
              ;; Require ibuffer extentions (used for ibuffer-never-show-predicates)
              (add-to-list 'ibuffer-never-show-predicates "^\\*slime-events\\*$")
              (add-to-list 'ibuffer-never-show-predicates "^\\*Completions\\*$")
              (add-to-list 'ibuffer-never-show-predicates "^\\*tramp/.*\\*$")

              ;; Add vc-status line to ibuffer
              (setq ibuffer-formats '((mark modified read-only vc-status-mini " "
                                            (name 18 18 :left :elide) " "
                                            (size 9 -1 :right) " "
                                            (mode 16 16 :left :elide) " "
                                            (vc-status 16 16 :left) " "
                                            filename-and-process)
                                      (mark " " (name 16 -1) " " filename)))

              ;; Merge the results of filtering by creating filter groups first for projectile
              ;; then for each major mode (of currently open buffers
              (add-hook 'ibuffer-mode-hook
                        (lambda ()
                          (setq ibuffer-filter-groups
                                (append
                                 (ibuffer-projectile-generate-filter-groups)
                                 (mapcar (lambda (mode)
                                           (cons (format "%s" mode) `((mode . ,mode))))
                                         (let ((modes
                                                (ibuffer-remove-duplicates
                                                 (mapcar (lambda (buf)
                                                           (buffer-local-value 'major-mode buf))
                                                         (buffer-list)))))
                                           (if ibuffer-view-ibuffer
                                               modes
                                             (delq 'ibuffer-mode modes))))))
                          (let ((ibuf (get-buffer "*Ibuffer*")))
                            (when ibuf
                              (with-current-buffer ibuf
                                (pop-to-buffer ibuf)
                                (ibuffer-update nil t))))))))

ibuffer Icons

Leverage all-the-icons-ibuffer to show pretty icons beside buffers in iBuffer.

  (use-package all-the-icons-ibuffer
    :after all-the-icons
    :config (all-the-icons-ibuffer-mode 1))

Insert Templates

  ;;
  ;; Considering phasing this out in-place of yasnippet
  ;;

  (setq auto-insert-query nil ;; If you don't want to be prompted before insertion
        autoinsert-tpl-author "Collin J. Doering"
        autoinsert-tpl-email "collin.doering@rekahsoft.ca"
        auto-insert-directory (no-littering-expand-etc-file-name "templates/") ;; Trailing slash important
        auto-insert-alist
          '(("\\.c$" . ["c-template.c" auto-update-generic-template])
            ("\\.cc\\|cpp$" . ["cpp-template.cc" auto-update-generic-template])
            ("\\.php$" . ["php-template.php" auto-update-generic-template])
            ("\\.rb$" . ["ruby-template.rb" auto-update-generic-template])
            ("\\.lua$" . ["lua-template.lua" auto-update-generic-template])
            ("\\.erl$" . ["erlang-template.erl" auto-update-generic-template])
            ("\\.sh$" . ["shell-template.sh" auto-update-generic-template])
            ("\\.rkt$" . ["racket-template.rkt" auto-update-generic-template])
            ("\\.scm$" . ["scheme-template.scm" auto-update-generic-template])
            ("\\.clj$" . ["clojure-template.clj" auto-update-generic-template])
            ("\\.lisp$" . ["lisp-template.lisp" auto-update-generic-template])
            ("\\.el$" . ["emacs-lisp-template.el" auto-update-generic-template])
            ("\\.hs$" . ["haskell-template.hs" auto-update-generic-template])
            ("\\.ml$" . ["ocaml-template.ml" auto-update-generic-template])
            ("\\.sml$" . ["sml-template.sml" auto-update-generic-template])
            ("\\.py$" . ["python-template.py" auto-update-generic-template])
            ("\\.java$" . ["java-template.java" auto-update-generic-template])
            ("\\.scala$" . ["scala-template.scala" auto-update-generic-template])
            ("\\.htm\\|html$" . ["html-template.html" auto-update-generic-template])
            ("\\.js$" . ["java-script-template.js" auto-update-generic-template])
            ("\\.css$" . ["css-template.css" auto-update-generic-template])
            ("\\.scss$" . ["scss-template.scss" auto-update-generic-template])
            ("\\.sass$" . ["sass-template.sass" auto-update-generic-template])
            ("\\.haml$" . ["haml-template.haml" auto-update-generic-template])
            ("\\.markdown$" . ["markdown-template.markdown" auto-update-generic-template])
            ("\\.tex$" . ["latex-template.tex" auto-update-generic-template]))
          auto-insert 'other)

  ;; auto-insert options template and auto-completion
  (add-hook 'find-file-hooks 'auto-insert)

  ;; TODO: remove interactive-ness from auto-update-generic-template as it's not needed
  ;;       and there only as a workaround. Python and PHP templates are not filled for
  ;;       some unknown reason.
  (defun auto-update-generic-template ()
    (interactive)
    (save-excursion
      ;; Replace @!@FILENAME@!@ with file name sans suffix
      (while (search-forward "@!@FILENAME@!@" nil t)
        (save-restriction
          (narrow-to-region (match-beginning 0) (match-end 0))
          (replace-match (file-name-sans-extension (file-name-nondirectory buffer-file-name)) t))))
    (save-excursion
      ;; Replace @!@FILE@!@ with file name
      (while (search-forward "@!@FILE@!@" nil t)
        (save-restriction
          (narrow-to-region (match-beginning 0) (match-end 0))
          (replace-match (file-name-nondirectory buffer-file-name) t))))
    (save-excursion
      ;; replace @!@DATE@!@ with today's date
      (while (search-forward "@!@DATE@!@" nil t)
        (save-restriction
          (narrow-to-region (match-beginning 0) (match-end 0))
          (replace-match "")
          (insert-date))))
    (save-excursion
      ;; Replace @!@YEAR@!@ with the current year
      (while (search-forward "@!@YEAR@!@" nil t)
        (save-restriction
          (narrow-to-region (match-beginning 0) (match-end 0))
          (replace-match (format-time-string "%Y" (current-time))))))
    (save-excursion
      ;; Replace @!@AUTHOR@!@ with the current year
      (while (search-forward "@!@AUTHOR@!@" nil t)
        (save-restriction
          (narrow-to-region (match-beginning 0) (match-end 0))
          (replace-match "")
          (insert-author))))
    (save-excursion
      ;; Replace @!@EMAIL@!@ with the current year
      (while (search-forward "@!@EMAIL@!@" nil t)
        (save-restriction
          (narrow-to-region (match-beginning 0) (match-end 0))
          (replace-match "")
          (insert-author-email)))))

  ;; Insert current date at cursor in the currently active buffer
  (defun insert-date ()
    "Insert today's date into buffer"
    (interactive)
    (insert (format-time-string "%b %e, %Y" (current-time))))

  (defun insert-author ()
    "Insert author name at point"
    (interactive)
    (insert autoinsert-tpl-author))

  (defun insert-author-email ()
    "Insert author email at point"
    (interactive)
    (insert autoinsert-tpl-email))

Terminal

  ;; Setup multi-term
  ;; See: http://www.emacswiki.org/MultiTerm
  (use-package multi-term
    :init ;; Fixes issue with tab key
    ;; See: https://github.com/capitaomorte/yasnippet/issues/289&sa=U&ei=8HJ2VLnbFvHhsASa0oGYBw&ved=0CBgQFjAB&sig2=3GORWPQWDpRVgReLr40nkw&usg=AFQjCNFvbeuG4bRq4nbWTPwuu7-5S4UChA
    (add-hook 'term-mode-hook (lambda ()
                                (yas-minor-mode -1)))
    :bind (("C-c t" . multi-term)
           ("C-c T" . multi-term-dedicated-toggle)
           ("C-c F" . multi-term-next)
           ("C-c B" . multi-term-prev)))

  (use-package vterm
    :config
    (setq vterm-kill-buffer-on-exit t
          vterm-max-scrollback 100000 ;; Max 100000
          vterm-eval-cmds '(("man" man)
                            ("info" info-other-window)
                            ("find-file" find-file)
                            ("message" message)
                            ("vterm-clear-scrollback" vterm-clear-scrollback))))

Hydra

  (use-package hydra)

direnv Support

  (use-package envrc
    :config (envrc-global-mode))

Which key?

  (use-package which-key
    :config (which-key-mode))

Programs

IRC - rcirc

  ;; Use rcirc for irc; see: http://www.emacswiki.org/emacs/rcirc
  ;; Use rcirc-notify extension; see: http://www.emacswiki.org/emacs/rcircNotify
  (use-package rcirc
    :demand t
    :if (daemonp)
    :bind ("C-c I" . rcirc)
    :hook ((rcirc-mode . flyspell-mode)) ;; Turn on flyspell mode in rcirc buffers
    :config (progn
              (use-package dbus
                :config (progn
                          (defun nm-is-connected()
                            (equal 70 (dbus-get-property
                                       :system "org.freedesktop.NetworkManager" "/org/freedesktop/NetworkManager"
                                       "org.freedesktop.NetworkManager" "State")))))

              (use-package rcirc-notify
                :config (rcirc-notify-add-hooks))

              ;; Change user info
              (setq rcirc-default-nick "rekahsoft"
                    rcirc-default-user-name "rekahsoft"
                    rcirc-default-full-name "rekahsoft"
                    rcirc-time-format "%Y-%m-%d %H:%M "
                    rcirc-log-flag t

                    rcirc-server-alist
                    '(("irc.libera.chat" :port 6697 :encryption tls
                       :channels ("#emacs" "#haskell" "#racket" "#xmonad" "#guix" "#guile"))
                      ("localhost" :port 6667 :channels () :method 'bitlbee)))

              ;; Advice rcirc function to read credentials from via auth-source api
              (defun rcirc--read-from-auth-source (&optional args)
                (unless args
                  (setq rcirc-authinfo
                        (seq-reduce (lambda (rst x)
                                      (let* ((host (car x))
                                             (rec (cdr x))
                                             (user (or (plist-get rec :user)
                                                      rcirc-default-user-name))
                                             (method (or (plist-get rec :method)
                                                        'nickserv))
                                             (p (auth-source-search :host host :user user :require '(:user :secret) :max 1))
                                             (secret (if p (plist-get (car p) :secret))))
                                        (cons (list host method user (if (functionp secret)
                                                                         (funcall secret)
                                                                       secret)) rst)))
                                    rcirc-server-alist nil))))
              (advice-add 'rcirc :before #'rcirc--read-from-auth-source)

              ;; TODO: this function uses the first password and encryption in rcirc-server-alist
              ;;       where it should use the one for server being reconnected to
              ;; Thanks to: http://www.emacswiki.org/emacs/rcircReconnect
              (defun-rcirc-command reconnect (arg)
                "Reconnect the server process."
                (interactive "i")
                (unless process
                  (error "There's no process for this target"))
                (let* ((server (car (process-contact process)))
                       (port (process-contact process :service))
                       (nick (rcirc-nick process))
                       channels query-buffers)
                  (dolist (buf (buffer-list))
                    (with-current-buffer buf
                      (when (eq process (rcirc-buffer-process))
                        (remove-hook 'change-major-mode-hook
                                     'rcirc-change-major-mode-hook)
                        (if (rcirc-channel-p rcirc-target)
                            (setq channels (cons rcirc-target channels))
                          (setq query-buffers (cons buf query-buffers))))))
                  (delete-process process)
                  (rcirc-connect server port nick
                                 rcirc-default-user-name
                                 rcirc-default-full-name
                                 channels
                                 (plist-get (cdr rcirc-server-alist) :password)
                                 (plist-get (cdr rcirc-server-alist) :encryption))))

              ;; Thanks to: http://www.emacswiki.org/emacs/rcircAll
              (defun-rcirc-command all (input)
                "Run the arguments as a command for all connections.
  Example use: /all away food or /all quit zzzz."
                (interactive "s")
                (let ((buffers (mapcar 'process-buffer (rcirc-process-list))))
                  (dolist (buf buffers)
                    (with-current-buffer buf
                      (goto-char (point-max))
                      (insert "/" input)
                      (rcirc-send-input)))))

              ;; Turn on rcirc tracking to be displayed in mode-line
              (rcirc-track-minor-mode)

              ;; Connect to servers auto-matically if in daemon-mode
              (if (and (daemonp) (nm-is-connected))
                  (rcirc nil))))

Feed Reader - elfeed

  (use-package elfeed
    :bind (("C-x w" . elfeed)))

Setup elfeed-org

  (use-package elfeed-org
    :after elfeed
    :config (elfeed-org)
    :custom rmh-elfeed-org-files (list "~/.org/roam/20230422115510-feeds.org"))

Email - Mu4e

  ;; Setup email using mu4e (mbsync in the background) and smtpmail
  (use-package mu4e
    :init (setq mu4e~main-buffer-name "*mu4e*")
    :config (progn
              (setq mail-user-agent 'mu4e-user-agent
                    mu4e-maildir "~/.mail"
                    mu4e-get-mail-command "mbsync -a"
                    mu4e-update-interval 300
                    message-send-mail-function 'smtpmail-send-it
                    message-kill-buffer-on-exit t
                    mu4e-use-fancy-chars t
                    mu4e-confirm-quit nil
                    mu4e-headers-date-format "%d/%b/%Y %H:%M"
                    message-signature-insert-empty-line t
                    mu4e-view-show-images t
                    mu4e-view-image-max-width 800

                    ;; Enable org-mode support (enabled by default)
                    mu4e-support-org t
                    ;; When in headers view, link to the query, not the message at point
                    mu4e-org-link-query-in-headers-mode t

                    mml-secure-openpgp-sign-with-sender t
                    message-cite-style message-cite-style-gmail

                    ;; Change the filename when moving mail; this is required to avoid duplicate uids with mbsync
                    mu4e-change-filenames-when-moving t

                    ;; Configure Gmail style citation
                    message-citation-line-format "On %d %b %Y at %R, %f wrote:\n"
                    message-citation-line-function 'message-insert-formatted-citation-line

                    mu4e-view-actions '(("capture message" . mu4e-action-capture-message)
                                        ("pdf view" . mu4e-action-view-as-pdf)
                                        ("browser view" . mu4e-action-view-in-browser)
                                        ("show this thread" . mu4e-action-show-thread))

                    mu4e-contexts
                    `( ,(make-mu4e-context
                         :name "Personal"
                         :enter-func (lambda () (mu4e-message "Entering Personal context"))
                         :leave-func (lambda () (mu4e-message "Leaving Personal context"))
                         ;; we match based on the contact-fields of the message
                         :match-func (lambda (msg)
                                       (when msg
                                         (mu4e-message-contact-field-matches msg :to "collin@rekahsoft.ca")))
                         :vars `((user-mail-address . "collin@rekahsoft.ca")
                                 (user-full-name . "Collin J. Doering")
                                 (mu4e-sent-folder . "/collin@rekahsoft.ca/Sent")
                                 (mu4e-drafts-folder . "/collin@rekahsoft.ca/Drafts")
                                 (mu4e-trash-folder . "/collin@rekahsoft.ca/Trash")
                                 (mu4e-refile-folder . "/collin@rekahsoft.ca/Archive")
                                 (mu4e-maildir-shortcuts . (("/collin@rekahsoft.ca/Inbox" . ?i)
                                                            ("/collin@rekahsoft.ca/Archive" . ?a)
                                                            ("/collin@rekahsoft.ca/Sent" . ?s)
                                                            ("/collin@rekahsoft.ca/Drafts" . ?d)
                                                            ("/collin@rekahsoft.ca/Trash" . ?t)
                                                            ("/collin@rekahsoft.ca/Junk" . ?J)
                                                            ("/collin@rekahsoft.ca/Mailing Lists" . ?m)
                                                            ("/collin@rekahsoft.ca/Mailing Lists/Gnu/Guix" . ?g)))
                                 (mu4e-bookmarks . ((:name  "Unread messages"
                                                     :query "flag:unread AND NOT flag:trashed AND maildir:/collin@rekahsoft.ca/*"
                                                     :key ?u)
                                                    (:name "Today's messages"
                                                     :query "date:today..now AND maildir:/collin@rekahsoft.ca/*"
                                                     :key ?t)
                                                    (:name "Last 7 days"
                                                     :query "date:7d..now AND maildir:/collin@rekahsoft.ca/*"
                                                     :key ?w)
                                                    (:name "Messages with images"
                                                     :query "mime:image/* AND maildir:/collin@rekahsoft.ca/*"
                                                     :key ?p)))
                                 ;; Move emails to sent folder upon being successfully sent
                                 (mu4e-sent-messages-behavior . sent)
                                 (mu4e-compose-signature .
                                                         (concat
                                                          "Collin J. Doering\n\n"
                                                          "http://rekahsoft.ca\n"
                                                          "http://blog.rekahsoft.ca\n"
                                                          "http://git.rekahsoft.ca\n"))))
                       ,(make-mu4e-context
                         :name "GMail"
                         :enter-func (lambda () (mu4e-message "Entering GMail context"))
                         :leave-func (lambda () (mu4e-message "Leaving GMail context"))
                         ;; we match based on the contact-fields of the message
                         :match-func (lambda (msg)
                                       (when msg
                                         (mu4e-message-contact-field-matches msg :to "collin.doering@gmail.com")))
                         :vars `((user-mail-address . "collin.doering@gmail.com")
                                 (user-full-name . "Collin J. Doering")
                                 (mu4e-sent-folder . "/collin.doering@gmail.com/[Gmail]/.Sent Mail")
                                 (mu4e-drafts-folder . "/collin.doering@gmail.com/[Gmail]/.Drafts")
                                 (mu4e-trash-folder . "/collin.doering@gmail.com/[Gmail]/.Trash")
                                 (mu4e-refile-folder . "/collin.doering@gmail.com/[Gmail]/.All Mail")
                                 (mu4e-maildir-shortcuts . (("/collin.doering@gmail.com/Inbox" . ?i)
                                                            ("/collin.doering@gmail.com/[Gmail]/.All Mail" . ?a)
                                                            ("/collin.doering@gmail.com/[Gmail]/.Sent Mail" . ?s)
                                                            ("/collin.doering@gmail.com/[Gmail]/.Drafts" . ?d)
                                                            ("/collin.doering@gmail.com/[Gmail]/.Starred" . ?f)
                                                            ("/collin.doering@gmail.com/[Gmail]/.Trash" . ?t)
                                                            ("/collin.doering@gmail.com/[Gmail]/.Spam" . ?J)))
                                 (mu4e-bookmarks . ((:name  "Unread messages"
                                                     :query "flag:unread AND NOT flag:trashed AND maildir:/collin.doering@gmail.com/*"
                                                     :key ?u)
                                                    (:name "Today's messages"
                                                     :query "date:today..now AND maildir:/collin.doering@gmail.com/*"
                                                     :key ?t)
                                                    (:name "Last 7 days"
                                                     :query "date:7d..now AND maildir:/collin.doering@gmail.com/*"
                                                     :key ?w)
                                                    (:name "Messages with images"
                                                     :query "mime:image/* AND maildir:/collin.doering@gmail.com/*"
                                                     :key ?p)))
                                 ;; GMail automatically adds messages to the users sent folder, so successfully sent messages should be deleted to avoid duplicates
                                 (mu4e-sent-messages-behavior . delete)
                                 (mu4e-compose-signature .
                                                         (concat
                                                          "Collin J. Doering\n\n"
                                                          "http://rekahsoft.ca\n"
                                                          "http://blog.rekahsoft.ca\n"
                                                          "http://git.rekahsoft.ca\n"))))
                       ,(make-mu4e-context
                         :name "Rekahsoft-GMail"
                         :enter-func (lambda () (mu4e-message "Entering Rekahsoft-GMail context"))
                         :leave-func (lambda () (mu4e-message "Leaving Rekahsoft-GMail context"))
                         ;; we match based on the contact-fields of the message
                         :match-func (lambda (msg)
                                       (when msg
                                         (mu4e-message-contact-field-matches msg :to "rekahsoft@gmail.com")))
                         :vars `((user-mail-address . "rekahsoft@gmail.com")
                                 (user-full-name . "Collin J. Doering")
                                 (mu4e-sent-folder . "/rekahsoft@gmail.com/[Gmail]/.Sent Mail")
                                 (mu4e-drafts-folder . "/rekahsoft@gmail.com/[Gmail]/.Drafts")
                                 (mu4e-trash-folder . "/rekahsoft@gmail.com/[Gmail]/.Trash")
                                 (mu4e-refile-folder . "/rekahsoft@gmail.com/[Gmail]/.All Mail")
                                 (mu4e-maildir-shortcuts . (("/rekahsoft@gmail.com/Inbox" . ?i)
                                                            ("/rekahsoft@gmail.com/[Gmail]/.All Mail" . ?a)
                                                            ("/rekahsoft@gmail.com/[Gmail]/.Sent Mail" . ?s)
                                                            ("/rekahsoft@gmail.com/[Gmail]/.Drafts" . ?d)
                                                            ("/rekahsoft@gmail.com/[Gmail]/.Starred" . ?f)
                                                            ("/rekahsoft@gmail.com/[Gmail]/.Trash" . ?t)
                                                            ("/rekahsoft@gmail.com/[Gmail]/.Spam" . ?J)))
                                 (mu4e-bookmarks . ((:name  "Unread messages"
                                                     :query "flag:unread AND NOT flag:trashed AND maildir:/rekahsoft@gmail.com/*"
                                                     :key ?u)
                                                    (:name "Today's messages"
                                                     :query "date:today..now AND maildir:/rekahsoft@gmail.com/*"
                                                     :key ?t)
                                                    (:name "Last 7 days"
                                                     :query "date:7d..now AND maildir:/rekahsoft@gmail.com/*"
                                                     :key ?w)
                                                    (:name "Messages with images"
                                                     :query "mime:image/* AND maildir:/rekahsoft@gmail.com/*"
                                                     :key ?p)))
                                 ;; GMail automatically adds messages to the users sent folder, so successfully sent messages should be deleted to avoid duplicates
                                 (mu4e-sent-messages-behavior . delete)
                                 (mu4e-compose-signature .
                                                         (concat
                                                          "Collin J. Doering\n\n"
                                                          "http://rekahsoft.ca\n"
                                                          "http://blog.rekahsoft.ca\n"
                                                          "http://git.rekahsoft.ca\n"))))
                       ,(make-mu4e-context
                         :name "NoRedInk"
                         :enter-func (lambda () (mu4e-message "Entering NoRedInk context"))
                         :leave-func (lambda () (mu4e-message "Leaving NoRedInk context"))
                         ;; we match based on the contact-fields of the message
                         :match-func (lambda (msg)
                                       (when msg
                                         (mu4e-message-contact-field-matches msg :to "collin@noredink.com")))
                         :vars `((user-mail-address . "collin@noredink.com")
                                 (user-full-name . "Collin J. Doering")
                                 (mu4e-sent-folder . "/collin@noredink.com/[Gmail]/.Sent Mail")
                                 (mu4e-drafts-folder . "/collin@noredink.com/[Gmail]/.Drafts")
                                 (mu4e-trash-folder . "/collin@noredink.com/[Gmail]/.Trash")
                                 (mu4e-refile-folder . "/collin@noredink.com/[Gmail]/.All Mail")
                                 (mu4e-maildir-shortcuts . (("/collin@noredink.com/Inbox" . ?i)
                                                            ("/collin@noredink.com/[Gmail]/.All Mail" . ?a)
                                                            ("/collin@noredink.com/[Gmail]/.Sent Mail" . ?s)
                                                            ("/collin@noredink.com/[Gmail]/.Drafts" . ?d)
                                                            ("/collin@noredink.com/[Gmail]/.Starred" . ?f)
                                                            ("/collin@noredink.com/[Gmail]/.Trash" . ?t)
                                                            ("/collin@noredink.com/[Gmail]/.Spam" . ?J)))
                                 (mu4e-bookmarks . ((:name  "Unread messages"
                                                     :query "flag:unread AND NOT flag:trashed AND maildir:/collin@noredink.com/*"
                                                     :key ?u)
                                                    (:name "Today's messages"
                                                     :query "date:today..now AND maildir:/collin@noredink.com/*"
                                                     :key ?t)
                                                    (:name "Last 7 days"
                                                     :query "date:7d..now AND maildir:/collin@noredink.com/*"
                                                     :key ?w)
                                                    (:name "Messages with images"
                                                     :query "mime:image/* AND maildir:/collin@noredink.com/*"
                                                     :key ?p)))
                                 ;; GMail automatically adds messages to the users sent folder, so successfully sent messages should be deleted to avoid duplicates
                                 (mu4e-sent-messages-behavior . delete)
                                 (mu4e-compose-signature .
                                                         (concat
                                                          "Collin Doering\n"
                                                          "Engineering Manager, NoRedInk\n"
                                                          "P: 519-500-0931\n"
                                                          "F: 844-667-3346\n"
                                                          "https://www.noredink.com\n\n"))))))

              ;; Use imagemagick to display images (if available)
              (when (fboundp 'imagemagick-register-types)
                (imagemagick-register-types))

              ;; Verify gpg signatures for known keys
              (setq mm-verify-option 'known)
              ;; Sign outgoing mail by default
              (add-hook 'message-send-hook #'mml-secure-message-sign-pgpmime))
    :hook ((mu4e-compose-mode . (lambda () (auto-save-mode -1)))
           (mu4e-compose-mode . turn-off-auto-fill)
           (mu4e-compose-mode . visual-line-mode))
    :bind (("C-x M" . mu4e)))

  (use-package mu4e-icalendar
    :after mu4e
    :config (mu4e-icalendar-setup))

  (use-package mu4e-alert
    :after mu4e
    :commands mu4e-alert-set-default-style
    :config (progn
              (setq mu4e-alert-interesting-mail-query "flag:unread AND (maildir:/collin@rekahsoft.ca/Inbox OR maildir:/collin.doering@gmail.com/Inbox)")
              (mu4e-alert-set-default-style 'libnotify))
    :hook ((after-init . mu4e-alert-enable-notifications)
           (after-init . mu4e-alert-enable-mode-line-display)))

  (use-package smtpmail
    :config (progn
              ;; Setup smtp defaults
              (setq user-full-name "Collin J. Doering"
                    smtpmail-smtp-server "smtp.migadu.com"
                    smtpmail-smtp-service 587
                    smtpmail-stream-type 'starttls
                    smtpmail-debug-info t)

              (setq smtp-accounts
                    '(("collin@rekahsoft.ca" "Collin J. Doering" "smtp.migadu.com")
                      ("collin.doering@gmail.com" "Collin J. Doering" "smtp.gmail.com")
                      ("rekahsoft@gmail.com" "rekahsoft" "smtp.gmail.com")
                      ("collin@noredink.com" "Collin J. Doering" "smtp.gmail.com")))

              (defun my-change-smtp ()
                (save-excursion
                  (cl-loop with from = (save-restriction
                                         (message-narrow-to-headers)
                                         (message-fetch-field "from"))
                           for (addr fname server) in smtp-accounts
                           when (string-match addr from)
                           do (setq user-mail-address addr
                                    user-full-name fname
                                    smtpmail-smtp-server server
                                    smtpmail-smtp-user addr))))

              (defadvice smtpmail-via-smtp
                  (before change-smtp-by-message-from-field (recipient buffer &optional ask) activate)
                (with-current-buffer buffer (my-change-smtp)))

              (ad-activate 'smtpmail-via-smtp)))

  ;; Attach files using dired using 'C-c RET C-a'
  ;; Thanks to: http://www.djcbsoftware.nl/code/mu/mu4e/Attaching-files-with-dired.html#Attaching-files-with-dired
  (use-package gnus-dired
    :config (progn
              ;; make the `gnus-dired-mail-buffers' function also work on
              ;; message-mode derived modes, such as mu4e-compose-mode
              (defun gnus-dired-mail-buffers ()
                "Return a list of active message buffers."
                (let (buffers)
                  (save-current-buffer
                    (dolist (buffer (buffer-list t))
                      (set-buffer buffer)
                      (when (and (derived-mode-p 'message-mode)
                                 (null message-sent-message-via))
                        (push (buffer-name buffer) buffers))))
                  (nreverse buffers)))

              (setq gnus-dired-mail-mode 'mu4e-user-agent)
              (add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode)))

HTML Email using org-mode

Use the org-mime package, to enable sending html email.

  (use-package org-mime)

PDF Viewer

Emacs comes with a built in pdf reader that leverages imagemagic to convert the pdf to images which are then read off disk. This is not optimal from the perspective of performance however, but luckily the pdf-tools package resolves this, as well as adds a variety of nice features (eg search, text annotation, highlighting, and more..) on top of providing a much nicer user experience.

  (use-package pdf-tools
    :config (pdf-tools-install))

TODO Editing   needs_review

This section should be reviewed as this has grown over time. Also, some if not many of these settings can likely be replaced by the sensible-defaults package.

  ;; Turn off indentation (use spaces instead)
  (setq-default indent-tabs-mode nil)

  ;; Turn on global pretty-ification of symbols
  (global-prettify-symbols-mode 1)

  (setq
    ;; Make cursor the width of the character it is under
    x-stretch-cursor t
    ;; Single spaces denote end sentences for use with sentence commands
    sentence-end-double-space nil)

  ;; Show column number in mode-line
  (column-number-mode)

  ;; Show size of file in mode-line
  (size-indication-mode)

  ;; Set the fill-column for text filling
  (setq-default fill-column 93)

  ;; TODO: guix - this currently does not load in daemon mode
  ;; Turn hl-line-mode on globally
  ;;(global-hl-line-mode)

  ;; Activate linum-mode in all buffers used for programming
  (activate-mode-with-hooks (lambda () (display-line-numbers-mode 1)) code-modes)

  ;; Add little indicators to fringe indicating the location of point in the given window
  ;; See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Displaying-Boundaries.html
  ;;      https://www.gnu.org/software/emacs/manual/html_node/emacs/Useless-Whitespace.html
  (setq-default indicate-buffer-boundaries 'left
                indicate-empty-lines t)

  ;; Don't scroll the buffer to center when using follow-mode
  (add-hook 'follow-mode-hook (lambda ()
                                (make-local-variable 'scroll-conservatively)
                                (set 'scroll-conservatively 101)))

  ;; Indicate empty space at the end of a line
  ;;(setq-default show-trailing-whitespace nil)

  ;; Do not display error when killing from a read-only buffer; instead just show a warning
  ;; Note: in both cases the killed text is copied to the kill-ring
  ;; See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Kill-Options.html#Kill-Options
  (setq kill-read-only-ok t)

  ;; Enable the disabled narrowing commands
  (put 'narrow-to-defun 'disabled nil)
  (put 'narrow-to-page 'disabled nil)
  (put 'narrow-to-region 'disabled nil)

  ;; Setup cursor-blink-mode
  (setq blink-cursor-blinks 3)
  (blink-cursor-mode)

  ;; Setup electric-pair mode globally
  (electric-pair-mode)

  ;; Use view-mode aggressively for read-only files
  ;; See: http://www.emacswiki.org/emacs/ViewMode
  (setq view-read-only t)

  ;; Stop renaming of saved files to filename~ which ends up breaking hardlinks
  (setq backup-by-copying-when-linked t)

  ;; fixes color output issues; see: http://wiki.archlinux.org/index.php/Emacs#Colored_output_issues
  (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)

  ;; Automatically open some config files with an associated major mode
  ;; Note: regexp's used to match buffer filenames are intentionally left
  ;;       unbounded (without '$') to catch cases where the filename may
  ;;       take the format: filename~
  (setq auto-mode-alist
        (append auto-mode-alist
                '(("\\.conkerorrc" . js-mode))
                '(("\\.xmobarrc" . haskell-mode))
                '(("\\.screenrc" . conf-mode))
                '(("\\.stumpwmrc" . lisp-mode))
                '(("\w*\\.service" . conf-mode))
                '(("\w*\\.socket" . conf-mode))
                '(("\\.mpdconf" . conf-mode))
                '(("dunstrc" . conf-mode))))

  ;; Thanks to: http://ergoemacs.org/emacs/emacs_byte_compile.html
  (defun byte-compile-if-elisp ()
    "`byte-compile' current buffer if it's emacs-lisp-mode and compiled file exists."
    (interactive)
    (when (and (eq major-mode 'emacs-lisp-mode)
               (file-exists-p (byte-compile-dest-file buffer-file-name)))
      (byte-compile-file buffer-file-name)))

  (add-hook 'after-save-hook 'byte-compile-if-elisp)

  (use-package string-inflection)

  (use-package fill-column-indicator
    :config (turn-on-fci-mode))

  ;; Highlight indentation
  (use-package highlight-indent-guides
    :init (progn
            (setq highlight-indent-guides-method 'character
                  highlight-indent-guides-character ?\|
                  highlight-indent-guides-auto-odd-face-perc 15
                  highlight-indent-guides-auto-even-face-perc 15
                  highlight-indent-guides-auto-character-face-perc 20))
    :config (add-hook 'prog-mode-hook 'highlight-indent-guides-mode))

Enable some commands that are by default disabled.

  (put 'set-goal-column 'disabled nil)
  (put 'upcase-region 'disabled nil)

Ace Jump Mode

  (use-package ace-jump-mode
    :bind ("C-c SPC" . ace-jump-mode))

Link Navigation

avy is a package that assist with jumping to visible text using a char-based decision tree. The ace-link package leverages avy to provide quick navigation of links in various contexts.

  (use-package ace-link
    :config (ace-link-setup-default))

Expand Region

  (use-package expand-region
    :bind ("C-=" . er/expand-region))

Multiple Cursors

  (use-package multiple-cursors
    :bind (("C-S-c C-S-c" . mc/edit-lines)
           ("C->" . mc/mark-next-like-this)
           ("C-<" . mc/mark-previous-like-this)
           ("C-c C-<" . mc/mark-all-like-this)))

  (use-package ace-mc
    :after multiple-cursors ace-jump-mode
    :bind (("C-c m m" . ace-mc-add-multiple-cursors)
           ("C-c m s" . ace-mc-add-single-cursor)))

Auto Complete

Setup fancy auto-complete

  (use-package auto-complete)
  (use-package auto-complete-config
    :after auto-complete
    :config (progn
              (ac-config-default)
              ;; Set trigger keys so yasnippet and auto-complete play nicely. If tab is pressed
              ;; and the word at point exists in yasnippet, then yassippet will be used;
              ;; otherwise auto-complete will be used.
              (ac-set-trigger-key "TAB")
              (ac-set-trigger-key "<tab>")))
  ;;(add-to-list 'ac-dictionary-directories "/usr/share/emacs/site-lisp/auto-complete/ac-dict")

Yaml

  (use-package yaml-mode
    :config (add-hook 'yaml-mode-hook
                      (lambda ()
                        (make-variable-buffer-local 'yas-indent-line)
                        (setq yas-indent-line nil))))

Yasnippet

  ;; Courtesy of https://github.com/makp/emacs-configs/blob/master/mk_yasnippet-setup.el
  (defun shk-yas/helm-prompt (prompt choices &optional display-fn)
    "Use helm to select snippet choices."
    (interactive)
    (setq display-fn (or display-fn 'identity))
    (if (require 'helm-config)
        (let (tmpsource cands result rmap)
          (setq cands (mapcar (lambda (x) (funcall display-fn x)) choices))
          (setq rmap (mapcar (lambda (x) (cons (funcall display-fn x) x)) choices))
          (setq tmpsource
                (list
                 (cons 'name prompt)
                 (cons 'candidates cands)
                 '(action . (("Expand" . (lambda (selection) selection))))
                 ))
          (setq result (helm-other-buffer '(tmpsource) "*helm-select-yasnippet"))
          (if (null result)
              (signal 'quit "user quit!")
            (cdr (assoc result rmap))))
      nil))

  (use-package yasnippet
    :defer t
    :config (progn
              (if is-guix-system-p
                  (yas-load-directory "~/.guix-home/profile/share/emacs/site-lisp/yasnippet-snippets-1.0/snippets")
                (yas-load-directory "~/.emacs.d/elpa/yasnippet-snippets-20200802.1658"))
              (yas-load-directory (no-littering-expand-etc-file-name "snippets"))
              (setq-default
               ac-sources (push 'ac-source-yasnippet ac-sources)
               yas-prompt-functions '(shk-yas/helm-prompt yas-dropdown-prompt))
              (yas-global-mode 1)))

  (use-package helm-c-yasnippet
    :bind (("C-<tab>" . helm-yas-complete))
    :config (setq helm-yas-space-match-any-greedy t))

  (use-package auto-yasnippet
    :bind (("C-c w" . aya-create)
           ("C-c e" . aya-expand)))

Rebox2

  ;; TODO: this is breaks multiple cursors (rebox replaces yank and other commands)
  ;; url: http://www.emacswiki.org/emacs/rebox2
  (use-package rebox2
    :init (setq rebox-style-loop '(10 11 12 13 15 16 17 20 21 22 23 26 27))
    :config (define-globalized-minor-mode global-rebox-mode rebox-mode rebox-mode))

God Mode

  (use-package god-mode
    :config (progn
              ;;(require 'god-mode-isearch)
              ;;(define-key isearch-mode-map (kbd "<escape>") 'god-mode-isearch-activate)
              ;;(define-key god-mode-isearch-map (kbd "<escape>") 'god-mode-isearch-disable)

              (define-key god-local-mode-map (kbd ".") 'repeat)
              (define-key god-local-mode-map (kbd "i") 'god-local-mode)

              (setq god-exempt-major-modes
                    (append god-exempt-major-modes
                            '(eshell-mode term-mode rcirc-mode mu4e-main-mode
                              mu4e-view-mode mu4e-headers-mode mu4e-compose-mode)))
              ;; (setq god-exempt-predicates nil)

              (defun c/god-mode-update-cursor ()
                (cond (god-local-mode (progn
                                        (set-face-background 'mode-line "grey20")
                                        (set-face-foreground 'mode-line "#1ddeaa")
                                        (set-face-background 'mode-line-inactive "grey3")
                                        (set-face-foreground 'mode-line-inactive "grey80")))
                        (t (progn
                             (set-face-background 'mode-line "grey20")
                             (set-face-foreground 'mode-line "#00afff")
                             (set-face-background 'mode-line-inactive "grey5")
                             (set-face-foreground 'mode-line-inactive "grey80")))))

              (add-hook 'god-mode-enabled-hook 'c/god-mode-update-cursor)
              (add-hook 'god-mode-disabled-hook 'c/god-mode-update-cursor))
    ;; This kills the ESC prefix keymap, so I may replace it with a different binding
    :bind ("<escape>" . god-mode-all))

Dired

  ;; Enable dired-find-alternative-file
  (put 'dired-find-alternate-file 'disabled nil)

  ;; Use human readable file sizes
  (setq dired-listing-switches "-alh")

  ;; Setup omit-mode in dired
  (require 'dired-x)
  (setq-default dired-omit-files-p t) ; Buffer-local variable
  (setq dired-omit-files (concat dired-omit-files "\\|^\\..+$"))

  ;; Use dired-subtree from dired-hacks
  (use-package dired-subtree
    :config (bind-keys :map dired-mode-map
               ("i" . dired-subtree-insert)
               (";" . dired-subtree-remove)))

  ;; Use dired-narrow from dired-hacks to narrow dired to match filter
  (use-package dired-narrow
    :bind (:map dired-mode-map
                ("/" . dired-narrow)))

Undo Tree

  ;; Setup undo-tree
  (use-package undo-tree
    :config (progn
              (global-undo-tree-mode)))

Code Folding

  (use-package vimish-fold
    :config (vimish-fold-global-mode 1)
    :bind (("C-x n f" . vimish-fold)
           ("C-x n F" . vimish-fold-refold-all)
           ("C-x n k" . vimish-fold-delete)
           ("C-x n u" . vimish-fold-unfold-all)))

TODO Debugging

Currently this is broken and results in a non-blocking error upon emacs startup.

  (use-package realgud
    :disabled)

Swiper

  (use-package swiper
    :bind (("C-c s" . swiper)))

Modes

Winner

  (use-package winner
    :init (winner-mode +1)
    :bind (("<M-left>" . winner-undo)
           ("<M-right>" . winner-redo)))

Magit

  (defun magit-status-with-prefix ()
    "Call magit-status with the prefix key"
    (interactive)
    (let ((current-prefix-arg '(4)))
      (call-interactively 'magit-status)))

  (use-package magit
    :init (setq magit-commit-signoff t
                magit-status-buffer-switch-function 'switch-to-buffer
                magit-last-seen-setup-instructions "1.4.0"
                vc-follow-symlinks t)
    :bind (("C-x g" . magit-status)
           ("C-x G" . magit-status-with-prefix)))

Use magit-forge to interact with git forges.

  (use-package forge
    :after magit
    :config (setq forge-alist
                  (append forge-alist
                          '(("git.rekahsoft.ca:2222" "git.rekahsoft.ca/api/v1"
                             "git.rekahsoft.ca" forge-gitea-repository)))))

Use orgit to allow links to magit from org-mode files.

  (use-package orgit
    :after org
    :config
    ;; Allow org-export to work with magit links to repositories in my personal git forges
    (setq orgit-export-alist
          (append orgit-export-alist
                  '(("git.home.rekahsoft.ca[:/]\\(.+\\)$"
                     "https://git.home.rekahsoft.ca/%n" "https://git.home.rekahsoft.ca/%n/commits/%r" "https://git.home.rekahsoft.ca/%n/commit/%r")
                    ("git.rekahsoft.ca[:/]\\(.+\\)$"
                     "https://git.rekahsoft.ca/%n" "https://git.rekahsoft.ca/%n/commits/%r" "https://git.rekahsoft.ca/%n/commit/%r")))))

Calendar

  (setq calendar-latitude 43.1
        calendar-longitude -80.7
        calendar-location-name "Woodstock, ON")
  (setq calendar-time-zone -300
        calendar-standard-time-zone-name "EST"
        calendar-daylight-time-zone-name "EDT")

Provide functions to show sunset and sunrise separately in org-mode diary/agenda

  ;; Taken from: https://www.reddit.com/r/orgmode/comments/a1z26t/sunrise_sunset_as_separate_entries_on_agenda_view/
  (use-package solar)

  (defun solar-sunrise-string (date &optional nolocation)
    "String of *local* time of sunrise and daylight on Gregorian DATE."
    (let ((l (solar-sunrise-sunset date)))
      (format
       "%s (%s hours daylight)"
       (if (car l)
           (concat "Sunrise " (apply 'solar-time-string (car l)))
         "no sunrise")
       (nth 2 l)
       )))
  ;; To be called from diary-list-sexp-entries, where DATE is bound.
  ;;;###diary-autoload
  (defun diary-sunrise ()
    "Local time of sunrise as a diary entry.
    Accurate to a few seconds."
    (or (and calendar-latitude calendar-longitude calendar-time-zone)
       (solar-setup))
    (solar-sunrise-string date))

  (defun solar-sunset-string (date &optional nolocation)
    "String of *local* time of sunset and daylight on Gregorian DATE."
    (let ((l (solar-sunrise-sunset date)))
      (format
       "%s (%s hours daylight)"
       (if (cadr l)
           (concat "Sunset " (apply 'solar-time-string (cadr l)))
         "no sunset")
       (nth 2 l)
       )))
  ;; To be called from diary-list-sexp-entries, where DATE is bound.
  ;;;###diary-autoload
  (defun diary-sunset ()
    "Local time of sunset as a diary entry.
    Accurate to a few seconds."
    (or (and calendar-latitude calendar-longitude calendar-time-zone)
       (solar-setup))
    (solar-sunset-string date))

Org Mode

Use gnuplot alongside org-mode to generate plots.

  (use-package gnuplot
    :defer t)
  (use-package org
    :custom org-modules (append '(org-habit) org-modules)
    :config (progn
              (setq org-directory "~/.org"
                    org-attach-directory (concat (file-name-as-directory org-directory) "data")
                    org-todo-keywords '((sequence "TODO(t)" "NEXT(n!/!)" "DOING(s!/!)" "|" "DONE(d!/@)")
                                        (sequence "WAIT(w@/!)" "PAUSE(p@/!)" "|" "CANCELED(c@/@)" "PHONE(P!)")
                                        (sequence "REPLY(r!)" "|" "SENT(S!)"))
                    org-todo-keyword-faces '(("TODO" . org-warning)
                                             ("DOING" . "cyan")
                                             ("DONE" . org-done)
                                             ("WAIT" . "orange red")
                                             ("PAUSE" . "yellow")
                                             ("CANCELED" . (:foreground "orange" :weight bold))
                                             ("PHONE" . org-done)
                                             ("REPLY" . org-warning)
                                             ("SENT" . org-done))
                    org-agenda-sorting-strategy '((agenda time-up priority-down category-keep)
                                                  (todo priority-down category-keep)
                                                  (tags priority-down category-keep)
                                                  (search category-keep))
                    org-agenda-window-setup 'current-window
                    org-agenda-clockreport-parameter-plist '(:link t :maxlevel 3 :hidefiles t :properties ("CATEGORY"))

                    ;; Use org-indent-mode globally
                    org-startup-indented t

                    ;; Ensure that org-add-note puts notes after drawers
                    ;; See: https://emacs.stackexchange.com/questions/17282/org-mode-logbook-note-entry-without-logbook-drawer
                    org-log-state-notes-insert-after-drawers t

                    ;; Set org-jump default interface. The org-jump outline can still be
                    ;; accessed using a universal prefix
                    ;; See: https://emacs.stackexchange.com/questions/32617/how-to-jump-directly-to-an-org-headline
                    org-goto-interface 'outline-path-completion

                    ;; Do not save archive file automatically (this allows for a nicer recursive archiving, with large encrypted org files)
                    org-archive-subtree-save-file-p nil

                    org-return-follows-link t
                    org-id-link-to-org-use-id 'use-existing
                    org-log-done 'time
                    org-src-fontify-natively t
                    org-enforce-todo-dependencies t
                    org-refile-use-outline-path t
                    org-global-properties '(("Effort_ALL" . "0 0:10 0:15 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00"))
                    org-columns-default-format "%65ITEM(Task) %TODO %3PRIORITY %8Effort(Effort){:} %CLOCKSUM %SCHEDULED %DEADLINE %TAGS"
                    org-complete-tags-always-offer-all-agenda-tags t

                    ;; Setup external applications used when org-mode opens files
                    ;; See: https://orgmode.org/worg/org-faq.html#external-application-launched-to-open-file-link
                    org-file-apps '((auto-mode . emacs)
                                    (directory . emacs)
                                    ("\\.mm\\'" . default)
                                    ("\\.x?html?\\'" . "xdg-open %s")
                                    ("\\.pdf\\'" . default)))

              ;; Setup org-crypt
              (setq org-crypt-key "E05BFEC8"
                    org-crypt-disable-auto-save 'encrypt)
              (org-crypt-use-before-save-magic)

              ;; Enable markdown export
              (require 'ox-md)

              ;; Enable koma-letter support
              ;; See: https://orgmode.org/worg/exporters/koma-letter-export.html
              (require 'ox-koma-letter)

              (setq org-agenda-files
                    (let ((agenda-dir "~/.org/roam/agenda")
                          (agenda-files '()))
                      (append
                       ;; Include manually specified agenda-files
                       agenda-files
                       ;; Include all *.org{,.gpg} files in agenda-dir
                       (if (file-directory-p agenda-dir)
                           (let* ((all-org-agenda-dir-files (directory-files-recursively
                                                             agenda-dir
                                                             "^[.-a-zA-Z]+\.org\\(\.gpg\\)?$" t t))
                                  (all-org-agenda-dir-archive-files (seq-filter (apply-partially #'string-match (concat "^" agenda-dir "[.-a-zA-Z]+\-archive.org\\(\.gpg\\)?$")) all-org-agenda-dir-files))
                                  (all-org-agenda-dir-files-sans-archive-files (cl-set-difference all-org-agenda-dir-files all-org-agenda-dir-archive-files)))
                             (append all-org-agenda-dir-files-sans-archive-files
                                     all-org-agenda-dir-archive-files)))))
                    org-refile-targets `((nil :maxlevel . 5)
                                         (,org-agenda-files :maxlevel . 3)))

              ;; This provides a much more usable experience with org-refile from helm
              (setq org-outline-path-complete-in-steps nil
                    org-refile-use-outline-path t)

              ;; Set org-timer options
              (setq org-timer-default-timer "0:25:00"
                    org-clock-sound (no-littering-expand-etc-file-name "assets/beep.wav")
                    org-show-notification-timeout 5)

              ;; Persist org-mode clocks between emacs sessions
              (setq org-clock-persist 'history)
              (org-clock-persistence-insinuate)

              ;; Enable org-mode capture
              (setq org-default-notes-file "~/.org/roam/agenda/capture.org")

              (setq org-capture-templates
                    '(("t" "Templates for tasks")
                      ("tt" "Task" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* TODO %?\n %i\n")
                      ("tT" "Immediate task" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* DOING %?\n%T\n%i\n" :clock-in t :clock-resume t)
                      ("te" "Task regarding a particular email" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* TODO Follow up on %a from '%:fromname' %?\n%U\n")
                      ("tE" "Immediately start task regarding a particular email" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* Doing Follow up on %a from %:fromname %?\n%U\n" :clock-in t :clock-resume t)
                      ("tf" "Task with regards to a particular file" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* TODO %?\nFile: [[%F]]\n")
                      ("tF" "Task with regards to a particular file" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* TODO %?\nFile: [[%F]]\n" :clock-in t :clock-keep t)

                      ("a" "Templates for appointments")
                      ("aa" "Appointment" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* %?\n%i%^T")
                      ("aA" "Immediate appointment" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* %?\n %i\n%i%T" :clock-in t :clock-resume t)
                      ("ab" "Immediate break" entry
                       (file+olp "~/.org/roam/agenda/noredink-agenda.org.gpg" "Breaks")
                       "* %?\n%i%i%T" :clock-in t :clock-resume t)

                      ("r" "Templates for replies")
                      ("re" "Reply to a particular email" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* REPLY to '%:fromname' regarding %a%?\n%U\n")
                      ("rE" "Immediately reply to a particular email" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* SENT to %:fromname regarding %a%?\n%U\n" :clock-in t :clock-resume t)

                      ("c" "Phone call" entry
                       (file+olp "~/.org/roam/agenda/capture.org" "Triage")
                       "* PHONE %?\n %U\n" :clock-in t :clock-resume t)

                      ;; ;; TODO: these should likely be moved into org-roam-dailies capture templates
                      ;; ("l" "Templates for logging")
                      ;; ("ll" "Log" entry
                      ;;  (file+datetree "~/.org/logbook.org")
                      ;;  "* %?\n Entered on %U\n %i\n")
                      ;; ("lf" "Log with file link" entry
                      ;;  (file+datetree "~/.org/logbook.org")
                      ;;  "* %?\n Entered on %U\n %i\n %a")
                      ))

              ;; Automatically give all capture entries an id
              (add-hook 'org-capture-mode-hook #'org-id-get-create)

              ;; Use minted (requires python package pygments) for exported source code listings when using
              ;; pdflatex
              ;; See: https://emacs.stackexchange.com/questions/27154/exporting-highlighted-source-code-to-pdf-in-org-mode
              (add-to-list 'org-latex-packages-alist '("" "minted"))
              (setq org-latex-listings 'minted
                    org-latex-pdf-process
                    '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
                      "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
                      "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))

              ;; Add additional languages for org-babel (now part of org-mode)
              (org-babel-do-load-languages
               'org-babel-load-languages
               '((haskell . t)
                 (gnuplot . t)
                 (emacs-lisp . t)
                 (C . t)
                 (python . t)
                 (awk . t)
                 (clojure . t)
                 (ditaa . t)
                 (dot . t)
                 (groovy . t)
                 (java . t)
                 (python . t)
                 (lisp . t)
                 (ledger . t)
                 (makefile . t)
                 (scheme . t)
                 (plantuml . t)
                 (octave . t)
                 (rec . t)
                 (sql . t)
                 (sqlite . t)
                 (shell . t)))

             (setq org-plantuml-exec-mode 'plantuml))
    :bind (("C-c c" . org-capture)
           ("C-c l" . org-store-link)
           ("C-c a" . org-agenda)
           ("C-c b" . org-switchb)
           ("C-c L" . org-insert-link-global)
           ("C-c o" . org-open-at-point-global)
           ("C-c C-/" . org-decrypt-entries)))
  ;; Enable easy templates
  (use-package org-tempo
    :after org)
  (use-package org-expiry
    :config (progn
              (org-expiry-insinuate)
              (setq org-expiry-inactive-timestamps t))
    :after org
    :hook ((org-capture-mode-hook org-expiry-insert-created)))
  ;; GUIX TODO: This package is not available
  ;; (use-package org-magit
  ;;   :after org)
  ;; TODO:  this package is not available on guix
  ;; (use-package org-ac
  ;;   :after org
  ;;   :config (org-ac/config-default))

Setup org-projectile

  (use-package org-projectile
    :bind (("C-c f p" . org-projectile-project-todo-completing-read))
    :config
    (progn
      (setq org-projectile-projects-file
                "~/.org/roam/20210111004034-projects.org")
      (setq org-agenda-files (append org-agenda-files (org-projectile-todo-files))
            org-refile-targets `((nil :maxlevel . 5)
                                 (,org-agenda-files :maxlevel . 3)))
      (add-to-list 'org-capture-templates (org-projectile-project-todo-entry) t)))

Setup org-download

  (use-package org-download
      :after org
      :config (setq org-download-screenshot-method "scrot -s %s")
      :custom
      (org-download-method 'attach)
      :bind (:map org-mode-map
                  ("C-c d y" . org-download-yank)
                  ("C-c d s" . org-download-screenshot)
                  ("C-c d e" . org-download-edit)))

Setup org-roam

  (use-package org-roam
        :init
        (setq org-roam-v2-ack t)
        :custom
        ;; TODO: these settings need to be reviewed after the upgrade to v2
        (org-roam-directory (expand-file-name "~/.org/roam"))
        (org-roam-graph-viewer nil)
        (org-roam-db-update-method 'immediate)
        (org-roam-index-file "Index") ; Default
        (org-roam-node-display-template
         (concat "${title:*} " (propertize "${tags:45}" 'face 'org-tag)))
        (org-roam-capture-templates
         ;; TODO: ROAM_ref (formally roam_key) can not be set in the file PROPERTIES in roam v2
         ;; See: https://github.com/org-roam/org-roam/issues/1920
         '(("d" "default" plain "%?"
            :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                               "#+title: ${title}
    ")
            :unnarrowed t)
           ("p" "person" entry "* %^{name}%?"
            :target (node "863ddb0b-ed30-4cf6-86c7-915afda0c0d1"))
           ("b" "book" entry "* %^{name}%?"
            :target (node "64a98813-bdf8-4813-9f4e-e1153170ffa9"))
           ("s" "software" entry "* %^{name}
  %?"
            :target (node "d0c93085-3e96-4f4b-ad83-ced92cf4de33"))))
        (org-roam-dailies-directory "")
        (org-roam-dailies-capture-templates
              '(("d" "default" entry "* %?" :target (file+datetree "dailies.org.gpg" week))))
        :bind (("C-c n l" . org-roam-buffer-toggle)
               ("C-c n f" . org-roam-node-find)
               ("C-c n F" . org-roam-ref-find)
               ("C-c n g" . org-roam-graph)
               ("C-c n D" . org-roam-doctor)

               ("C-c n i" . org-roam-node-insert)
               ("C-c n I" . org-roam-node-insert-section)
               ("C-c n a" . org-roam-alias-add)
               ("C-c n A" . org-roam-alias-remove)
               ("C-c n ." . org-roam-tag-add)
               ("C-c n <" . org-roam-tag-remove)
               ("C-c n r" . org-roam-ref-add)
               ("C-c n R" . org-roam-ref-remove)

               ("C-c n c" . org-roam-capture)
               ("C-c n y" . org-roam-dailies-goto-yesterday)
               ("C-c n t" . org-roam-dailies-goto-today)
               ("C-c n T" . org-roam-dailies-goto-tomorrow)
               ("C-c n d" . org-roam-dailies-goto-date))
        :config
        (org-roam-db-autosync-enable))

  (use-package org-roam-protocol
    :after org-roam)

  (use-package org-roam-export
    :after org-roam)
TODO [C] org-roam files pollute .emacs.d   open_source

This can be adjusted via the customize variable org-roam-db-location. However, this should also be pushed upstream to the no-littering package. The default value is ~/.emacs.d/org-roam.db.

TODO Turn on org-roam encryption

Encryption via gpg can be enabled transparently for org-roam org files. This can be enabled via the customize variable org-roam-encrypt-files.

Setup org-roam-ui
  (use-package websocket
      :after org-roam)

  (use-package org-roam-ui
      :after org-roam
      :config
      (progn
        (setq org-roam-ui-sync-theme t
              org-roam-ui-follow t
              org-roam-ui-update-on-save t
              org-roam-ui-open-on-start nil)
        (when (daemonp)
          (org-roam-ui-mode))))

Setup org-ql

  (use-package org-ql
    :bind (("C-c f D" . org-ql-find-in-org-directory)
           ("C-c f A" . org-ql-find-in-agenda)
           ("C-c f s" . org-ql-search)
           ("C-c f v" . org-ql-view))
    :after org
    :custom
    (org-ql-search-directories-files-recursive t)
    (org-ql-views
     (list (cons "Projects: All tasks"
                 (list :buffers-files #'org-agenda-files
                       :query '(and (tags "project") (todo))
                       :sort '(todo deadline priority date)
                       :super-groups '((:auto-outline-path))
                       :title "All Project Tasks"))
           (cons "Projects: This Project's Tasks"
                 (lambda ()
                   "Show current projects tasks."
                   (interactive)
                   (let ((project-name (file-name-nondirectory
                                        (directory-file-name
                                         (project-root (project-current))))))
                     (org-ql-search (list org-projectile-projects-file)
                       `(and (todo) (category ,project-name))
                       :sort '(todo deadline priority date)
                       :super-groups '((:auto-outline-path))
                       :title (concat "Project Tasks (" project-name ")")))))
           (cons "Overview: Agenda-like"
                 (list :buffers-files #'org-agenda-files
                       :query '(and (not (done))
                                    (or (habit)
                                        (deadline auto)
                                        (scheduled :to today)
                                        (ts-active :on today)))
                       :sort '(todo priority date)
                       :super-groups 'org-super-agenda-groups
                       :title "Agenda-like"))
           (cons "Overview: NEXT tasks"
                 (list :buffers-files #'org-agenda-files
                       :query '(todo "NEXT")
                       :sort '(date priority)
                       :super-groups 'org-super-agenda-groups
                       :title "Overview: NEXT tasks"))
           (cons "Calendar: Today"
                 (list :buffers-files #'org-agenda-files
                       :query '(ts-active :on today)
                       :title "Today"
                       :super-groups 'org-super-agenda-groups
                       :sort '(priority)))
           (cons "Calendar: This week"
                 (lambda ()
                   "Show items with an active timestamp during this calendar week."
                   (interactive)
                   (let* ((ts (ts-now))
                          (beg-of-week (->> ts
                                            (ts-adjust 'day (- (ts-dow (ts-now))))
                                            (ts-apply :hour 0 :minute 0 :second 0)))
                          (end-of-week (->> ts
                                            (ts-adjust 'day (- 6 (ts-dow (ts-now))))
                                            (ts-apply :hour 23 :minute 59 :second 59))))
                     (org-ql-search (org-agenda-files)
                       `(ts-active :from ,beg-of-week
                                   :to ,end-of-week)
                       :title "This week"
                       :super-groups 'org-super-agenda-groups
                       :sort '(priority)))))
           (cons "Calendar: Next week"
                 (lambda ()
                   "Show items with an active timestamp during the next calendar week."
                   (interactive)
                   (let* ((ts (ts-adjust 'day 7 (ts-now)))
                          (beg-of-week (->> ts
                                            (ts-adjust 'day (- (ts-dow (ts-now))))
                                            (ts-apply :hour 0 :minute 0 :second 0)))
                          (end-of-week (->> ts
                                            (ts-adjust 'day (- 6 (ts-dow (ts-now))))
                                            (ts-apply :hour 23 :minute 59 :second 59))))
                     (org-ql-search (org-agenda-files)
                       `(ts-active :from ,beg-of-week
                                   :to ,end-of-week)
                       :title "Next week"
                       :super-groups 'org-super-agenda-groups
                       :sort '(priority)))))
           (cons "Review: Recently timestamped" #'org-ql-view-recent-items)
           (cons (propertize "Review: Dangling tasks"
                             'help-echo "Tasks whose ancestor is done")
                 (list :buffers-files #'org-agenda-files
                       :query '(and (todo)
                                    (ancestors (done)))
                       :title (propertize "Review: Dangling tasks"
                                          'help-echo "Tasks whose ancestor is done")
                       :sort '(todo priority date)
                       :super-groups '((:auto-parent t))))
           (cons (propertize "Review: Stale tasks"
                             'help-echo "Tasks without a timestamp in the past 2 weeks")
                 (list :buffers-files #'org-agenda-files
                       :query '(and (todo)
                                    (not (ts :from -14)))
                       :title (propertize "Review: Stale tasks"
                                          'help-echo "Tasks without a timestamp in the past 2 weeks")
                       :sort '(todo priority date)
                       :super-groups '((:auto-parent t))))
           (cons (propertize "Review: Stuck projects"
                             'help-echo "Tasks with sub-tasks but no NEXT sub-tasks")
                 (list :buffers-files #'org-agenda-files
                       :query '(and (todo)
                                    (descendants (todo))
                                    (not (descendants (todo "NEXT"))))
                       :title (propertize "Review: Stuck projects"
                                          'help-echo "Tasks with sub-tasks but no NEXT sub-tasks")
                       :sort '(date priority)
                       :super-groups 'org-super-agenda-groups)))))
Setup helm-org-ql
  (use-package helm-org-ql
    :bind (("C-c f d" . helm-org-ql-org-directory)
           ("C-c f a" . helm-org-ql-agenda-files)))

Setup org-noter

  (use-package org-noter)

Setup org-super-agenda

  (use-package org-super-agenda
    :config
    (progn
      (setq org-super-agenda-groups
            '((:name "Today"
                     :time-grid t)
              (:name "Important"
                     :priority "A")
              (:name "In Progress"
                     :todo "DOING")
              (:name "Next Up"
                     :todo "NEXT")
              (:name "Paused"
                     :todo "PAUSE")
              (:name "Waiting"
                     :todo "WAIT")
              (:auto-category t))

            ; This will be supported in 1.3:
            ;   :org-super-agenda-hide-empty-groups
            org-agenda-custom-commands (append
                                        org-agenda-custom-commands
                                        '(("xp" "All TODOs groups by parent" alltodo ""
                                           ((org-super-agenda-groups '((:auto-parent t)))))
                                          ("xd" "All TODOs groups by scheduled date or deadline" alltodo ""
                                           ((org-super-agenda-groups '((:auto-planning t)))))
                                          ("xc" "All TODOs groups by category" alltodo ""
                                           ((org-super-agenda-groups '((:auto-category t)))))))
            org-super-agenda-date-format "%d %B %Y")
      (org-super-agenda-mode)))

treemacs

  (use-package treemacs
    :defer t
    :init
    (with-eval-after-load 'winum
      (define-key winum-keymap (kbd "M-0") #'treemacs-select-window))
    :config
    (progn
      (setq treemacs-collapse-dirs                 (if treemacs-python-executable 3 0)
            treemacs-deferred-git-apply-delay      0.5
            treemacs-directory-name-transformer    #'identity
            treemacs-display-in-side-window        t
            treemacs-eldoc-display                 t
            treemacs-file-event-delay              5000
            treemacs-file-extension-regex          treemacs-last-period-regex-value
            treemacs-file-follow-delay             0.2
            treemacs-file-name-transformer         #'identity
            treemacs-follow-after-init             t
            treemacs-git-command-pipe              ""
            treemacs-goto-tag-strategy             'refetch-index
            treemacs-indentation                   2
            treemacs-indentation-string            " "
            treemacs-is-never-other-window         nil
            treemacs-max-git-entries               5000
            treemacs-missing-project-action        'ask
            treemacs-move-forward-on-expand        nil
            treemacs-no-png-images                 nil
            treemacs-no-delete-other-windows       t
            treemacs-project-follow-cleanup        nil
            treemacs-position                      'left
            treemacs-recenter-distance             0.1
            treemacs-recenter-after-file-follow    nil
            treemacs-recenter-after-tag-follow     nil
            treemacs-recenter-after-project-jump   'always
            treemacs-recenter-after-project-expand 'on-distance
            treemacs-show-cursor                   nil
            treemacs-show-hidden-files             t
            treemacs-silent-filewatch              nil
            treemacs-silent-refresh                nil
            treemacs-sorting                       'alphabetic-asc
            treemacs-space-between-root-nodes      t
            treemacs-tag-follow-cleanup            t
            treemacs-tag-follow-delay              1.5
            treemacs-user-mode-line-format         nil
            treemacs-user-header-line-format       nil
            treemacs-width                         35
            treemacs-workspace-switch-cleanup      nil)

      ;; TODO: temporary workaround as in these fonts are missing
      (add-to-list 'all-the-icons-data/file-icon-alist '("dart" . "\xEB29"))
      (add-to-list 'all-the-icons-data/file-icon-alist '("fsharp" . "\xEB29"))

      ;; The default width and height of the icons is 22 pixels. If you are
      ;; using a Hi-DPI display, uncomment this to double the icon size.
      ;;(treemacs-resize-icons 44)

      (treemacs-follow-mode t)
      (treemacs-filewatch-mode t)
      (treemacs-fringe-indicator-mode t)
      (pcase (cons (not (null (executable-find "git")))
                   (not (null treemacs-python-executable)))
        (`(t . t)
         (treemacs-git-mode 'deferred))
        (`(t . _)
         (treemacs-git-mode 'simple))))
    :bind
    (:map global-map
          ("M-0"       . treemacs-select-window)
          ("C-x t 1"   . treemacs-delete-other-windows)
          ("C-x t t"   . treemacs)
          ("C-x t B"   . treemacs-bookmark)
          ("C-x t C-t" . treemacs-find-file)
          ("C-x t M-t" . treemacs-find-tag)))

  (use-package treemacs-projectile
    :after treemacs projectile)

  (use-package treemacs-all-the-icons
    :after treemacs all-the-icons
    :config (treemacs-load-theme 'all-the-icons))

  (use-package treemacs-icons-dired
    :config (treemacs-icons-dired-mode 1))

  (use-package treemacs-magit
    :after treemacs magit)

  ;; (use-package treemacs-persp ;;treemacs-persective if you use perspective.el vs. persp-mode
  ;;   :after treemacs persp-mode ;;or perspective vs. persp-mode
  ;;   :config (treemacs-set-scope-type 'Perspectives))

Ledger

  (use-package ledger-mode
    :config (setq ledger-binary-path "ledger"))

  (use-package flycheck-ledger
    :after flycheck)

Dashboard

Use emacs-dashboard.

  (use-package dashboard
    :custom (dashboard-startup-banner 'logo)
    :init (setq dashboard-set-heading-icons t
            dashboard-set-file-icons t
            dashboard-set-navigator t
            dashboard-items '((recents  . 5)
                              (bookmarks . 5)
                              (projects . 5)
                              (agenda . 5)
                              (registers . 5)))
    :config
    (progn
      (dashboard-setup-startup-hook)

      (defun dashboard ()
        (interactive)
        (let ((buffer "*dashboard*"))
          (when (not (get-buffer buffer))
            (dashboard-insert-startupify-lists))
          (switch-to-buffer buffer))))
    :bind (("C-c d" . dashboard)))

Rust

  (use-package rust-mode)

Nix

  (use-package nix-mode
    :mode "\\.nix\\'")

DHall

(use-package dhall-mode
  :mode "\\.dhall\\'")

TODO Lispy

Slime has been temporarily commented out below as it broke during an upgrade from 2.27 -> 2.28 (likely an issue specific to guix). The error was:

File is missing: Cannot open load file, No such file or directory, ../lib/macrostep

Briefly looking at the guix package, it appears that the emacs-macrostep package is not specified as a native-dependency (which may be a new dependency in 2.28).

  ;; Set default lisp program
  (setq inferior-lisp-program "/usr/bin/sbcl")

  ;; Since there is no support for the kawa implementation of scheme
  (defun run-kawa ()
    "Run Kawa Scheme in an Emacs buffer."
    (interactive)
    (require 'cmuscheme) ;; Built-in
    (let ((scheme-program-name "/usr/bin/kawa"))
      (run-scheme scheme-program-name)))

  ;; Function to start and/or connect to slime
  (defun start-slime ()
    (interactive)
    (unless (slime-connected-p)
      (save-excursion (slime))))

  ;; Setup slime mode
  ;(require 'slime-autoloads) ;; package
  ;(slime-setup '(slime-fancy))

  ;; Set usable lisp implementations
  (setq slime-lisp-implementations
        '((sbcl ("/usr/bin/sbcl" ""))
          (clisp ("/usr/bin/clisp" "-K base"))))

  ;; Setup enhanced scheme/racket mode consisting of geiser, quack and paredit
  ;; Setup geiser
  (use-package geiser
  ;; GUIX TODO: ac-geiser is not packaged upstream and causes this to break
  ;  :config (use-package ac-geiser)
    )

  ;; GUIX TODO: This breaks geiser on guix
  ;; ;; Setup auto-completion for geiser (ELPA)
  ;; (add-hook 'geiser-mode-hook 'ac-geiser-setup)
  ;; (add-hook 'geiser-repl-mode-hook 'ac-geiser-setup)
  ;; (eval-after-load "auto-complete"
  ;;   '(add-to-list 'ac-modes 'geiser-repl-mode))

  ;; Make struct stand out in scheme-mode for racket
  (defun racket-faces ()
    (font-lock-add-keywords nil
        '(("(struct \\(\\sw+\\)" 1 font-lock-function-name-face)
          ("(\\(struct\\)" 1 font-lock-keyword-face)
          ("(\\(λ\\)" 1 font-lock-function-name-face))))

  (add-hook 'scheme-mode-hook 'racket-faces)
  (add-hook 'geiser-repl-mode-hook 'racket-faces)

  ;; Setup scribble mode
  (use-package scribble-mode)

  ;; Setup quack
  (use-package quack)

  (defvar lispy-langs-hooks '(lisp-mode-hook lisp-interaction-mode-hook emacs-lisp-mode-hook scheme-mode-hook c-mode-hook c++-mode-hook python-mode-hook geiser-repl-mode-hook))

  ;; Setup paredit
  (use-package paredit
    :config (activate-mode-with-hooks (lambda () (paredit-mode 1)) lispy-langs-hooks))

  ;; Highlight sexp's in lispy languages
  (use-package highlight-sexp
    :config (activate-mode-with-hooks (lambda () (highlight-sexp-mode)) lispy-langs-hooks))

  ;; Paredit binds to C-j globally and thus disables the binding to
  ;; eval-print-last-sexp in emacs-lisp-mode (e.g *scratch*, etc..)
  (add-hook 'emacs-lisp-mode-hook
            #'(lambda ()
                (define-key emacs-lisp-mode-map "\C-xj" 'eval-print-last-sexp)))

  ;; Match paren's in given modes [to apply globally do (show-paren-mode 1)]
  (activate-mode-with-hooks (lambda () (show-paren-mode)) lispy-langs-hooks)

  ;; Highlight paren's near point
  (use-package highlight-parentheses
    :config (activate-mode-with-hooks (lambda () (highlight-parentheses-mode)) lispy-langs-hooks))

  ;; Setup rainbow-delimiters
  (use-package rainbow-delimiters
    :config (activate-mode-with-hooks 'rainbow-delimiters-mode-enable lispy-langs-hooks))
  ;;(global-rainbow-delimiters-mode) ;; TODO: breaks font coloring in erc for some reason?

  (use-package rainbow-mode
    :defer t
    :hook ((css-mode . rainbow-mode)))

  ;; upcomming functionallity: toggle paredit-mode due to annoying things like wrapping parens when a mistake is made
  ;; known issue..if paredit-mode is turned on when there are unbalanced parens an error is reported
  (defun toggle-paredit-mode ()
    (let ((active-minor-modes (list)))
      (mapatoms (lambda (sym)
                  (when (and (symbolp sym) (assq sym minor-mode-alist) (symbol-value sym))
                    (push sym active-minor-modes))))
      (if (member 'paredit-mode active-minor-modes) (paredit-mode -1) (paredit-mode 1))))

  (setq geiser-repl-use-other-window nil
        geiser-active-implementations '(racket guile))

Haskell

  ;; Thanks to: https://github.com/haskell/haskell-mode/wiki/Haskell-Interactive-Mode-Setup
  ;;            https://github.com/haskell/haskell-mode/blob/master/examples/init.el

  (custom-set-variables
   ;; Use notify.el (if you have it installed) at the end of running
   ;; Cabal commands or generally things worth notifying.
   '(haskell-notify-p t)

   ;; To enable tags generation on save.
   '(haskell-tags-on-save t)

   ;; To enable stylish on save.
   '(haskell-stylish-on-save t)

   '(haskell-process-suggest-remove-import-lines t)
   '(haskell-process-auto-import-loaded-modules t)
   '(haskell-process-log t))

  (add-hook 'haskell-mode-hook 'haskell-hook)
  (add-hook 'haskell-mode-hook 'turn-on-haskell-doc-mode)
  (add-hook 'haskell-mode-hook 'interactive-haskell-mode)
  (add-hook 'haskell-cabal-mode-hook 'haskell-cabal-hook)

  ;; Haskell main editing mode key bindings.
  (defun haskell-hook ()
    ;; Use indentation.
    ;;(haskell-indent-mode)

    ;; Use electric-quote-mode
    (electric-quote-local-mode)

    ;; Load the current file (and make a session if not already made).
    (define-key haskell-mode-map [?\C-c ?\C-l] 'haskell-process-load-file)
    (define-key haskell-mode-map [f5] 'haskell-process-load-file)

    ;; Switch to the REPL.
    (define-key haskell-mode-map [?\C-c ?\C-z] 'haskell-interactive-switch)

    ;; “Bring” the REPL, hiding all other windows apart from the source
    ;; and the REPL.
    (define-key haskell-mode-map (kbd "C-`") 'haskell-interactive-bring)

    ;; Build the Cabal project.
    (define-key haskell-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build)

    ;; Interactively choose the Cabal command to run.
    (define-key haskell-mode-map (kbd "C-c c") 'haskell-process-cabal)

    ;; Get the type and info of the symbol at point, print it in the
    ;; message buffer.
    (define-key haskell-mode-map (kbd "C-c C-t") 'haskell-process-do-type)
    (define-key haskell-mode-map (kbd "C-c C-i") 'haskell-process-do-info)

    ;; Contextually do clever things on the space key, in particular:
    ;; 1. Complete imports, letting you choose the module name.
    ;; 2. Show the type of the symbol after the space.
    ;; NOTE: the haskell-mode-contextual-space function has been depreciated
    ;;(define-key haskell-mode-map (kbd "SPC") 'haskell-mode-contextual-space)

    ;; Jump to the imports. Keep tapping to jump between import
    ;; groups. C-u f8 to jump back again.
    (define-key haskell-mode-map [f8] 'haskell-navigate-imports)

    ;; Jump to the definition of the current symbol.
    (define-key haskell-mode-map (kbd "M-.") 'haskell-mode-tag-find)

    ;; Indent the below lines on columns after the current column.
    (define-key haskell-mode-map (kbd "C-<right>")
      (lambda ()
        (interactive)
        (haskell-move-nested 1)))

    ;; Same as above but backwards.
    (define-key haskell-mode-map (kbd "C-<left>")
      (lambda ()
        (interactive)
        (haskell-move-nested -1)))

    ;; Interactive keybindings
    (define-key haskell-mode-map (kbd "C-c C-l") 'haskell-process-load-or-reload)
    (define-key haskell-mode-map (kbd "C-`") 'haskell-interactive-bring)
    (define-key haskell-mode-map (kbd "C-c C-t") 'haskell-process-do-type)
    (define-key haskell-mode-map (kbd "C-c C-i") 'haskell-process-do-info)
    (define-key haskell-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build)
    (define-key haskell-mode-map (kbd "C-c C-k") 'haskell-interactive-mode-clear)
    (define-key haskell-mode-map (kbd "C-c c") 'haskell-process-cabal)

    ;; Interactive keybindings that are useful in cabal-mode
    (define-key haskell-cabal-mode-map (kbd "C-`") 'haskell-interactive-bring)
    (define-key haskell-cabal-mode-map (kbd "C-c C-k") 'haskell-interactive-mode-clear)
    (define-key haskell-cabal-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build)
    (define-key haskell-cabal-mode-map (kbd "C-c c") 'haskell-process-cabal))

  ;; Useful to have these keybindings for .cabal files, too.
  (defun haskell-cabal-hook ()
    (define-key haskell-cabal-mode-map (kbd "C-c C-c") 'haskell-process-cabal-build)
    (define-key haskell-cabal-mode-map (kbd "C-c c") 'haskell-process-cabal)
    (define-key haskell-cabal-mode-map (kbd "C-`") 'haskell-interactive-bring)
    (define-key haskell-cabal-mode-map [?\C-c ?\C-z] 'haskell-interactive-switch))

  ;; Set inferior haskell default executable
  (setq haskell-program-name "/usr/bin/ghci")

  ;; TODO: this is rarely if ever used
  (use-package hamlet-mode)

PlantUML

  (use-package plantuml-mode
    :config (setq plantuml-default-exec-mode 'executable
                  plantuml-output-type "png"))

Graphviz Dot

  (use-package graphviz-dot-mode)

TODO Hex   deprecate

This is rarely if ever used. Consider removal.

  (use-package "intel-hex-mode")

TODO Web   needs_review

  ;; Setup web browsing
  (setq browse-url-browser-function 'browse-url-generic
        browse-url-generic-program "xdg-open")

  (defun browse-url-before-point ()
    "Find the first url before point and open it in a browser using browse-url"
    (interactive)
    (save-excursion
      (search-backward-regexp "\(file\|ftp\\|http\\|https\)://.*\.")
      (browse-url-at-point)))

  (global-set-key (kbd "C-c g") 'browse-url-at-point)
  (global-set-key (kbd "C-c G") 'browse-url-before-point)

  (use-package php-mode
    :defer t
    :mode "/*.\.php[345]?$")

  ;; Setup zencoding-mode (now emmet-mode)
  (use-package emmet-mode
    :defer t
    :bind (:map emmet-mode-keymap
                ;; Disable C-j keybinding set by zencoding-mode and replace it with 'C-c j'
                ("C-j" . nil)
                ("C-c j" . emmet-expand-line))
    :hook ((sgml-mode . emmet-mode) ;; Auto-start on any markup modes
           (css-mode .  emmet-mode) ;; Enable Emmet's css abbreviation.
           (emmet-mode . (lambda () (setq emmet-indentation 2))) ;; Indent 2 spaces.
           ))

  (use-package graphql-mode
    :defer t
    :mode "\\.gql\\'"
    :hook ((graphql-mode)))

  ;; Setup mmm-mode for multiple mode regions in the same buffer
  (use-package mmm-mode
    :defer t)
  ;;(setq mmm-global-mode 'maybe)

  (use-package restclient
    :defer t)

  (use-package elpher
    :defer t)

TODO Python   needs_review

Both of these packages are not yet available on guix. Further more, the use of ein needs to be further investigated.

  (use-package ipython
    :defer t)

  (use-package ein
    :defer t)

TODO Recutils

  (use-package rec-mode)

Latex

  (setq-default TeX-master nil)

  ;; Use auto-complete in latex buffers
  (eval-after-load "auto-complete"
    '(add-to-list 'ac-modes 'latex-mode))

  (use-package auctex
    :functions TeX-global-PDF-mode
    :hook ((LaTeX-mode . visual-line-mode)
           (LaTeX-mode . LaTeX-math-mode)
           (LaTeX-mode . turn-on-reftex))
    :config (progn
              (setq TeX-auto-save t
                    TeX-parse-self t
                    reftex-plug-into-AUCTeX t)
              (TeX-global-PDF-mode t)))

Tramp

  ;; Set default tramp method to ssh (for security purposes)
  (setq tramp-default-method "ssh")

  ;; Set tramp terminal type to tramp instead of the default dumb
  ;; This avoids forcing a simple PS1 on remote systems that are using zsh
  ;; via a dumb term. However this requires all remotes to disable fancy PS1's
  ;; by checking if the terminal type is "tramp"
  (setq tramp-terminal-type "tramp")

  ;; This fixes sudo tramp access
  (setq tramp-remote-path '(tramp-default-remote-path "/bin" "/usr/bin" "/sbin" "/usr/sbin" "/usr/local/bin" "/usr/local/sbin" "/local/bin" "/local/freeware/bin" "/local/gnu/bin" "/usr/freeware/bin" "/usr/pkg/bin" "/usr/contrib/bin" "/opt/bin" "/opt/sbin" "/opt/local/bin" "/run/current-system/profile/bin" "/run/setuid-programs"))

TODO Extensions   needs_review

This package is unavailable for guix. Further the package is defered and likely will never get loaded.

  (use-package vagrant-tramp
    :defer t)

Guix

  (use-package guix
    :bind ("C-c x g" . guix))

TODO Archlinux - Mode for PKGBUILD files   needs_review

This is not yet packaged for guix.

  (use-package pkgbuild-mode
    :defer t)

TODO Docker   needs_review

  (use-package dockerfile-mode)
  (use-package docker
    :bind ("C-c D" . docker))
  (use-package docker-compose-mode
    :disabled ;; TODO: auto-completion does not seem to work using this package
    :defer t)

eshell

  ;; TODO: make a function to toggle the eshell; given a the universal argument the following can occur:
  ;;       - if numerical then opens the nth scratch buffer "*eshell*<n>"
  ;;       - if no args then open a new eshell
  ;;
  ;; (defun toggle-eshell (arg)
  ;;   (interactive "P")
  ;;   (cond (((equal arg '(4)) ))))

  (defun eshell/catbuf (buffer-name)
    "Given a buffer-name returns the contents of said buffer"
    (interactive "bBuffer: ")
    (save-excursion
      (let ((code-buf (get-buffer buffer-name)))
        (if (null code-buf) (concat "The buffer given \"" buffer-name "\" does not exist")
          (set-buffer code-buf)
          (buffer-string)))))

  (defun eshell/find-file-ext (fp)
    "Finds a single file or a list of files matching a regxp and returns a list of their respective buffers"
    (interactive)
    (if (listp fp) (mapcar #'find-file fp)
      (list (find-file fp))))

  (defun eshell/ff (fp &rest other-fps)
    "A FP is either a file path (relative or absolute) or a regexp which eshell converts to a
  list of stings (file paths) which match the regexp (likely using file-expand-widcards).
  eshell/ff takes one or more file paths and opens them in the current buffer returning a list
  consisting of lists of buffers opened by each respective FP argument."
    (interactive)
    (mapcar #'eshell/find-file-ext (cons fp other-fps)))

  (defun eshell/clear ()
    "04Dec2001 - sailor, to clear the eshell buffer."
    (interactive)
    (let ((inhibit-read-only t))
      (erase-buffer)))

  (use-package eshell
    :bind (:map ctl-x-4-map
                ("e" . eshell))
    :config (setq
             eshell-scroll-to-bottom-on-input t
             eshell-visual-commands '("vi" "screen" "top" "less" ;; Commands to run without dumb-term
                                      "more" "lynx" "ncftp" "vim" "htop"
                                      "ncmpcpp" "irssi" "mc" "alsamixer")))

  ;; TODO: this doesn't belong in this file and should be relocated
  ;; Force ediff sessions to run in the same frame
  (setq ediff-window-setup-function 'ediff-setup-windows-plain)