#+TITLE: Emacs Configuration
#+AUTHOR: Preston Pan
#+DESCRIPTION: my personal emacs configuration for nixOS
#+html_head:
* Introduction
This is my Vanilla Emacs configuration, made to work with my NixOS configuration. For that
reason, you will not see :ensure t inside any use-package declaration, for emacs packages
are all compiled natively and reproducibly on the NixOS side. This configuration uses the
emacs-lisp language only to configure variables for said packages, for the most part.
** UI Elements
This section contains important UI elements and starting customization variables to make
emacs work in a semi-sane way and make it not look ugly:
#+begin_src emacs-lisp
(pixel-scroll-precision-mode 1)
(setq scroll-conservatively 101)
(display-battery-mode 1)
(setq display-time-24hr-format t)
(display-time-mode 1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(load-theme 'catppuccin :no-confirm)
(setq display-line-numbers-type 'relative)
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
(add-hook 'org-mode-hook #'display-line-numbers-mode)
(setq org-confirm-babel-evaluate nil)
(set-face-attribute 'default nil :height 120)
(setq use-short-answers t)
(setq make-backup-files nil)
(setq org-export-with-broken-links t)
(setq org-src-fontify-natively t)
;; (setq org-highlight-latex-and-related '(latex script entities))
(setq warning-minimum-level :emergency)
(add-hook 'text-mode-hook 'visual-line-mode)
(and window-system (server-start))
(setq debug-ignored-errors
(cons 'remote-file-error debug-ignored-errors))
(set-face-attribute 'default nil :font "Iosevka Nerd Font" :height 140)
(setq org-preview-latex-image-directory "/home/preston/.cache/ltximg/")
(add-hook 'prog-mode-hook
(lambda ()
(setq prettify-symbols-alist
'(("lambda" . ?λ)
("->" . ?→)
("map" . ?↦)
("/=" . ?≠)
("!=" . ?≠)
("==" . ?≡)
("<=" . ?≤)
(">=" . ?≥)
("&&" . ?∧)
("||" . ?∨)
("sqrt" . ?√)
("..." . ?…)))
(prettify-symbols-mode)))
(add-hook 'org-mode-hook (lambda ()
(setq prettify-symbols-alist
'(("#+begin_src" . ?)
("#+BEGIN_SRC" . ?)
("#+end_src" . ?)
("#+END_SRC" . ?)
("#+begin_example" . ?)
("#+BEGIN_EXAMPLE" . ?)
("#+end_example" . ?)
("#+END_EXAMPLE" . ?)
("#+header:" . ?)
("#+HEADER:" . ?)
("#+name:" . ?﮸)
("#+NAME:" . ?﮸)
("#+results:" . ?)
("#+RESULTS:" . ?)
("#+call:" . ?)
("#+CALL:" . ?)
(":PROPERTIES:" . ?)
(":properties:" . ?)
("lambda" . ?λ)
("->" . ?→)
("map" . ?↦)
("/=" . ?≠)
("!=" . ?≠)
("==" . ?≡)
("<=" . ?≤)
(">=" . ?≥)
("&&" . ?∧)
("||" . ?∨)
("sqrt" . ?√)
("..." . ?…))))
(prettify-symbols-mode))
(global-prettify-symbols-mode 1)
#+end_src
* Transparency
My NixOS configuration uses Hyprland to make things transparent:
#+begin_src emacs-lisp
(set-frame-parameter nil 'alpha-background 90)
(add-to-list 'default-frame-alist '(alpha-background . 90))
#+end_src
* Scrolling
Make emacs scroll in a sane way:
#+begin_src emacs-lisp
(setq mouse-wheel-scroll-amount '(1 ((shift) . 1)))
(setq mouse-wheel-progressive-speed nil)
(setq mouse-wheel-follow-mouse 't)
(setq scroll-step 1)
#+end_src
* Agenda
Configure org agenda variables:
#+begin_src emacs-lisp
(require 'org-habit)
(setq org-agenda-files (list "~/org/agenda.org"
"~/org/notes.org"))
(setq org-default-notes-file (concat org-directory "/notes.org"))
(setq org-habit-preceding-days 1)
#+end_src
* Publishing
This is the configuration required to publish my website:
#+begin_src emacs-lisp
(require 'ox-publish)
(setq org-publish-project-alist
'(("website-org"
:base-directory "~/org/website"
:base-extension "org"
:publishing-directory "~/website_html"
:recursive t
:publishing-function org-html-publish-to-html
:headline-levels 4
:html-preamble t
:html-preamble-format (("en" "
home | section main page
")))
("website-static"
:base-directory "~/org/website"
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|ico"
:publishing-directory "~/website_html/"
:recursive t
:publishing-function org-publish-attachment)
("website" :auto-sitemap t :components ("website-org" "website-static"))))
;; (setq org-export-html-postamble-format '(("en" "previous page | home
")))
(setq org-html-postamble "Copyright © 2024 Preston Pan")
#+end_src
* Autopair
Use electric-pair to automatically complete pairs of things. We need to change
what electric-pair does based on the mode.
#+begin_src emacs-lisp
(defun electric-pair ()
"If at end of line, insert character pair without surrounding spaces.
Otherwise, just insert the typed character."
(interactive)
(if (eolp) (let (parens-require-spaces) (insert-pair)) (self-insert-command 1)))
(add-hook 'org-mode-hook
(lambda ()
(define-key org-mode-map "\"" 'electric-pair)
(define-key org-mode-map "(" 'electric-pair)
(define-key org-mode-map "[" 'electric-pair)
(define-key org-mode-map "{" 'electric-pair)))
(add-hook 'prog-mode-hook
(lambda ()
(define-key prog-mode-map "\"" 'electric-pair)
(define-key prog-mode-map "(" 'electric-pair)
(define-key prog-mode-map "[" 'electric-pair)
(define-key prog-mode-map "{" 'electric-pair)))
#+end_src
* Lyrics
#+begin_src emacs-lisp
(use-package lyrics-fetcher
:after (emms)
:config
(setq lyrics-fetcher-genius-access-token
(password-store-get "genius_api"))
(lyrics-fetcher-use-backend 'genius))
#+end_src
* Fragtog
#+begin_src emacs-lisp
(use-package org-fragtog :hook (org-mode . org-fragtog-mode))
#+end_src
* Snippets
#+begin_src emacs-lisp
(use-package yasnippet
:config
(add-to-list 'yas-snippet-dirs "~/org/website/yasnippet/")
(yas-global-mode 1))
(add-hook 'org-mode-hook
(lambda () (yas-minor-mode)
(yas-activate-extra-mode 'latex-mode)))
#+end_src
* Completion
Company-mode! We need this to do autocomplete stuff.
#+begin_src emacs-lisp
(eval-after-load "company" '(add-to-list 'company-backends '(company-ispell company-capf company-yasnippet company-files))) (add-hook 'after-init-hook 'global-company-mode)
(use-package ispell
:init
(setq ispell-program-name "aspell")
(setq ispell-silently-savep t)
(setq ispell-dictionary "en")
(setq ispell-alternate-dictionary "/home/preston/.local/share/my.dict"))
#+end_src
* Spelling
#+begin_src emacs-lisp
(dolist (hook '(text-mode-hook))
(add-hook hook (lambda () (flyspell-mode 1))))
#+end_src
* Org Babel
For some reason, org-babel doesn't load these languages by default:
#+begin_src emacs-lisp
(org-babel-do-load-languages 'org-babel-load-languages
'(
(shell . t)
(python . t)
(latex . t)
)
)
#+end_src
* Packages
First, some small configurations and some evil-mode initilaization because I like vim keybindings:
#+begin_src emacs-lisp
(require 'org-tempo)
(use-package evil
:init
(setq evil-want-keybinding nil)
:config
(evil-mode 1)
(evil-set-undo-system 'undo-redo))
(use-package evil-collection
:init
(setq evil-want-keybinding nil)
:config
(evil-collection-init))
(with-eval-after-load 'evil-maps
(define-key evil-motion-state-map (kbd "SPC") nil)
(define-key evil-motion-state-map (kbd "RET") nil)
(define-key evil-motion-state-map (kbd "TAB") nil))
(use-package evil-commentary
:config
(evil-commentary-mode))
(use-package evil-org
:after org
:hook (org-mode . (lambda () evil-org-mode))
:config
(require 'evil-org-agenda)
(evil-org-agenda-set-keys))
(use-package which-key
:config
(which-key-mode))
(use-package page-break-lines
:init
(page-break-lines-mode))
(evil-set-initial-state 'pdf-view-mode 'normal)
#+end_src
** Journal
I use org-journal to journal about my life, and it's a part of my website:
#+begin_src emacs-lisp
(use-package org-journal
:after (org)
:init
(setq org-journal-dir "~/org/website/journal/")
(setq org-journal-date-format "%A, %d %B %Y")
(defun org-journal-file-header-func (time)
"Custom function to create journal header."
(concat
(pcase org-journal-file-type
(`daily "#+TITLE: Daily Journal\n#+STARTUP: showeverything\n#+DESCRIPTION: My daily journal entry\n#+AUTHOR: Preston Pan\n#+HTML_HEAD: \n#+html_head: \n#+html_head: \n#+options: broken-links:t")
(`weekly "#+TITLE: Weekly Journal\n#+STARTUP: folded")
(`monthly "#+TITLE: Monthly Journal\n#+STARTUP: folded")
(`yearly "#+TITLE: Yearly Journal\n#+STARTUP: folded"))))
(setq org-journal-file-header 'org-journal-file-header-func)
(setq org-journal-file-format "%Y%m%d.org")
(setq org-journal-enable-agenda-integration t)
)
#+end_src
** Doom Modeline
The default modeline is ugly.
#+begin_src emacs-lisp
(use-package doom-modeline
:config
(doom-modeline-mode 1))
#+end_src
** Grammar
I want to write good!
#+begin_src emacs-lisp
(use-package writegood-mode)
(dolist (hook '(text-mode-hook))
(add-hook hook (lambda () (writegood-mode))))
#+end_src
** Make Org Look Better
Org superstar adds those nice looking utf-8 bullets:
#+begin_src emacs-lisp
(use-package org-superstar
:after (org)
:config
(add-hook 'org-mode-hook (lambda () (org-superstar-mode 1))))
#+end_src
** LSP
We set up eglot, the LSP manager for emacs, now built in:
#+begin_src emacs-lisp
(use-package eglot
:config
(add-hook 'prog-mode-hook 'eglot-ensure))
#+end_src
** Projectile
Manages projects and shit.
#+begin_src emacs-lisp
(use-package projectile
:init
(setq projectile-project-search-path '("~/org" "~/src"))
:config
(projectile-mode +1))
#+end_src
** Dashboard
We want our emacs initialization to be pretty and display useful things.
#+begin_src emacs-lisp
(use-package dashboard
:after (projectile)
:init
(setq dashboard-banner-logo-title "Welcome, Commander!")
(setq dashboard-icon-type 'nerd-icons)
(setq dashboard-vertically-center-content t)
(setq dashboard-set-init-info t)
(setq dashboard-week-agenda t)
(setq dashboard-items '((recents . 5)
(bookmarks . 5)
(projects . 5)
(agenda . 5)
(registers . 5)))
:config
(dashboard-setup-startup-hook))
#+end_src
** Ivy
Ivy is a pretty cool general program for displaying stuff:
#+begin_src emacs-lisp
(use-package counsel)
(use-package ivy
:init
(setq ivy-use-virtual-buffers t)
(setq enable-recursive-minibuffers t)
;; enable this if you want `swiper' to use it
;; (setq search-default-mode #'char-fold-to-regexp)
(global-set-key "\C-s" 'swiper)
(global-set-key (kbd "C-c C-r") 'ivy-resume)
(global-set-key (kbd "") 'ivy-resume)
(global-set-key (kbd "M-x") 'counsel-M-x)
(global-set-key (kbd "C-x C-f") 'counsel-find-file)
(global-set-key (kbd " f") 'counsel-describe-function)
(global-set-key (kbd " v") 'counsel-describe-variable)
(global-set-key (kbd " o") 'counsel-describe-symbol)
(global-set-key (kbd " l") 'counsel-find-library)
(global-set-key (kbd " i") 'counsel-info-lookup-symbol)
(global-set-key (kbd " u") 'counsel-unicode-char)
(global-set-key (kbd "C-c g") 'counsel-git)
(global-set-key (kbd "C-c j") 'counsel-git-grep)
(global-set-key (kbd "C-c k") 'counsel-ag)
(global-set-key (kbd "C-x l") 'counsel-locate)
(global-set-key (kbd "C-S-o") 'counsel-rhythmbox)
:config
(ivy-mode))
(define-key ivy-minibuffer-map (kbd "C-j") 'ivy-immediate-done)
#+end_src
** Magit
#+begin_src emacs-lisp
(use-package magit)
#+end_src
** IRC
#+begin_src emacs-lisp
(setq
erc-nick "prestonpan"
erc-user-full-name "Preston Pan")
(defun prestonpan ()
(interactive)
(erc-tls :server "nullring.xyz"
:port "6697"))
#+end_src
** Matrix
#+begin_src emacs-lisp
(defun matrix-org ()
(interactive)
(ement-connect :uri-prefix "http://localhost:8009"))
#+end_src
** LLMs
#+begin_src emacs-lisp
(use-package gptel
:init
(setq gptel-default-mode 'org-mode)
(setq-default
gptel-model "zephyr:latest"
gptel-backend (gptel-make-ollama "Ollama"
:host "localhost:11434"
:stream t
:models '("zephyr:latest"))))
#+end_src
** Keybindings
#+begin_src emacs-lisp
(use-package general
:config
(general-create-definer leader-key
:prefix "SPC")
(leader-key 'normal
"o a" '(org-agenda :wk "Open agenda")
"o c" '(org-capture :wk "Capture")
"n j j" '(org-journal-new-entry :wk "Make new journal entry")
"n r f" '(org-roam-node-find :wk "Find roam node")
"n r i" '(org-roam-node-insert :wk "Insert roam node")
"n r a" '(org-roam-alias-add :wk "Add alias to org roam node")
"n r g" '(org-roam-graph :wk "Graph roam database")
"r s s" '(elfeed :wk "rss feed")
"." '(counsel-find-file :wk "find file")
"g /" '(magit-dispatch :wk "git commands")
"g P" '(magit-push :wk "git push")
"g c" '(magit-commit :wk "git commit")
"g p" '(magit-pull :wk "Pull from git")
"g s" '(magit-status :wk "Change status of files")
"o t" '(vterm :wk "Terminal")
"o e" '(eshell :wk "Elisp Interpreter")
"o m" '(mu4e :wk "Email")
"e w w" '(eww :wk "web browser")
"e c c" '(ellama-chat :wk "Chat with Ollama")
"e a b" '(ellama-ask-about :wk "Ask Ollama")
"e s" '(ellama-summarize :wk "Summarize text with Ollama")
"e c r" '(ellama-code-review :wk "Review code with Ollama")
"e c C" '(ellama-code-complete :wk "Complete code with Ollama")
"e c a" '(ellama-code-add :wk "Add code with Ollama")
"e c e" '(ellama-code-edit :wk "Edit code with Ollama")
"e w i" '(ellama-improve-wording :wk "Improve wording with Ollama")
"e g i" '(ellama-improve-grammar :wk "Improve grammar with Ollama")
"g s" '(gptel-send :wk "Send to Ollama")
"g e" '(gptel :wk "Ollama interface")
"p w" '(ivy-pass :wk "Password manager interface")
"m P p" '(org-publish :wk "Publish website components")
"s e" '(sudo-edit :wk "Edit file with sudo")
"m m" '(emms :wk "Music player")
"m l" '(lyrics-fetcher-show-lyrics :wk "Music lyrics")
"o p" '(treemacs :wk "Project Drawer")
"o P" '(treemacs-projectile :wk "Import Projectile project to treemacs")
"f f" '(eglot-format :wk "Format code buffer")
"i c" '(prestonpan :wk "Connect to my IRC server")
"h m" '(woman :wk "Manual")
"h i" '(info :wk "Info")
"s m" '(proced :wk "System Manager")
"l p" '(list-processes :wk "List Emacs Processes")
"m I" '(org-id-get-create :wk "Make org id")
"y n s" '(yas-new-snippet :wk "Create new snippet")
"u w" '((lambda () (interactive) (shell-command "rsync -azvP ~/website_html/ root@nullring.xyz:/usr/share/nginx/ret2pop/")) :wk "rsync website update")
"h r r" '(lambda () (interactive) (org-babel-load-file (expand-file-name "~/org/website/config/emacs.org")))
))
#+end_src
** RSS Feed
I use really simple syndication (RSS) in order to read news. As a result, I use
elfeed to fetch feeds found on my website:
#+begin_src emacs-lisp
(use-package elfeed
:init
(add-hook 'elfeed-search-mode-hook #'elfeed-update)
(setq elfeed-search-filter "@1-month-ago +unread"))
(use-package elfeed-org
:init
(setq rmh-elfeed-org-files '("~/org/website/config/elfeed.org"))
:config
(elfeed-org))
#+end_src
** Project Drawer
#+begin_src emacs-lisp
(use-package treemacs)
(use-package treemacs-evil
:after (treemacs evil))
(use-package treemacs-projectile
:after (treemacs projectile))
(use-package treemacs-magit
:after (treemacs magit))
#+end_src
** Eww
Used only for the purpose of viewing RSS feed items in emacs if I can, only resorting
to Chromium if I have to:
#+begin_src emacs-lisp
(setq search-engines
'(
(("google" "g") "https://google.com/search?q=%s")
(("duckduckgo" "d" "ddg") "https://duckduckgo.com/?q=%s")
(("rfc" "r") "https://www.rfc-editor.org/rfc/rfc%s.txt")
(("rfc-kw" "rk") "https://www.rfc-editor.org/search/rfc_search_detail.php?title=%s")))
(setq search-engine-default "google")
(setq eww-search-prefix "https://google.com/search?q=")
(setq browse-url-secondary-browser-function 'browse-url-generic browse-url-generic-program "chromium")
(setq browse-url-browser-function 'eww-browse-url)
(add-hook 'eww-mode-hook
(lambda () (local-set-key (kbd "y Y") #'eww-copy-page-url)))
#+end_src
** Org Roam
For all my mathematics and programming notes:
#+begin_src emacs-lisp
(use-package org-roam
:init
(setq org-roam-db-update-on-save t)
(setq org-roam-graph-viewer "chromium")
(setq org-roam-directory (file-truename "~/org/website/mindmap"))
(setq org-roam-capture-templates '(("d" "default" plain "%?"
:target (file+head "${title}.org"
"#+title: ${title}\n#+author: Preston Pan\n#+html_head: \n#+html_head: \n#+html_head: \n#+options: broken-links:t")
:unnarrowed t)))
:config
(org-roam-db-autosync-mode))
(use-package org-roam-ui
:after org-roam
:hook (after-init . org-roam-ui-mode)
:config
(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 t))
#+end_src
** Pinentry
Set up pinentry so that I can use emacs as my pinentry frontend:
#+begin_src emacs-lisp
(use-package pinentry
:init (setq epa-pinentry-mode `loopback)
:config (pinentry-start))
#+end_src
** LaTeX
Make LaTeX a litle better:
#+begin_src emacs-lisp
(setq TeX-PDF-mode t)
(setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5))
(setq org-return-follows-link t)
;; (use-package latex-preview-pane
;; :config
;; (latex-preview-pane-enable))
#+end_src
** Email
Email in emacs can be done with Mu4e.
#+begin_src emacs-lisp
;; SMTP settings:
(setq user-mail-address "preston@nullring.xyz")
(setq user-full-name "Preston Pan")
(setq sendmail-program "msmtp"
send-mail-function 'smtpmail-send-it
message-sendmail-f-is-evil t
message-sendmail-extra-arguments '("--read-envelope-from")
message-send-mail-function 'message-send-mail-with-sendmail)
(require 'smtpmail)
(use-package mu4e
:init
(setq mu4e-drafts-folder "/Drafts")
(setq mu4e-sent-folder "/Sent")
(setq mu4e-trash-folder "/Trash")
(setq mu4e-attachment-dir "~/Downloads")
(setq mu4e-view-show-addresses 't)
(setq mu4e-confirm-quit nil)
(setq message-kill-buffer-on-exit t)
(setq mu4e-compose-dont-reply-to-self t)
(setq mu4e-change-filenames-when-moving t)
(setq mu4e-get-mail-command "mbsync prestonpan")
(setq mu4e-compose-reply-ignore-address '("no-?reply" "ret2pop@gmail.com"))
(setq mu4e-html2text-command "w3m -T text/html" ; how to hanfle html-formatted emails
mu4e-update-interval 300 ; seconds between each mail retrieval
mu4e-headers-auto-update t ; avoid to type `g' to update
mu4e-view-show-images t ; show images in the view buffer
mu4e-compose-signature-auto-include nil ; I don't want a message signature
mu4e-use-fancy-chars t))
#+end_src
** Password Manager
I use ~pass~ in order to manage my passwords on linux, and this is an ivy frontend for it:
#+begin_src emacs-lisp
(use-package ivy-pass)
#+end_src
** Music
Set up emms in order to play music from my music directory:
#+begin_src emacs-lisp
(use-package emms
:init
(emms-all)
(setq emms-source-file-default-directory (expand-file-name "~/music/"))
(setq emms-player-mpd-music-directory (expand-file-name "~/music/"))
(setq emms-player-mpd-server-name "localhost")
(setq emms-player-mpd-server-port "6600")
(setq emms-player-list '(emms-player-mpd))
(add-to-list 'emms-info-functions 'emms-info-mpd)
(add-to-list 'emms-player-list 'emms-player-mpd)
:config
(emms-player-mpd-connect))
#+end_src
** Stem
My own programming language.
#+begin_src emacs-lisp
(use-package stem-mode)
(add-to-list 'auto-mode-alist '("\\.stem\\'" . stem-mode))
#+end_src
** Server
So that emacsclient can connect.
#+begin_src emacs-lisp
(server-start)
#+end_src