Emacs Configuration

Posted: 2021 / 11 / 27

This is my literate init.el file. There are a lot of highly opinionated choices here! My config is in a constant state of disrepair. If you’re reading this on my website, then this is probably quite close to my current config since this file is updated by the website’s build script.

1 Internals

1.1 Performance

Increase gc threshold. This is at the top of the file to ensure that it benfits startup time of stuff later on.

(setq read-process-output-max (* 1024 1024)) ;; 1mb (setq gc-cons-threshold 100000000)

1.2 Packages & Straight bootstrapping

I use straight.el for my package management. I find it to be much more flexible than plain use-package or package.el. Also, it has much better portability and version stability. Finally, I find its recipe-writing features very nice, with the killer feature being clean support for forks, since I fork emacs packages on a somewhat regular basis.

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

straight-use-package is a bit of a keyful to type, especially interactively.

(defalias 'sup 'straight-use-package)

1.3 JIT

I use a custom compiled version of emacs with native compilation enabled. The first line enabled deferred comp so that elisp is compiled async when its first called. The second line is so that I don’t see annoying popups every time.

(setq comp-deferred-compilation t) (setq warning-suppress-log-types '((comp)))

If the variable doesn’t exist, setq won’t error (it’ll make and intern the new variable) and it’ll do nothing.

1.4 Macros

Some macros that are used in this file:

1.4.1 Better hooks

Hook to add multiple functions and forms to a hook. This accepts any number of arguments where the first is the (quoted) hook itself, and each element is either an unquoted symbol, in which its treated as a function, or a form in which case it is inserted directly.

(defmacro add-fs-to-hook (hook &rest funcs) "Add functions to hook. A function is either an unquoted token, or a form. If it's a token, then its treated as a function and enabled. Otherwise, the form is run." `(add-hook ,hook (fn ,@(mapcar (lambda (el) (if (listp el) el (list el 1))) funcs))))

Sometimes its helpful to think about adding a single function to many hooks. Everything should be quoted here.

(defmacro add-to-hooks (f &rest hooks) "Add a single function to many quoted hooks" `(progn ,@(mapcar (lambda (hook) `(add-hook ,hook ,f)) hooks)))

1.4.2 Nullary lambda

(defmacro fn (&rest forms) (declare (indent 0)) `(lambda () ,@forms))

1.4.3 Furcula

Implementation of the furcula (branching arrow) as described in the swiss-arrows macroset for clojure. I am a huge fan of this idea, and I included an implementation of it in my janet fork, matsurika. The basic idea is that the first entry will get inserted as either the first or last item (argument, usually) in every subsequent lisp form, or function. The result is then a list of the execution of all of these forms or functions. For instance, (-< 5 (+ 1) (* 2) (- 1)) results in '(6 10 4). Things can get really exciting when you start combining -< and ->>!

This particular implementation comes from Adam Porter’s (alphapapa) code from his 2018 proposal to dash.el. Kind of bummed this never got merged.

(defmacro -< (expr &rest forms) (declare (indent defun)) (let ((var (gensym))) `(let ((,var ,expr)) (list ,@(--map (pcase it ((pred symbolp) (list it var)) ((pred listp) (-snoc it var))) forms))))) (defmacro -<< (expr &rest forms) (declare (indent defun)) (let ((var (gensym))) `(let ((,var ,expr)) (list ,@(--map (pcase it ((pred symbolp) (list it var)) (`(,first . ,rest) `(,first ,var ,@rest))) forms)))))

1.5 Libraries

Classic libraries I use regularly.

good string manipulation
functional programming essentials like threading (piping, not multithreading) and recursive style list manipulation

(sup 's) (sup 'dash)

1.6 Readline muscle memory compatibility

Too used to the command line, make C-h backspace and C-x h help.

(global-set-key [?\C-h] 'delete-backward-char) (global-set-key [?\C-x ?h] 'help-command)

Not exactly readliney, but close enough for me. C-z (and C-u) in readline like systems typically kills backward only.

(global-set-key [?\C-z] #'kill-whole-line)

2 Visual configuration

2.1 Disable bell

I don’t know who thought it was a good idea to include this.

(setq ring-bell-function 'ignore)

2.2 Disable UI bloat

Turn off the menu-bar, tool-bar, an scroll-bar. I hate bars!

The tool bar and the menu bar can be turned off by calling their functions with the -1 argument of course, but doing it this way shaves off actually a significant amount of time from startup. I don’t (anymore) run emacs in a daemon, so startup time is somewhat valuable to me.

(push '(tool-bar-lines . 0) default-frame-alist) (push '(menu-bar-lines . 0) default-frame-alist) (scroll-bar-mode -1)

2.3 Better parenthesis location

I can’t count parentheses. I use an advice override to change how the parenthesis locating functionality works. This is because I use a block cursor with meow, which makes cursor position slightly deceptive.

Basically, the block cursor by default highlights the parenthesis when your cursor is immediately AFTER the parenthesis in question, because the point is always between two characters in emacs (the point is really right after the parenthesis as well). So, if you have nested parentheses, as we often do, it’s strange to see the “wrong parenthesis” highlighted.

This advice first checks before the point and only then after the point for a parenthesis. I think this behavior is very intuitive. The defined function overrides the internal function used to find parentheses.

(column-number-mode) (show-paren-mode) (defun show-paren--locate-near-paren-ad () "Locate an unescaped paren \"near\" point to show. If one is found, return the cons (DIR . OUTSIDE), where DIR is 1 for an open paren, -1 for a close paren, and OUTSIDE is the buffer position of the outside of the paren. Otherwise return nil." (let* ((before (show-paren--categorize-paren (point)))) (when (or (eq (car before) 1) (eq (car before) -1)) before))) (advice-add 'show-paren--locate-near-paren :override #'show-paren--locate-near-paren-ad)

2.4 Colorize color strings.

(sup 'rainbow-mode) (add-hook 'prog-mode #'rainbow-mode)

2.5 Highlight current line

I find this very helpful not only to quickly locate the cursor, but to read code in general. It helps me focus, especially when stepping through code line by line.


2.6 Auto whitespace cleanup

Couldn’t be bothered to care about whitespace myself. Didn’t we make computers to do repetitive stuff for us??

(add-fs-to-hook 'prog-mode-hook (add-hook 'after-save-hook (fn (whitespace-cleanup))))

2.7 Fonts

My default fonts. Iosevka Meiseki is a customized version of iosevka font. You can find a copy of it here.

(defvar emacs-english-font "Iosevka Meiseki Sans") (defvar emacs-cjk-font "IPAGothic") (setq my-font (concat emacs-english-font "-12")) (add-to-list 'default-frame-alist `(font . ,my-font)) (set-face-attribute 'default t :font my-font)

2.8 Theme

I’ve tried countless other themes, but somehow I just keep coming back to gruvbox. I switch between gruvbox-light-hard and gruvbox-dark-hard fairly often though, both are nice. I’m a big fan of the dark version’s warm and retro-y feel, its visually distinct and comfy.

(sup 'gruvbox-theme) (load-theme 'gruvbox-dark-hard t nil)

2.9 Frame

Make the title look better so that my status bar can print it nicely for the rice screenshots. %b is substituted for the name of the currently active buffer.

(setq-default frame-title-format '("emacs: %b"))

2.10 Modeline

I’ve liked smart-mode-line in the past, but I prefer telephone line’s modularity, design, and visual appeal right now.

(sup 'telephone-line)

Use a neat cubic curved shape to separate segments.

(require 'telephone-line) (setq telephone-line-primary-left-separator 'telephone-line-cubed-left telephone-line-secondary-left-separator 'telephone-line-cubed-hollow-left telephone-line-primary-right-separator 'telephone-line-cubed-right telephone-line-secondary-right-separator 'telephone-line-cubed-hollow-right) (setq telephone-line-height 24)

evil-use-short-tag makes telephone-line’s meow segment, which I wrote and upstreamed, use single letters to show meow state instead of the whole word, so like “N” instead of “NORMAL”.

(setq telephone-line-evil-use-short-tag t)

2.10.1 Custom segments

One of the big reasons I like telephone line is the absolute ease of defining new segments that look nice as hell.

(telephone-line-defsegment* telephone-line-simpler-major-mode-segment () (concat "[" (if (listp mode-name) (car mode-name) mode-name) "]"))

This segment is a simpler indicator of position. I don’t use line numbers on the side of my screen, so it’s pretty neccessary for me to quickly parse my position at a glance. Column numbers are on the left since they change more often, and this segment is the leftmost element on the right side of my bar. This means that there’s no unnecessary movement.

(telephone-line-defsegment* telephone-line-simple-pos-segment () (concat "%c : " "%l/" (number-to-string (count-lines (point-min) (point-max)))))

2.10.2 Segment setup

The meat of my line config.

(setq telephone-line-lhs '((nil . (telephone-line-projectile-buffer-segment)) (accent . (telephone-line-simpler-major-mode-segment)) (nil . (telephone-line-meow-tag-segment telephone-line-misc-info-segment))) telephone-line-rhs '((nil . (telephone-line-simple-pos-segment)) (accent . (telephone-line-buffer-modified-segment)))) (telephone-line-mode 1)

2.11 Pixel scrolling

New feature in Emacs 29! Do try it out, it’s really quite neat. this function only activates when possible.

(defun pixel-scroll-setup () (interactive) (setq pixel-scroll-precision-large-scroll-height 1) (setq pixel-scroll-precision-interpolation-factor 1)) (when (boundp 'pixel-scroll-precision-mode) (pixel-scroll-setup) (add-hook 'prog-mode-hook #'pixel-scroll-precision-mode) (add-hook 'org-mode-hook #'pixel-scroll-precision-mode))

3 Packages

3.1 Nyaatouch

Nyaatouch is my personal modal editing system. It is highly optimized for the dvorak keyboard and is built on meow (hence the name).

(sup '(nyaatouch :repo "https://github.com/eshrh/nyaatouch" :fetcher github)) (turn-on-nyaatouch)

Nyaatouch brings in some packages as dependencies: avy, swiper, meow, smartparens. You can find more information about it at the repo.

3.1.1 Just exchange point and mark

I use C-x C-x to return to a point more than I use it to reverse selection. Meow uses kmacros, so I have to redefine the macro used when meow-reverse is called as well.

(defun just-exchange-point-and-mark () (interactive) (call-interactively #'exchange-point-and-mark) (deactivate-mark)) (global-set-key (kbd "C-x C-x") #'just-exchange-point-and-mark) (global-set-key (kbd "C-x 9 1") #'exchange-point-and-mark) ; unused key (setq meow--kbd-exchange-point-and-mark "C-x 9 1")

3.2 Far

Add a meow keybind for paragraph filling.

(straight-use-package '(far :type git :repo "https://github.com/eshrh/far.el")) (meow-normal-define-key '("`" . far-fill-paragraph))

Far.el is an implementation of far, a DP-based paragraph filling algorithm that minimizes variance of line lengths.

3.3 Undo-tree

Better undo for emacs. I really enjoy the tree visualization feature this package adds. If you haven’t checked it out, try pressing C-x u!

(sup 'undo-tree) (global-undo-tree-mode) (setq undo-tree-auto-save-history nil)

3.4 Ace-window

Ace-window is super nice because it lets you quickly switch to a window when you have >2 open by providing a letter hint.

(sup 'ace-window) (global-set-key [remap other-window] 'ace-window)

dvorak moment

(setq aw-keys '(?a ?o ?e ?u ?i ?d ?h ?t ?n ?s))

don’t hint me for things outside the frame

(setq aw-scope 'frame)

I never want to switch to the current buffer

(setq aw-ignore-current t) (setq aw-background nil)

3.5 Dashboard

An essential component of any emacs-window-with-neofetch-and-tiling-wm-and-anime-girl-wp screenshot.

(sup 'dashboard) (dashboard-setup-startup-hook)

This is an important section because in order for dashboard to produce the org agenda, every org file needs to be opened, which means your recent list is just cluttered. The second line makes dashboard close each buffer after opening them so it doesn’t clutter up your buffer list.

(setq recentf-exclude '("~/org/")) (setq dashboard-agenda-release-buffers t)

Startup to the dashboard

(setq initial-buffer-choice (get-buffer "*dashboard*"))

(setq dashboard-center-content t) (setq dashboard-show-shortcuts nil) (setq dashboard-set-footer nil)

Declutter the items shown on the dashboard and change the section names to be hip (lower case) and cool (shorter)

(setq dashboard-items '((recents . 5) (projects . 5) (agenda . 5))) (setq dashboard-agenda-sort-strategy '(time-up)) (setq dashboard-item-names '(("Recent Files:" . "recent:") ("Projects:" . "projects:") ("Agenda for the coming week:" . "agenda:")))

Nice image and nice title. If we’re in the terminal, display an ASCII gnu instead.

(setq dashboard-banner-logo-title "GNU emacsへようこそ。") (defmacro set-dashboard-banner (name) `(setq dashboard-startup-banner (expand-file-name ,name user-emacs-directory))) (if (or (display-graphic-p) (daemonp)) (set-dashboard-banner "hiten_render_rsz.png") (set-dashboard-banner "gnu.txt"))

3.6 Company

The one true autocompleter! Company mode takes a bit to startup, so defer.

(sup 'company) (add-hook 'after-init-hook #'global-company-mode) (sup 'company-ctags)

3.7 Projectile

Project-aware emacs commands.

(sup 'projectile) (projectile-mode 1) (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)

The main feature I use from projectile is it’s awesome fuzzy search for files across your entire project. I use this so much that I use it as my default find file function, only when I’m in a project.

(defun find-file-or-projectile () (interactive) (if (projectile-project-p) (call-interactively 'projectile-find-file) (call-interactively 'find-file))) (global-set-key (kbd "C-x C-f") 'find-file-or-projectile) ;; just in case i need to use standard find file, probably to make a file. (meow-leader-define-key '("U" . find-file))

3.8 Searching

3.8.1 Vertico

(sup '(vertico :files (:defaults "extensions/*") :includes (vertico-directory))) (vertico-mode)

When using the find-file dialog, pressing backspace should take you back up to the parent, not just delete one character.

(define-key vertico-map (kbd "DEL") #'vertico-directory-delete-char)

3.8.2 Marginalia

An essential addition to the completing-read buffer that offers a bit of documentation to entries.

(sup 'marginalia) (marginalia-mode)

3.8.3 Posframe

In GUI mode, I like to have all completing-read queries come up in a new frame in the middle of my screen. Posframe does this with a child frame. I like how it looks.

(when (display-graphic-p) (sup 'vertico-posframe) (vertico-posframe-mode 1))

Specifically in gruvbox, the border is the same color as the background for some silly reason. I intend to submit an issue for this.

(when (display-graphic-p) (set-face-background 'vertico-posframe-border (face-attribute 'region :background)))

3.8.4 Orderless completion

(sup 'orderless) (setq completion-styles '(orderless basic) completion-category-defaults nil completion-category-overrides '((file (styles partial-completion))))

3.9 Helpful

Better describe* functions that have more information and look neater. For example helpful’s describe-function includes the source code of the function itself, which is very useful when writing elisp.

(sup 'helpful)

Override keybindings.

(-map (lambda (pair) (global-set-key (kbd (concat "C-x h " (car pair))) (cdr pair))) (-zip '("f" "v" "k") '(helpful-callable helpful-variable helpful-key)))

3.10 Dired

Dired jump opens dired to the directory of the file visited by the current buffer. Typically this is set to C-x C-j but this is mildly uncomfortable to type on dvorak. C-x d is where dired with prompt is originally.

(global-set-key (kbd "C-x d") #'dired-jump) (global-set-key (kbd "C-x C-j") #'dired)

With two dired panes open, any command in one pane will autocomplete to the path in the second pane.

(setq dired-dwim-target t)

Don’t show owner and perms by default. Pressing ( toggles this off again.

(add-fs-to-hook 'dired-mode-hook (dired-hide-details-mode 1))

Use only one dired directory at a time.

(setq dired-kill-when-opening-new-dired-buffer t)

(add-fs-to-hook 'dired-mode-hook (define-key dired-mode-map (kbd "-") #'swiper) (define-key dired-mode-map (kbd "<") #'beginning-of-buffer) (define-key dired-mode-map (kbd ">") #'end-of-buffer))

3.11 Tree-sitter

Introduced in emacs 30.

(setq treesit-available (and (fboundp 'treesit-available-p) (treesit-available-p)))

Create a list of grammar urls.

(when treesit-available (defun treesitter-grammar-url (lang) (concat "https://github.com/tree-sitter/tree-sitter-" lang)) (setq treesit-langs '(bash c cpp haskell html java javascript julia rust python)) (setq treesit-language-source-alist (--map `(,it . (,(treesitter-grammar-url (symbol-name it)))) treesit-langs)))

(defun treesit-ensure (lang) (unless (treesit-language-available-p lang) (treesit-install-language-grammar lang)))

3.12 Highlights

3.12.1 Lisp highlighting

Install a bunch of Fanael’s visual packages to make lisp source editing much nicer.

  • highlight-defined: highlight known symbols instead of just the built in ones
  • highlight-numbers: numbers
  • highlight-delimiters: highlight brackets and parens nicely
  • highlight-quoted: highlight quoted symbols in a different color [applies only to elisp]

I really do find these pretty essential for comfortable lisp programming, but maybe just because I’m so used to them.

(sup 'highlight-defined) (sup 'highlight-numbers) (sup 'rainbow-delimiters) (sup 'highlight-quoted) (defun highlight-lisp-things-generic () (highlight-numbers-mode) (highlight-defined-mode) (rainbow-delimiters-mode))

The function highlight-lisp-things-generic does not include highlight-quoted, which only makes sense for emacs lisp

(add-hook 'emacs-lisp-mode-hook #'highlight-quoted-mode) (add-to-hooks #'highlight-lisp-things-generic 'lisp-data-mode-hook 'clojure-mode-hook)

Most lisp modes inherit from lisp-data-mode. Clojure-mode does not.

3.12.2 Highlight todos

(sup 'hl-todo) (global-hl-todo-mode)

3.13 Which-key

There are too many emacs keybindings and life is too short.

(sup 'which-key) (which-key-mode)

3.14 Terminal and shell config

Vterm is undoubtedly the best terminal in emacs. Depends on the module libvterm, which means your emacs has to be compiled with module support enabled (damn you Ubuntu!!).

(sup 'vterm) (sup 'fish-mode)

3.14.1 Config

Make hl-line-mode turn off in vterm-mode.

(add-fs-to-hook 'vterm-mode-hook (setq-local global-hl-line-mode (null global-hl-line-mode)))

Kill the buffer when C-d is pressed to exit the shell.

(setq vterm-kill-buffer-on-exit t)

Change the name of the buffer

(setq vterm-buffer-name-string "vt")

Start vterm mode in the insert meow state.

(add-to-list 'meow-mode-state-list '(vterm-mode . insert))

3.14.2 Vterm-toggle

Toggles a window with a re-usable vterm. Good for reducing buffer clutter.

(sup 'vterm-toggle) (setq vterm-toggle-hide-method 'delete-window) (setq vterm-toggle-fullscreen-p nil) (add-to-list 'display-buffer-alist '((lambda (buffer-or-name _) (let ((buffer (get-buffer buffer-or-name))) (equal major-mode 'vterm-mode))) (display-buffer-reuse-window display-buffer-at-bottom) (dedicated . t) (reusable-frames . visible) (window-height . 0.3)))

3.14.3 Kill vterm buffer and window

(defun vterm--kill-vterm-buffer-and-window (process event) "Kill buffer and window on vterm process termination." (when (not (process-live-p process)) (let ((buf (process-buffer process))) (when (buffer-live-p buf) (with-current-buffer buf (kill-buffer) (ignore-errors (delete-window)) (message "VTerm closed.")))))) (add-fs-to-hook 'vterm-mode-hook (set-process-sentinel (get-buffer-process (buffer-name)) #'vterm--kill-vterm-buffer-and-window))

3.14.4 Keybindings

(meow-leader-define-key '("d" . vterm-toggle-cd))

3.15 Org-mode

(sup 'org)

Path configuration. I use a directory called org in my home directory to store my org files.

(when (file-exists-p "~/org/") (setq org-directory "~/org/") (setq org-agenda-files '("~/org/")))

Allow lists like a) b) c)

(setq org-list-allow-alphabetical t)

Enable and disable some modes on opening an org buffer

  • Indent-mode means that star headings are hidden and hierarchy is whitespace-based
  • Turn off electrict quote completion because it makes typing elisp quotes annoying.
  • Turn on auto-fill mode to prevent lines from getting too long.

(add-fs-to-hook 'org-mode-hook org-indent-mode (electric-quote-mode -1) auto-fill-mode)

Don’t insert lines in between headers and list items.

(setf org-blank-before-new-entry '((heading . nil) (plain-list-item . nil)))

Change the backends.

(sup 'ox-pandoc) (setq org-export-backends '(latex beamer md html odt ascii pandoc))

Don’t indent code in org-babel

(setq org-edit-src-content-indentation 0)

Even emacs can’t make me not procrastinate!

(setq org-deadline-warning-days 2)

Babel src setup

(setq org-src-fontify-natively t org-confirm-babel-evaluate nil org-src-preserve-indentation t)

3.15.1 Org-fragtog

A neat little package to render latex fragments as you write them.

(sup 'org-fragtog)

Quick function to disable fragtogging while in a table

(defun org-inside-latex-block () (eq (nth 0 (org-element-at-point)) 'latex-environment)) (setq org-fragtog-ignore-predicates '(org-at-table-p org-inside-latex-block))

3.15.2 Org-ref

Cool package to deal with citations in org. Especially nice when writing latex in org-mode.

My typical workflow involves importing papers into zotero, which will automatically update a system-wide bibliography file stored in bibtex thanks to the better bibtex extension, which is essential.

(sup 'org-ref) (sup 'ivy-bibtex) (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 org-ref-cite-onclick-function (lambda (_) (org-ref-citation-hydra/body))) (with-eval-after-load 'org (define-key org-mode-map (kbd "s-<return>") 'org-meta-return) (define-key org-mode-map (kbd "C-c ]") 'org-ref-insert-link) (define-key org-mode-map (kbd "S-]") 'org-ref-insert-link-hydra/body) (define-key org-mode-map (kbd "C-c r") 'org-ref-citation-hydra/body)) (setq bibtex-completion-bibliography '("~/docs/library.bib")) (setq org-latex-pdf-process (list "latexmk -shell-escape -bibtex -f -pdf %f"))

3.15.3 Org-roam

(sup 'org-roam) (setq org-roam-v2-ack t)

Basic setup. org-roam-db-autosync-mode is nice, but adds about 1.5s to my startup time. Not good!

(unless (file-directory-p "~/roam") (make-directory "~/roam")) (setq org-roam-directory (file-truename "~/roam"))

Pressing enter while your point is on a link should follow the link.

(setq org-return-follows-link t)

Keybindings for my most used roam actions. publish.el refers to a personal elisp file I use to generate a website from my roam files you can find here!

(global-set-key (kbd "C-c c i") #'org-roam-node-insert) (global-set-key (kbd "C-c c f") #'org-roam-node-find) (global-set-key (kbd "C-c c s") #'org-roam-db-sync) (global-set-key (kbd "C-c c p") (fn (interactive) (load-file "~/roam/publish.el")))

The default file name looks ugly and leads to ugly urls once exported. This makes the filenames just the titles.

(setq org-roam-capture-templates '(("d" "default" plain "%?" :target (file+head "${slug}.org" "#+title: ${title}\n") :unnarrowed t)))

3.15.4 Latex

Adds my favorite document class, IEEE transactions to the org latex export.

(with-eval-after-load 'ox-latex (add-to-list 'org-latex-classes '("IEEEtran" "\\documentclass{IEEEtran}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))))

3.16 IRC

I store some IRC secrets outside of dotfile version control.

(setq erc-default-server "irc.libera.chat") (add-hook 'erc-before-connect (lambda (SERVER PORT NICK) (when (file-exists-p "ircconfig.elc") (load-file (expand-file-name "ircconfig.elc" user-emacs-directory)))))

3.17 YASnippet

Just works!

(sup 'yasnippet) (yas-global-mode) (setq yas-indent-line 'fixed)

3.18 Flycheck

(sup 'flycheck)

3.19 Magit

Nothing to be said here

(sup 'magit)

Ediff makes dealing with merging conflicts extremely comfortable. I make some quick changes to how it lays out merge windows.

(setq ediff-diff-options "") (setq ediff-custom-diff-options "-u") (setq ediff-window-setup-function 'ediff-setup-windows-plain) (setq ediff-split-window-function 'split-window-vertically)

3.20 Ligatures and symbols

Prettify symbols is emacs’ built in method for symbol replacment. Any string of any length can be replaced by a character. The prettify-symbols-alist is buffer local, so it can be modified via hook.

(global-prettify-symbols-mode) (add-fs-to-hook 'emacs-lisp-mode-hook (push '("fn" . ?∅) prettify-symbols-alist))

Ligature.el provides true ligatures.

(sup 'ligature) (ligature-set-ligatures 'prog-mode '( "|||>" "<|||" "<==>" "<!--" "~~>" "***" "||=" "||>" "://" ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!==" "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<" "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->" "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<" "..." "+++" "/==" "///" "_|_" "&&" "^=" "~~" "~@" "~=" "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|" "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:" ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:" "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!" "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:" "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)")) (global-ligature-mode)

3.21 LSP

eglot is built into emacs 29. install if not found.

(unless (boundp 'eglot) (sup 'eglot)) (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(python-ts-mode . ("pylsp")))) (add-to-hooks #'eglot-ensure 'python-mode-hook 'python-ts-mode-hook)

turn off bold symbol highlighting

(custom-set-faces '(eglot-highlight-symbol-face ((t (:inherit nil)))))

(add-fs-to-hook 'flymake-mode-hook (define-key flymake-mode-map (kbd "C-c C-n") #'flymake-goto-next-error))

3.22 Grep

Deadgrep offers (imo) the best interface to ripgrep, a fast text searcher. In the interest of portability, it is only installed if the ripgrep binary, “rg” is also installed

(when (executable-find "rgrep") (sup 'deadgrep))

3.23 Language-specific config

3.23.1 Java

(sup 'meghanada) (add-fs-to-hook 'java-mode-hook meghanada-mode flycheck-mode (setq c-basic-offset 4) (setq tab-width 4))

3.23.2 Haskell

Interactive haskell mode lets you use the nice repl with C-c C-z

(sup 'haskell-mode) (add-hook 'haskell-mode-hook #'interactive-haskell-mode)

Interactive haskell error customization

(setq haskell-interactive-popup-errors t)

3.23.3 C

(when treesit-available (treesit-ensure 'c) (treesit-ensure 'cpp) (treesit-ensure 'rust) (add-to-list 'major-mode-remap-alist '(c-mode . c-ts-mode))) (setq-default c-basic-offset 4 kill-whole-line t indent-tabs-mode nil)

3.23.4 Lisp

The best common lisp ide!

(sup 'slime) (setq inferior-lisp-program "sbcl") (sup 'slime-company) (add-fs-to-hook 'common-lisp-mode-hook (slime-setup '(slime-fancy slime-company))) (add-hook 'lisp-mode-hook #'flycheck-mode)

  1. Smartparens


    Define a function to disable auto quote-completion. This is annoying in lisplike languages.

    (defun sp-disable (mode str) (sp-local-pair mode str nil :actions nil))

    Disable single quote pairing in lisp-data modes

    (sp-disable 'lisp-data-mode "'")

  2. Elisp

    (sup 'elisp-format) (setq elisp-format-column 80) (sp-disable 'emacs-lisp-mode "'") (sp-disable 'emacs-lisp-mode "`") (sp-disable 'org-mode "'")

  3. Aggressive indenting

    Keeps code indented no matter what. This package is extremely broken for most block based languages, but works like a charm for lisps.

    (sup 'aggressive-indent-mode) (add-hook 'lisp-data-mode-hook #'aggressive-indent-mode 1)

3.23.5 TeX

AuCTeX offers a lot of sweet features that I’ve come to take for granted

(sup 'auctex) (setq TeX-parse-self t)

TeX-parse-self enables parsing your to give you more options in the environment inserter (C-c C-e)

Use sioyek to view pdfs compiled with tex. Sioyek has some rough edges to be sure, but it also has really cute features centered around technical material.

I also include some fallbacks in order of my preference.

In order to add a custom tex viewing program, it must have an entry in TeX-view-program-list that uses some expansion tokens that you’re free to copy from here. Some day, TODO, I’d like to get this upstreamed to emacs, I do believe that sioyek is sufficiently popular.

(setq pdf-viewer-exec-alist '((sioyek . "Sioyek") (zathura . "Zathura") (evince . "evince") (okular . "Okular"))) (setq my-pdf-viewer (->> pdf-viewer-exec-alist (-first (-compose #'executable-find #'symbol-name #'car)) cdr)) (add-fs-to-hook 'LaTeX-mode-hook (setq TeX-view-program-selection `((output-pdf ,my-pdf-viewer) (output-dvi ,my-pdf-viewer) (output-html "xdg-open"))) auto-fill-mode)

Reftex integration

(add-hook 'LaTeX-mode-hook #'turn-on-reftex) (setq reftex-plug-into-AUCTeX t)

Make <tab> cycle sections just like in org mode

(sup 'outline-magic) (add-hook 'LaTeX-mode-hook #'outline-minor-mode) (add-fs-to-hook 'LaTeX-mode-hook (define-key outline-minor-mode-map (kbd "<tab>") 'outline-cycle))

  1. Japanese latex mode use dvipdfmx

    Some journal templates for japanese organizations use platex rather than latex. japanese-latex-mode tries to choose the correct compilers, but for whatever reason fails to use dvipdfmx to convert dvi to pdf.

    (advice-add #'japanese-latex-mode :after (lambda () (setq TeX-PDF-from-DVI "Dvipdfmx")))

3.23.6 Python

(when treesit-available (treesit-ensure 'python) (add-to-list 'major-mode-remap-alist '(python-mode . python-ts-mode))) (setq python-mode-hook-alias (if treesit-available 'python-ts-mode-hook 'python-mode-hook)) (setq python-mode-map-alias (if treesit-available 'python-ts-mode-map 'python-mode-map))

  1. IPython

    Make ipython the default shell

    (setq python-shell-interpreter "ipython" python-shell-interpreter-args "-i --simple-prompt --InteractiveShell.display_page=True")

    conda env management

    (sup 'conda)

  2. Campus

    A small package I wrote to make repl interaction cleaner

    (sup '(campus :type git :fetcher github :repo "https://github.com/eshrh/campus-emacs" :files ("*.el"))) (if treesit-available (add-fs-to-hook 'python-ts-mode-hook (define-key python-ts-mode-map (kbd "C-c C-l") #'python-shell-send-buffer) (define-key python-ts-mode-map (kbd "C-c + +") #'campus-make-partition) (define-key python-ts-mode-map (kbd "C-c + -") #'campus-remove-partition-forward) (define-key python-ts-mode-map (kbd "C-c C-c") #'campus-send-region)) (add-fs-to-hook 'python-mode-hook (define-key python-mode-map (kbd "C-c C-l") #'python-shell-send-buffer) (define-key python-mode-map (kbd "C-c + +") #'campus-make-partition) (define-key python-mode-map (kbd "C-c + -") #'campus-remove-partition-forward) (define-key python-mode-map (kbd "C-c C-c") #'campus-send-region)))

  3. Make describe at point functional

    Default describe at point function sends the symbols to the python process as a string. This means it will never work for functions imported from somewhere. I therefore redefine this function here

    (defun python-describe-at-point1 (symbol process) (interactive (list (python-info-current-symbol) (python-shell-get-process))) (comint-send-string process (concat "help(" symbol ")\n"))) (advice-add #'python-describe-at-point :override #'python-describe-at-point1)

  4. Extra convenience bindings

    Ipython with %matplotlib QtAgg lets you throw up a window and repeatedly reuse it for different figures. This is a cool feature, but it leads to me calling plt.clf() really often.

    (defun python-clear-matplotlib () (interactive) (python-shell-send-string-no-output "plt.clf()") (message "Matplotlib plot cleared.")) (if treesit-available (add-fs-to-hook 'python-ts-mode-hook (define-key python-ts-mode-map (kbd "C-c C-,") #'python-clear-matplotlib)) (add-fs-to-hook 'python-mode-hook (define-key python-mode-map (kbd "C-c C-,") #'python-clear-matplotlib)))

  5. Symbols via prettify

    Some people will call this deranged. I don’t care.

    (add-fs-to-hook (if treesit-available 'python-ts-mode-hook 'python-mode-hook) (push '("None" . ?∅) prettify-symbols-alist) (push '("return" . ?») prettify-symbols-alist)) ;❱)

3.23.7 Clojure

Cider is really good

(sup 'clojure-mode) (sup 'cider) (sp-disable 'clojure-mode "'")

3.23.8 Julia

Julia-snail is a cider-flavor ide environment for julia. I find it’s dwim send-to-repl feature nice. This package depends on both standard julia-mode and also vterm for its repl.

(sup 'julia-snail) (add-hook 'julia-mode-hook #'julia-snail-mode)

3.23.9 ASM

The default asm indentation style is completely deranged.

(defun my-asm-mode-hook () (setq tab-always-indent (default-value 'tab-always-indent))) (add-fs-to-hook 'asm-mode-hook (local-unset-key (vector asm-comment-char)) (setq tab-always-indent (default-value 'tab-always-indent)))

3.23.10 Other

  1. Kmonad

    Kmonad config lang. Only load when the file exists.

    (sup '(kbd-mode :type git :repo "https://github.com/kmonad/kbd-mode")) (add-hook 'kbd-mode-hook (fn (aggressive-indent-mode -1)))

  2. Matsurika

    (sup '(matsurika-mode :type git :host github :repo "eshrh/matsurika-mode" :files ("*.el" "docs.txt")))

4 Other config and elisp

4.1 Inhibit startup screen

(setq inhibit-startup-screen t)

4.2 User information

Add some variables that various programs, especially mail programs use.

(setq user-full-name "Eshan Ramesh" user-mail-address "esrh@gatech.edu")

4.3 Yes or no to y/n

Turn the yes or no prompts into y or n prompts. This makes it easier and faster to type since emacs will insist you type out y e s.

(defalias 'yes-or-no-p 'y-or-n-p)

Don’t ask for confirmation when i visit a git-controlled source file. This is especially helpful when you want to get to a build file from a help page from some package installed by straight.

(setq vc-follow-symlinks nil)

4.5 Don’t confirm on buffer kill

Living dangerously! Don’t confirm when killing a buffer.

(setq kill-buffer-query-functions (delq 'process-kill-buffer-query-function kill-buffer-query-functions))

4.6 Temporary files in /tmp

Taken from emacswiki. Makes emacs stop littering your working directories with autosave information. Instead, leave them all in /tmp/emacsXXXX where XXXX is a user unique id (which prevents multiple users (who don’t exist on my computers) from having conflicting auto save files).

(defconst emacs-tmp-dir (expand-file-name (format "emacs%d" (user-uid)) temporary-file-directory)) (setq backup-directory-alist `((".*" . ,emacs-tmp-dir))) (setq auto-save-file-name-transforms `((".*" ,emacs-tmp-dir t))) (setq auto-save-list-file-prefix emacs-tmp-dir)

4.7 Make directories in find-file

(defadvice find-file (before make-directory-maybe (filename &optional wildcards) activate) "Create parent directory if not exists while visiting file." (unless (file-exists-p filename) (let ((dir (file-name-directory filename))) (unless (file-exists-p dir) (make-directory dir t)))))

4.8 Split and follow

Does what it says on the tin. I feel like everyone has some version of these functions copied from somewhere or another on the internet.

(defun split-and-follow-horizontally () (interactive) (split-window-below) (balance-windows) (other-window 1)) (defun split-and-follow-vertically () (interactive) (split-window-right) (balance-windows) (other-window 1))

Bind these new functions to override the old ones

(global-set-key (kbd "C-x 2") 'split-and-follow-horizontally) (global-set-key (kbd "C-x 3") 'split-and-follow-vertically)

4.9 Delete frame and buffer

Taken from here

(defun maybe-delete-frame-buffer (frame) "When a dedicated FRAME is deleted, also kill its buffer. A dedicated frame contains a single window whose buffer is not displayed anywhere else." (let ((windows (window-list frame))) (when (eq 1 (length windows)) (let ((buffer (window-buffer (car windows)))) (when (eq 1 (length (get-buffer-window-list buffer nil t))) (kill-buffer buffer)))))) (add-hook 'delete-frame-functions #'maybe-delete-frame-buffer)

4.10 Custom keybinds

4.10.1 Kill both buffer and window keybinding

(global-set-key (kbd "C-x k") 'kill-buffer) (global-set-key (kbd "C-x C-k") 'kill-buffer-and-window)

4.10.2 Comment or uncomment

(global-set-key (kbd "C-c /") #'comment-or-uncomment-region)

4.11 Spellcheck locale

Taken from here: http://blog.binchen.org/posts/what-s-the-best-spell-check-set-up-in-emacs/

(cond ;; try hunspell at first ;; if hunspell does NOT exist, use aspell ((executable-find "hunspell") (setq ispell-program-name "hunspell") (setq ispell-local-dictionary "en_US") (setq ispell-local-dictionary-alist ;; Please note the list `("-d" "en_US")` contains ACTUAL parameters passed to hunspell ;; You could use `("-d" "en_US,en_US-med")` to check with multiple dictionaries '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_US") nil utf-8))) ;; new variable `ispell-hunspell-dictionary-alist' is defined in Emacs ;; If it's nil, Emacs tries to automatically set up the dictionaries. (when (boundp 'ispell-hunspell-dictionary-alist) (setq ispell-hunspell-dictionary-alist ispell-local-dictionary-alist))) ((executable-find "aspell") (setq ispell-program-name "aspell") ;; Please note ispell-extra-args contains ACTUAL parameters passed to aspell (setq ispell-extra-args '("--sug-mode=ultra" "--lang=en_US"))))

4.12 Switch two buffers

(global-set-key (kbd "C-x w") 'ace-swap-window)

4.13 Function to reload init

I make too many changes to type this out every time.

(defun load-init () (interactive) (load-file (expand-file-name "init.el" user-emacs-directory)))

4.14 Load current file

(defun load-this-file () (interactive) (load-file (buffer-file-name))) (define-key emacs-lisp-mode-map (kbd "C-c C-b") 'load-this-file)

4.15 Kill other buffers

(defun kill-other-buffers () "Kill all other buffers." (interactive) (mapc 'kill-buffer (delq (current-buffer) (buffer-list))))

4.16 Spaces over tabs

I don’t like tabs

(setq-default indent-tabs-mode nil)

4.17 Final newline

(setq mode-require-final-newline t)

4.18 Aggressive indenting

(sup 'aggressive-indent-mode) (add-hook 'lisp-data-mode-hook #'aggressive-indent-mode)

4.19 Scratch config

Set the initial mode to be lisp interaction. No default text.

(setq initial-major-mode 'lisp-interaction-mode) (setq initial-scratch-message "")

4.20 Disable dialog boxes

(setq use-dialog-box nil)

4.21 C-x remap

Important code that switches C-x and C-u. This is helpful for me because I use dvorak, and C-x is far more common and useful compared te C-u. This must be at the end of the file because it basically redefines every other command that I bound to the C-x prefix anywhere above.

(define-key key-translation-map [?\C-x] [?\C-u]) (define-key key-translation-map [?\C-u] [?\C-x])

4.22 Don’t confirm exiting when active processes exist

(setq confirm-kill-processes nil)

4.23 Comint keybindings

(define-key comint-mode-map (kbd "C-p") #'comint-previous-input) (define-key comint-mode-map (kbd "C-n") #'comint-next-input) (define-key comint-mode-map (kbd "C-w") #'backward-kill-word)