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))
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 useC-x C-f
or regular keybindings; just not the usual keybinds)
Try EXWM, I have no big use for this.
With the addition ofAvy 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 ?)
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…
- Built-in Emacs Features
- LaTeX Input
- The Way of Emacs
- Awesome Emacs GitHub
- Mastering Emacs
- Sacha Chua's Beginner Resources
- The keybindings (in Emacs, of course)
C-h r
andC-h i
Other's Configs
Here are some other well-developed configs I've found:
- Sacha Chua's config
- Streamed Config A config built entirely on stream, so you can go and watch a video explanation of the config building process
- Megumacs A config I found randomly on GitHub
- Prot's config A config by an Emacs package writer