Sam's Emacs

Table of Contents

Intro

This config is mainly for writing a mixture of Org and \(\LaTeX\) documents. Version controlling is done through Git.

This is organized as an Org document for several reasons: One is for readability, another for easy publishing as a webpage, but the main one is to allow other users to build their own configs from. How? Filtering by TODO keywords and tags. Want all my keybindings, but none of my LaTeX stuff? You can org-sparse-tree or C-c / to apply filters and select what you want. At the end of the day, tangle the filtered results.

General

We make a run key that gives a menu to different programs you might want a glance at.

(defun add-menu-item (key command)
       (global-set-key (kbd (concat "C-; " key)) command))
(defun find-init-file () (interactive) (find-file user-init-file))
(add-menu-item "i" 'find-init-file)
(add-menu-item "p" 'proced)
(setq external-pdf-viewer "zathura")

Let's do a different custom-settings file:

(setq custom-file "~/.emacs.d/custom.el")
(load-file custom-file)

Neofetch Start Screen   bling

We make a Neofetch start screen with lots of data for a startup screen (and for that sweet karma).

(defun neofetch () (interactive) (async-shell-command "neofetch" "*Neofetch*"))
(setq inhibit-startup-screen t)
(neofetch)

Document Preparation

IN PROGRESS Pure Latex   TeX

We add a function for package load-time settings:

(defun latex-init-settings ()
  (setq TeX-parse-self t)
  (add-to-list 'TeX-command-list
               '("latexmk lualatex compile" "latexmk -lualatex %s" TeX-run-command nil t))
  (add-to-list 'TeX-command-list
               '("latexmk lualatex preview" "latexmk -lualatex -pvc -view=none %s" TeX-run-command nil t))
  (add-to-list 'TeX-command-list
               '("latexindent" "latexindent -w %s" TeX-run-command nil t))
  (setq TeX-view-program-selection '((output-pdf "Okular")))
  (setq cdlatex-math-modify-alist
        '((98 "\\mathbb" nil t nil nil)
          (102 "\\mathfrak" nil t nil nil)))
  (setq cdlatex-math-symbol-alist
        '((120 "\\chi" "\\otimes")
          (62 "\\geq" "\\geqsim")
          (60 "\\leq" "\\lesssim"))))

Let's set a mode hook function:

(defun latex-hook ()
  (cdlatex-mode 1)
  (reftex-mode 1)
  (prettify-symbols-mode 1))
(use-package latex
  :defer t
  :ensure auctex
  :config (latex-init-settings))

(add-hook 'LaTeX-mode-hook 'latex-hook)

CANCELLED make minor mode for wrapping text in LaTeX but not math mode text

Decided I didn't mind… probably not worth the effort.

CANCELLED Jump to items and sections

Let's try to write a function that jumps to the previous thing…

(defun latex-last-item () (interactive)
       (isearch-backward "\\item"))
(defun latex-last-section () (interactive)
       (isearch-backward "\\.*section"))
(defun latex-next-item () (interactive)
       (isearch "\\item"))
(defun latex-next-section () (interactive)
       (isearch "\\.*section"))

Org Settings   Org

Several things go on here: we use some minor modes, we set scaling on Latex preview, and then we add CDLaTeX math symbols and commands.

(defun latex-in-org-settings ()
  (progn
       (require 'ox-bibtex)
       (setq org-highlight-latex-and-related '(latex script entities))
       (plist-put org-format-latex-options :scale 2.0)
       ))

A small helper function

(defun org-indent-paragraph () (interactive)
       (org-backward-paragraph)
       (push-mark)
       (org-forward-paragraph)
       (org-indent-region
        (mark) (point)))

(defun org-insert-block (type header-args) (interactive "sBlock Type: \nsHeader Arguments: ")
         (insert "\n#+BEGIN_" type " " header-args "\n\n#+END_" type)
         (previous-line))

Let's add some keywords, keybindings, and such:

(defun org-init-settings ()
  (latex-in-org-settings)
  (setq org-agenda-start-on-weekday 0)
  (require 'org-ref-ivy)
  (setq org-ref-insert-link-function 'org-ref-insert-link-hydra/body
        org-ref-insert-cite-function 'org-ref-cite-insert-ivy
        org-ref-insert-label-function 'org-ref-insert-label-link
        org-ref-insert-ref-function 'org-ref-insert-ref-link)
  (require 'org-ref)
  (require 'org-noter)
  (setq org-todo-keywords
        '((sequence "IN" "NEXT ACTIONS" "WAITING FOR" "EVENTUALLY" "|" "DONE" "CANCELLED"))))

Let's write an Org-mode-hook function:

(defun org-hook () ()
       (visual-line-mode)
       (local-set-key (kbd "C-c ]") 'org-ref-insert-link-hydra)
       (local-set-key (kbd "C-c b") 'org-insert-block)
       (org-cdlatex-mode))
(use-package org
  :defer t
  :config (org-init-settings))
(add-menu-item "a" 'org-agenda)
(add-hook 'org-mode-hook 'org-hook)

Bibliography Management   bibtex

(setq bibtex-completion-pdf-field "file"
      bibtex-completion-bibliography "~/zoterolib.bib"
      bibtex-completion-pdf-open-function
      (lambda (fpath) (call-process external-pdf-viewer nil 0 nil fpath))
      bibtex-completion-library-path '("~/pdfs"))
(add-menu-item "b" 'ivy-bibtex)

Keybindings and Emacs Management

Editing Settings   keys

First we'll add some extra commands of my own…

(defun machine-uptime () (interactive) (shell-command "uptime"))
(defun pacman-update () (interactive) (async-shell-command "sudo pacman -Syu"))
(defun get-weather () (interactive)
       (async-shell-command "curl -s 'https://wttr.in/chicago?0p'" "*wttr.in*" nil))
(defun smart-kill-word () (interactive)
       (forward-word)
       (kill-word -1))

(defun my-editing-keybindings () (interactive)
       (setq sentence-end-double-space nil)
       (global-set-key (kbd "C-x s") 'swiper)
       (global-set-key (kbd "C-k") 'crux-smart-kill-line)
       (global-set-key (kbd "M-d") 'smart-kill-word)
       (unbind-key "C-z")
       (global-set-key (kbd "C-z p") 'ping)
       (global-set-key (kbd "C-z t") 'machine-uptime)
       (global-set-key (kbd "C-z b") 'battery)
       (global-set-key (kbd "C-z u") 'pacman-update)
       (global-set-key (kbd "C-z w") 'get-weather)
          )
(use-package magit)
(use-package projectile
:config (progn
          (projectile-mode 1)
          (define-key projectile-mode-map (kbd "C-x p") 'projectile-command-map)))
(use-package dired-x
:config (progn
          (setq dired-listing-switches "-ahl")
          (setq dired-guess-shell-alist-user
                `(("\\.bib$" "~/.local/bin/bibly")
                  ("\\.pdf$" ,(concat external-pdf-viewer " *"))))))
(defun counsel-keybindings () (interactive)
       (global-set-key (kbd "M-o") 'ace-window)
       (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
       (global-set-key (kbd "M-s m") 'counsel-imenu)
       (global-set-key (kbd "M-s b") 'counsel-ibuffer)
       (global-set-key (kbd "C-x b") 'counsel-switch-buffer)
       (global-set-key (kbd "C-x 4 b") 'counsel-switch-buffer-other-window)
       (global-set-key (kbd "M-z") 'counsel-linux-app))
(use-package counsel
:config (progn
          (counsel-mode 1)
          (counsel-projectile-mode 1)
          (ivy-mode 1)
          (counsel-keybindings)))
(use-package crux
:config (my-editing-keybindings))
(repeat-mode)

Visual/Window settings

Try EXWM   bling wm

Decided not to. A fully-afeatured window manager is probably better.

It's back with a vengeance.

Here we goooo….

(defun lock-screen-with-slock () (interactive) (call-process "slock"))

(defun exwm-settings ()
    (setq exwm-workspace-number 4)
    (add-hook 'exwm-update-class-hook
          (lambda ()
            (unless (or (string-prefix-p "sun-awt-X11-" exwm-instance-name)
                        (string= "gimp" exwm-instance-name))
              (exwm-workspace-rename-buffer exwm-class-name))))
    (add-hook 'exwm-update-title-hook
              (lambda ()
            (when (or (not exwm-instance-name)
                      (string-prefix-p "sun-awt-X11-" exwm-instance-name)
                      (string= "gimp" exwm-instance-name))
              (exwm-workspace-rename-buffer exwm-title))))
    (setq exwm-input-global-keys
          `((,(kbd "s-SPC") . counsel-linux-app)
            (,(kbd "s-r") . exwm-reset)
            (,(kbd "s-M-o") . exwm-workspace-switch-to-buffer)
            (,(kbd "s-o") . exwm-workspace-switch)
            (,(kbd "s-g") . lock-screen-with-slock)
            )
          )
    (exwm-enable)
    )
  (use-package exwm-randr
    :config (progn
              (setq exwm-randr-workspace-output-plist '(0 "eDP-1" 1 "eDP-1" 2 "HDMI-1" 3 "HDMI-1"))
              (add-hook 'ewm-randr-screen-change-hook
                        (lambda ()
                          (start-process-shell-command
                           "xrandr" nil "xrandr --output HDMI-1 --right-of eDP-1 --auto")))
              (exwm-randr-enable)))
  (use-package exwm
    :config (exwm-settings))

<2022-05-21 Sat> Well, it's been fun folks. I'm done with full-time EXWM, and back to Awesomewm. I'll keep the above snippet for any interested parties.

Dynamic Window Layouts   wm

Here we look to implement two dynamic window layouts, inspired by tiling window managers. This replaces the native window splitting function.

We first do an XMonad Tall layout:

(defun xmonad-tree-navigator (tree)
  (if (windowp tree) tree
    (if (listp tree) (xmonad-tree-navigator (car (last tree)))
        (error "Encountered a non-list or non window argument"))))

(defun xmonad-tall (curr-win)
       (if (one-window-p) (split-window-right)
         (progn
           (select-window (xmonad-tree-navigator (car (window-tree))))
           (split-window-below))))

And we do a BSPWM one (or a vertical split version):

(defun bsp-tree-navigator (tree)
  (if (windowp tree) tree
    (if (listp tree) (bsp-tree-navigator (car (last tree)))
      (error "Encountered a non-list or non-window argument"))))

(defun bspwm (curr-win)
       (let ((to-window (bsp-tree-navigator (car (window-tree)))))
         (progn
           (select-window to-window)
           (if (window-combined-p to-window t)
               (split-window-below)
             (split-window-right)))))

(defun bspwm-vert (curr-win)
       (let ((to-window (bsp-tree-navigator (car (window-tree)))))
         (progn
           (select-window to-window)
           (if (window-combined-p to-window)
               (split-window-right)
             (split-window-below)))))

And now we add a function to switch between layouts:

(setq layout-list '(split-window-sensibly xmonad-tall bspwm bspwm-vert))
(defun select-window-layout (symbol) (interactive "Slayout: ")
       (if (member symbol layout-list) (setq split-window-preferred-function symbol)
         (error "Not a layout in layout-list")))
(defun current-window-layout () (interactive)
       (message split-window-preferred-function))

DONE Minibuffer Frame

Here we create a pop-up minibuffer window to use where ever. The model is this: you should run emacsclient --eval '(runner)' and the minibuffer menu should appear.

(defun runner () (interactive)
       (setq default-minibuffer-frame (make-frame
                                       '((minibuffer . only) (title . "erunner") (left . 0.25) (top . 0.25) (height . 0.5) (width . 0.5))))
       (setq minibuffer-auto-raise t))

Currently needs:

  • [ ] raise frame if already created
  • [X] put windows in main frame (just use C-x C-f or regular keybindings; just not the usual keybinds)

<2022-04-21 Thu> With the addition of Try EXWM, I have no big use for this.

Avy   keys

Let's get warmed up with some simple keybindings for avy:

(defun my-avy-keybindings () (interactive)
(global-set-key (kbd "M-g g") 'avy-goto-char-2)
(global-set-key (kbd "M-g c") 'avy-goto-char)
(global-set-key (kbd "M-g M-g") 'avy-goto-line)
(global-set-key (kbd "M-g f") 'avy-goto-char-in-line)
(global-set-key (kbd "M-g e") 'avy-goto-end-of-line))
(use-package avy
:config (my-avy-keybindings))

Repeat Keymaps   keys

Repeat keymaps that are useful to me:

(defvar make-window-repeat-map
  (let ((map (make-sparse-keymap)))
        (define-key map "2" 'split-window-below)
        (define-key map "3" 'split-window-right)
        (define-key map "0" 'delete-window)
        (define-key map "=" 'balance-windows)
        (define-key map "b" 'switch-to-buffer) map)
    "making, breaking, and switching window. for use in repeat-mode")

(put 'split-window-below 'repeat-map 'make-window-repeat-map)
(put 'split-window-right 'repeat-map 'make-window-repeat-map)
(put 'delete-window 'repeat-map 'make-window-repeat-map)
(put 'balance-windows 'repeat-map 'make-window-repeat-map)
(put 'switch-to-buffer 'repeat-map 'make-window-repeat-map)

Moving around the buffer…

(defvar move-map
  (let ((map (make-sparse-keymap)))
    (define-key map "n" 'next-line)
    (define-key map "p" 'previous-line)
    (define-key map "a" 'beginning-of-line-text)
    (define-key map "e" 'end-of-line)
    (define-key map "f" 'forward-word)
    (define-key map "g" 'keyboard-quit)
    (define-key map "b" 'backward-word)
    (define-key map (kbd "SPC") 'set-mark-command)
    (define-key map "w" 'kill-region)
    (define-key map "y" 'yank) map)
  "movement and editing commands. tiny vim normal mode.")

(put 'next-line 'repeat-map 'move-map)
(put 'previous-line 'repeat-map 'move-map)
(put 'beginning-of-line-text 'repeat-map 'move-map)
(put 'end-of-line 'repeat-map 'move-map)
(put 'set-mark-command 'repeat-map 'move-map)
(put 'kill-region 'repeat-map 'move-map)
(put 'yank 'repeat-map 'move-map)
(put 'forward-word 'repeat-map 'move-map)
(put 'backward-word 'repeat-map 'move-map)

PROGRESS Mini Embark-Become

Here's a miniature version of Embark's embark-become:

(defvar cumulative-object-ring nil "An object to be repeatedly acted on by stick-cmd")

(defun cumulative-push-object (lisp-object) (interactive "XLisp Object:") (push lisp-object cumulative-object-ring))

(defvar cumulative-action-ring nil  "A list of symbols to act on cumulative-object")

(defun cumulative-push-action (command) (interactive "CCumulative Action:") (push command cumulative-actions))

(defun cumulative-exec ()
  (interactive)
  (dolist (cmd cumulative-actions)
    (dolist (cumulative-object cumulative-object-ring)
    (eval `(,cmd ,cumulative-object))))
  (setq cumulative-object nil)
  (setq cumulative-actions nil))

Here are some quick-use functions:

(defun cumulative-push-buffer (buf) (interactive "bCumulative Buffer:") (push buf cumulative-object-ring))
(defun cumulative-push-file (fil) (interactive "FCumulative File:") (push buf cumulative-object-ring))
(defun cumulative-push-region (beg end) (interactive "r") (push (list beg end) cumulative-object-ring))

(defun cumulative-push-save-and-kill () (interactive) (cumulative-push-action 'save-buffer) (cumulative-push-action 'kill-buffer))
(defun cumulative-push-find-other-window () (interactive) (cumulative-push-action 'find-file-other-window))
(defun cumulative-push-kill () (interactive) (cumulative-push-action 'kill-region))

(defun cumulative-clear-actions () (interactive) (setq cumulative-action-ring nil))
(defun cumulative-clear-objects () (interactive) (setq cumulative-object-ring nil))

And we make a minor mode with key-bindings:

(defvar cumulative-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "M-c o") 'cumulative-push-object)
    (define-key map (kbd "M-c a") 'cumulative-push-action)
    (define-key map (kbd "M-c x") 'cumulative-exec)
    (define-key map (kbd "M-c b") 'cumulative-push-buffer)
    (define-key map (kbd "M-c f") 'cumulative-push-file)
    (define-key map (kbd "M-c r") 'cumulative-push-region)
    (define-key map (kbd "M-c s") 'cumulative-push-save-and-kill)
    (define-key map (kbd "M-c 4 f") 'cumulative-push-find-other-window)
    (define-key map (kbd "M-c k") 'cumulative-push-kill) map)
  "keymap for some common cumulative commands")

(define-minor-mode cumulative-mode
  "collect functions and targets for cumulative actions that can be executed."
  :global t
  :init-value nil
  :lighter " cum"
  :keymap cumulative-map)

Random Theme

Switch to a random dark theme, because why not?

(setq my-dark-themes [dracula
                      modus-vivendi
                      alect-black
                      alect-black-alt
                      alect-dark
                      alect-dark-alt
                      gruvbox-dark-hard
                      gruvbox-dark-medium
                      gruvbox-dark-medium])

(defun load-random-theme (theme-list) (interactive "XTheme List: ")
       (load-theme (seq-random-elt theme-list) t))

(load-random-theme my-dark-themes)

Work Mode

This mode designates predefined directories as 'work directories:' if you have a file open in a directory, you won't be able to open any other files in other directories without closing all your work directories. This is to incentivize staying on-task.

(setq my-work-dirs  '("~/repos/AppliedAnalysis/"  "~/school/"))

(defun leave-work () (let ((leaving (y-or-n-p "Leave all your work behind?")))
                       (if leaving (mapc (lambda (buf)
                                           (if (member t (mapcar (lambda (dir) (if (buffer-file-name buf) (locate-dominating-file (buffer-file-name buf) dir))) my-work-dirs))
                                               (kill-buffer buf)))
                                         (buffer-list)))
                         leaving))

External Integrations

CANCELLED Nyxt Integration   web

Here we provide a couple of helper functions for interacting with Nyxt. I'll explain in a little more detail here.

We pass Lisp code to the running Nyxt process via shell commands. This requires that REMOTE-EXECUTION-P must not be nil (in Nyxt). Once that is done, we can use the following functions to pass arbitrary Lisp code:

(defun format-for-nyxt-eval (list)  (shell-quote-argument (format "%S" list))) ;; prepare lisp code to be passed to the shell
(defun eval-in-nyxt (s-exps)  (call-process "nyxt" nil nil nil (concat "--remote --eval " (format-for-nyxt-eval s-exps))))

Now we can only do so by elisp code, to prevent mistakes. Now we can use it!

(defun set-in-nyxt (variable elisp) (eval-in-nyxt `(setq ,variable (list ,@elisp))))
(defun eval-region-in-nyxt (start end) (interactive "r") (eval-in-nyxt (read (buffer-substring start end))))

And if we happen to have the following in our init file for Nyxt (usually in $HOME/.config/nyxt/init.lisp)…

(ql:quickload :cl-strings)

(defun eval-in-emacs (&rest s-exps)
  "Evaluate S-EXPS with emacsclient."
  (let ((s-exps-string (cl-strings:replace-all
                        (write-to-string
                         `(progn ,@s-exps) :case :downcase)
                        ;; Discard the package prefix.
                        "nyxt::" "")))
    (format *error-output* "Sending to Emacs:~%~a~%" s-exps-string)
    (uiop:run-program
     (list "emacsclient" "--eval" s-exps-string))))

(Taken directly from here) then we can use the following function in Emacs:

(defun get-nyxt-buffers () (eval-in-nyxt
                            '(eval-in-emacs
                              `(setq nyxt-buffer-list
                                     (list ,@(mapcar #'title (buffer-list)))))))
(defun search-in-nyxt (search-term) (interactive "sSeach in Nyxt:") (eval-in-nyxt
                                                                     `(buffer-load (make-instance 'new-url-query
                                                                                                  :query ,search-term
                                                                                                  :engine (first (last (search-engines (current-buffer))))))))

CANCELLED Stumpwm   wm

Let's be able to give commands to Stumpwm. Instead of doing this through slime or Sly, we can do it through stumpish, a command shell for stumpwm. It's in the stumpwm contrib module. We save the path to it and make a function to run Lisp code in Stumpwm:

(setq stumpish-path "~/.stumpwm.d/modules/util/stumpish/stumpish")
(defun eval-in-stumpwm (s-exps) (call-process stumpish-path nil nil nil (format "eval %S" s-exps)))
(defun eval-in-stumpwm-and-return (s-exps) (read (shell-command-to-string (concat
                                                                           stumpish-path " eval "
                                                                           (shell-quote-argument (format "%S" s-exps))))))
(defun eval-region-in-stumpwm (start end) (interactive "r") (eval-in-stumpwm (read (buffer-substring start end))))

We would also like to be able to pass ELisp to Emacs from Stumpwm:

(defun stumpwm-eval-in-emacs (s-exps)
  (run-shell-command (concatenate 'string "emacsclient --eval '" (write-to-string s-exps :case :downcase) "'")))

And now we're ready to write some helpful interaction commands!

CANCELLED Write major mode for editing a buffer for window manager interaction (dired/org-style)

First we collect the window tree from stumpwm:

(defun get-stumpwm-desktop () (eval-in-stumpwm-and-return
                               '(progn
                                  (dump-desktop-to-file "~/.stumpwm.d/.desktop")
                                  ())))



(defun make-desktop-to-buffer ()
  (let (( window-tree (get-stumpwm-desktop)))
    '()
    )) ;;finish

I am running into some problems with this… Stumpwm doesn't expose enough useful functions to get a full desktop tree. Accessor functions are hidden, slot values as well… Not sure what the solution is. (Maybe try SLIME ?)

<2021-11-15 Mon> Checking in on this after a while… I'm not using stumpwm so gave up.

Elfeed   web

Let's set up Elfeed!

(defun my-elfeed-settings () (interactive)
(global-set-key (kbd "C-; e") 'elfeed)
(setq elfeed-feeds
      '(("https://api.quantamagazine.org/feed/" science)
        ("https://acoup.blog/feed" history)
        ("https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" finance)
        ("https://cvgmt.sns.it/papers/rss.xml" preprint)
        ("https://planet.emacslife.com/atom.xml" emacs)
        ("https://kbd.news/rss2.php" keyboard)
        ("https://thealexandrian.net/feed" dnd)
        ("https://sachachua.com/blog/feed/" emacs)
        )))
(use-package elfeed
:config (my-elfeed-settings))
(use-package pdf-tools
  :config (pdf-tools-install))

Wikipedia Search   web

Let's search Wikipedia…

(defun wiki-search (search-term) (interactive "sSearch Wikipedia: ") (browse-url (concat "https://en.wikipedia.org/w/index.php?title=Special%3ASearch&search=" search-term)))

Mu4e Settings   mail

First, update mail settings:

(defun init-mail-settings () ()
  (setq
   mu4e-get-mail-command "offlineimap -q -o"
   mu4e-update-interval 30000))
(use-package mu4e
  :load-path  "/usr/share/emacs/site-lisp/mu4e"
  :init (init-mail-settings))
(add-menu-item "m" 'mu4e)
(add-hook 'mu4e-compose-hook 'turn-off-autofill)

Resources

Beginner and General Resources

Here's a brief list of resources for reading on Emacs…

Other's Configs

Here are some other well-developed configs I've found:

Author: Samuel Wallace

Created: 2022-06-17 Fri 17:05

Validate