commit 59ae461dd97d51a8d20a9c6a20e74eedf729b621 Author: quantumish <freifeld.david@gmail.com> Date: Thu, 6 Aug 2026 21:20:41 -0700 2026 initial commit oops, no history! Diffstat:
| A | early-init.el | | | 40 | ++++++++++++++++++++++++++++++++++++++++ |
| A | hacks/gentoo.el | | | 173 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | init.el | | | 24 | ++++++++++++++++++++++++ |
| A | modules/firefox.el | | | 172 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | modules/ide.el | | | 98 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | modules/introspection.el | | | 22 | ++++++++++++++++++++++ |
| A | modules/music.el | | | 36 | ++++++++++++++++++++++++++++++++++++ |
| A | modules/org.el | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | modules/packages.el | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
| A | modules/pdf.el | | | 17 | +++++++++++++++++ |
| A | modules/system.el | | | 163 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | modules/themeing.el | | | 36 | ++++++++++++++++++++++++++++++++++++ |
| A | modules/tweaks.el | | | 74 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | modules/vompec.el | | | 76 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | readme.org | | | 4 | ++++ |
15 files changed, 1039 insertions(+), 0 deletions(-)
diff --git a/early-init.el b/early-init.el @@ -0,0 +1,40 @@ +;; -*- lexical-binding: t; -*- + +;; make garbage collector less invasive +(setq gc-cons-threshold most-positive-fixnum + gc-cons-percentage 0.6) + +(setq package-enable-at-startup nil) + +(defvar default-gc-cons-threshold 16777216 ; 16mb + "Default GC cons threshold to use.") + +(push '(menu-bar-lines . 0) default-frame-alist) +(push '(tool-bar-lines . 0) default-frame-alist) +(push '(left-fringe . 0) default-frame-alist) +(push '(right-fringe . 0) default-frame-alist) +(push '(vertical-scroll-bars) default-frame-alist) +;(push '(font . "UW Ttyp0-10") default-frame-alist) +(push '(mouse-color . "#f6e5db") default-frame-alist) +(push '(bottom-divider-width . 20) default-frame-alist) +(push '(right-divider-width . 20) default-frame-alist) + +(setq frame-inhibit-implied-resize t) + +(setq warning-minimum-level :error) + +(setq default-file-name-handler-alist file-name-handler-alist + file-name-handler-alist nil) + +(setq initial-scratch-message nil) +(setq inhibit-startup-screen t) +(defun display-startup-echo-area-message ()) + +(add-hook 'emacs-startup-hook + (lambda (&rest _) + (setq gc-cons-threshold default-gc-cons-threshold + gc-cons-percentage 0.1 + file-name-handler-alist default-file-name-handler-alist) + + ;; delete no longer necessary startup variable + (makunbound 'default-file-name-handler-alist))) diff --git a/hacks/gentoo.el b/hacks/gentoo.el @@ -0,0 +1,173 @@ +;;; gentoo.el --- Portage interface for Emacs. + +;;; Commentary: +;; +;; This is a minimal package that I threw together late one night to let me +;; use most of what I need from Portage from within Emacs. It's pretty nice! + +(require 'dash) +(require 'eat) +(require 'consult) + +;;; Code: + +(defvar gentoo--eix-format + "<category>/<name>|<description>|{havebest}<bestversion:NAMEVERSION>{else}none\n{}" + "Format string passed to eix --format flag.") + +(defvar gentoo-display-padding 60 + "Padding between package names and info.") +(defvar gentoo-gap-padding 10 + "Padding between various pieces of package info.") +(defvar gentoo-emerge-ask nil + "Whether or not to run emerge with --ask.") +(defvar gentoo-portage-use-file "/etc/portage/package.use" + "File path of the portage package.use file.") +(defvar gentoo-portage-accept-file "/etc/portage/package.accept_keywords/xcape" + "File path of the portage package.accept_keywords file.") +(defvar gentoo-cmd-display-type 'eat + "What to use to run commands. +Possible values are \\='eat (for using the EAT package) or +\\='compile (for using \\[compile]).") + +(defgroup gentoo nil + "Use portage from Emacs." + :group 'convenience + :group 'minibuffer + :group 'consult + :prefix "gentoo-") + +(defface gentoo-use-stop + '((t (:inherit default :foreground "green"))) + "Face to display DONE option.") + +(defface gentoo-use-enabled + '((t (:inherit default :foreground "red"))) + "Face to display enabled USE flags.") + +(defface gentoo-use-disabled + '((t (:inherit default :foreground "cyan"))) + "Face to display disabled USE flags.") + +(defun gentoo--table-from-lists (keypairs) + "Create a hash table given list of kv pairs KEYPAIRS." + (let ((table (make-hash-table :test #'equal))) + (dolist (x keypairs) + (puthash (car x) (cdr x) table)) + table)) + +(defun gentoo--build-package-dict (&optional installed) + "Create hash table of possibly INSTALLED packages by querying eix." + (let* ((cmd (format "EIX_LIMIT_COMPACT=0 eix -Sc%s --format '%s'" + (if installed "I" "") ;; HACK + gentoo--eix-format)) + (pkgs (butlast (split-string (shell-command-to-string cmd) "\n")))) + (gentoo--table-from-lists + (--filter (> (length it) 1) (--map (split-string it "|") pkgs))))) + +(defun gentoo--query-packages-annotate (cand info) + "Annotator function for `consult--read' for package CAND and its INFO." + ;; TODO fix this gross hack of a meta format string + (let ((fmtstr (format "%%%ds%%%ds%%s" (- gentoo-display-padding (length cand)) gentoo-gap-padding))) + (format fmtstr (cadr info) "" (car info)))) + +(defun gentoo--query-packages (&optional &key prompt &key installed) + "Interactively query possibly INSTALLED gentoo packages with a given PROMPT." + (let* ((table (gentoo--build-package-dict installed)) + (sel + (consult--read + table + :prompt (if prompt prompt "Select: ") + :annotate (lambda (x) (gentoo--query-packages-annotate x (gethash x table)))))) + (list sel (string= (cadr (gethash sel table)) "none")))) + +(defun gentoo--get-useflags (pkg) + "Get USE flags for Gentoo package PKG using equery." + (let ((uses (split-string (shell-command-to-string (format "equery -N -C uses %s" pkg)) "\n")) + (rgx (rx (seq " " (group (or "+" "-")) " " (or "+" "-") " " + (group (one-or-more (or alnum "_" "-" "/"))) + (zero-or-more blank) ": " (group (zero-or-more any)))))) + (gentoo--table-from-lists + (-non-nil (-map (lambda (x) + (if (string-match rgx x) + (list (match-string 2 x) (equal (match-string 1 x) "+") (match-string 3 x)))) + uses))))) + +(defun gentoo--query-useflags-annotate (cand info) + "Annotator function for `consult--read' for USE flag CAND and its INFO." + ;; TODO fix this cursed evil hack of a meta format string + (let ((fmtstr (format "%%%ds%%s" (- gentoo-display-padding (length cand))))) + (if (not info) (list (propertize cand 'face 'gentoo-use-stop) "" "") + (list (if (car info) + (propertize cand 'face 'gentoo-use-enabled) + (propertize cand 'face 'gentoo-use-disabled)) + (if (car info) "+" "-") (format fmtstr "" (cadr info)))))) + +(defun gentoo--ask-useflags (table pkg) + "Interactively query USE flags of PKG given TABLE of USE flags." + (let ((x (consult--read + (cons "_DONE_" (map-keys table)) + :prompt "Toggle USE flags: " + :annotate (lambda (x) (gentoo--query-useflags-annotate x (gethash x table)))))) + (unless (string= x "_DONE_") + (puthash x (list (not (car (gethash x table))) (cadr (gethash x table))) table) + (gentoo--ask-useflags table pkg)))) + +(defun gentoo--set-useflags (pkg) + "Interactively query and set USE flags of package PKG." + (let* ((flags (gentoo--get-useflags pkg)) + (default (copy-hash-table flags))) + (if (> (hash-table-count flags) 0) (gentoo--ask-useflags flags pkg)) + (let* ((diffs (-non-nil (map-apply (lambda (k v) + (if (not (eq (car (gethash k default)) (car v))) + (concat (if (car v) "" "-") k))) flags))) + (path (concat "/sudo::" gentoo-portage-use-file)) + (rgx (rx-to-string `(seq ,pkg (zero-or-more any)) t)) + (defn (format "%s %s" pkg (mapconcat (lambda (x) x) diffs " ")))) + (with-temp-buffer + (insert-file-contents path) + (if (string-match rgx (buffer-string)) + (replace-regexp rgx defn) + (progn (end-of-buffer) (insert (concat defn "\n")))) + (write-region (point-min) (point-max) path))))) + +(defun gentoo--unmask-pkg (pkg) + "Unmask package PKG by editing file at `gentoo-portage-accept-file'." + (if (y-or-n-p (format "Unmask %s?" pkg)) + (write-region (concat pkg "\n") + nil (concat "/sudo::" gentoo-portage-accept-file) 'append))) + +(defun gentoo--run-cmd (command &optional withsudo) + "Run command COMMAND possibly WITHSUDO using method in `gentoo-cmd-display-type'." + (let ((default-directory (if withsudo "/sudo::" default-directory)) + (inhibit-read-only t) + (eat-buffer-name "*emerge*") + (compilation-buffer-name-function (lambda (_) "*emerge*")) + (compilation-environment (if (eq gentoo-cmd-display-type 'compile) '("NO_COLOR=1")))) + (if (eq gentoo-cmd-display-type 'eat) + (eat command) (compile command)))) + +(defun gentoo-install-pkg () + "Install a package via emerge and set its USE flags." + (interactive) + (let* ((pkgi (gentoo--query-packages)) + (pkg (car pkgi)) (masked (cadr pkgi))) + (when (or (not masked) (and masked (gentoo--unmask-pkg pkg))) + (gentoo--set-useflags pkg) + (gentoo--run-cmd (format "emerge %s %s" (if gentoo-emerge-ask "--ask" "") pkg) t)))) + +(defun gentoo-depclean () + "Run emerge --depclean and remove unused packages." + (interactive) + (gentoo--run-cmd "emerge --depclean" t)) + +(defun gentoo-deselect () + "Interactively query a package to remove from @world." + (interactive) + (let ((pkg (car (gentoo--query-packages :prompt "Deselect: " :installed t)))) + (gentoo--run-cmd (format "emerge --deselect %s" pkg) t))) + +(provide 'gentoo) +;;; gentoo.el ends here + + diff --git a/init.el b/init.el @@ -0,0 +1,24 @@ +;; -*- lexical-binding: t; -*- + +(defun load-module (name) + (let* ((filename (concat name ".el")) + (path (file-name-concat user-emacs-directory "modules" filename)) + (fullpath (expand-file-name path))) + (load-file fullpath))) + +(load-module "packages") +(load-module "tweaks") + +(server-start) + +(when (eq system-type 'gnu/linux) + (load-module "system")) + +(load-module "themeing") +(load-module "vompec") +(load-module "introspection") +(load-module "ide") +(load-module "org") +(load-module "firefox") +(load-module "pdf") +(load-module "music") diff --git a/modules/firefox.el b/modules/firefox.el @@ -0,0 +1,172 @@ +(use-package ini) +(require 'sqlite) + +(defvar firefox-app-name "firefox-esr<" + "String that firefox window buffers will be prefixed with.") + +(defvar firefox--title-name-regex + ".*<\\([^>]*\\)>" + "Regex for extracting the title of a Firefox window from its buffer name.") + +(defvar firefox-extract-title-name + (lambda (string) + (let ((result '()) + (start 0)) + (while (string-match "<\\([^>]*\\)>" string start) + (push (match-string 1 string) result) + (setq start (match-end 0))) + (string-join (nreverse result) " - "))) + "Function used to the title name of a Firefox window from its buffer name.") + +(defvar firefox-mozilla-folder-path + (expand-file-name "~/.mozilla") + "Location of the .mozilla folder with Firefox profiles info.") + +(defun firefox--profiles-info () + (let* ((ini-path (expand-file-name "~/.mozilla/firefox/profiles.ini")) + (info (ini-decode ini-path)) + (raw-cands + (mapcar (lambda (x) (cons (cdr (assoc "Name" (cdr x))) + (cdr (assoc "Path" (cdr x))))) + info))) + (seq-filter 'car raw-cands))) + +(defvar firefox-current-profile + (assoc "default-esr" (firefox--profiles-info)) + "The name and folder name of the selected Firefox profile.") + +(defun firefox--profile-path () + "Get the file path of the the current Firefox profile's directory." + (expand-file-name (file-name-concat firefox-mozilla-folder-path + "firefox" + (cdr firefox-current-profile)))) + +(defun firefox-switch-profile () + "Switch the selected Firefox profile." + (interactive) + (let* ((cands (firefox--profiles-info)) + (chosen (completing-read "Profile: " cands nil nil))) + (setq firefox-current-profile (assoc chosen cands)))) + +(defvar firefox-command + "env MOZ_USE_XINPUT2=1 firefox-esr --new-window" + "Shell command to run to launch Firefox.") + +(defvar firefox-search-format + "https://www.google.com/search?q=%s" + "Format string to use for searching non-URL queries.") + +(defvar firefox-title-postprocessor + (lambda (x) (car (split-string x " — "))) + "Transformation function to run on the title of a page.") + +(defvar firefox-title-width 50 + "Number of characters used to display the page title.") + +(defvar firefox-url-width 100 + "Number of characters used to display the page URL.") + +(defvar firefox--known-tlds '("com" "org" "ai" "sh" "bike" "app") + "TLDs used for detecting almost-URLS. TODO this is a terrible solution!") + +(defvar firefox--places-temp-filename "places.tmp.sqlite" + "Name for temporary copy of the places.sqlite file.") + +(defvar firefox--places-data nil + "Cached browsing history completion candidates.") + +(add-to-list 'savehist-additional-variables 'firefox--places-data) + +(defvar firefox-history-sql-where + "last_visit_date IS NOT NULL AND (visit_count > 1 or unixepoch() - last_visit < 2628000)" + "WHERE expression to use when querying history.") + +(defvar firefox-history-sql-order-by + "frecency DESC" + "ORDER BY expression to use when querying history.") + +(defun firefox--get-browsing-history () + "Copy the places.sqlite file and query to find browsing history sorted by frecency." + (let* ((profile-path (firefox--profile-path)) + (temp-path (file-name-concat (firefox--profile-path) firefox--places-temp-filename))) + (copy-file (file-name-concat (firefox--profile-path) "places.sqlite") temp-path t) + (sqlite-execute + (sqlite-open temp-path) + (format + "SELECT url, title, visit_count, frecency, + datetime(last_visit_date/1000000, 'unixepoch') as last_visit + FROM moz_places + WHERE %s ORDER BY %s;" + firefox-history-sql-where + firefox-history-sql-order-by)))) + +(defun firefox--set-data-cache () + "Loads latest browsing data into `firefox--places-data`." + (setq firefox--places-data (firefox--get-browsing-history)) + (dolist (row firefox--places-data) + (if (cadr row) + (setf (cadr row) (funcall firefox-title-postprocessor (cadr row))))) + (setq firefox--places-data (mapcar 'firefox--make-completion firefox--places-data))) + +(defun firefox--urlify (input) + "Coerces INPUT to a URL by either adding a https prefix or converting to a search query." + (url-encode-url + (let ((url (url-generic-parse-url input))) + (cond ((url-type url) input) + ((member (car (last (split-string input "\\."))) firefox--known-tlds) (concat "https://" input)) + (t (format firefox-search-format (url-hexify-string input))))))) + +(defun firefox--make-completion (x) + "Generate a completion string for a given row of the table." + ;; 'invisible trick is taken from ready-player-mode. + ;; Really we should just use annotation-function properly but I want searchable URLs... + (if (cadr x) + (concat (truncate-string-to-width (cadr x) firefox-title-width nil ?\s "…") + " " + (truncate-string-to-width (propertize (car x) 'face 'font-lock-comment-face) + firefox-url-width nil ?\s "…") + (propertize (concat "firefox-emacs-url:" (car x)) 'invisible t)) + (concat (truncate-string-to-width (car x) firefox-url-width nil ?\s "…") + (propertize (concat "firefox-emacs-url:" (car x)) 'invisible t)))) + +(defun firefox--get-url-from-completion (x) + "Extract the URL from the completion string." + (firefox--urlify (car (last (split-string x "firefox-emacs-url:"))))) + +(defun firefox-new-tab () + "Open a new firefox tab, prompting with history." + (interactive) + (when (eq firefox--places-data nil) + (firefox--set-data-cache)) + (let ((vertico-preselect 'prompt) + (vertico-sort-function nil) + (cands firefox--places-data)) + (call-process-shell-command + (format "%s \"%s\"" + firefox-command + (firefox--get-url-from-completion + (completing-read "New tab: " cands nil nil))) + nil 0))) + +;; Personal (less package-y) configuration + +(defvar time-wasting-websites-keywords + '("youtube" + "lobste.rs" + "news.ycombinator.com" + "kittensgame.com" + "hntoplinks.com" + "hn.algolia.com") + "Websites I don't want suggested to me.") + +(defvar direct-websites-keywords + '("discord.com" + "discordapp.com" + "google.com/search") + "Websites that shouldn't be opened via firefox.el") + +(setq firefox-history-sql-where + (seq-reduce 'concat + (mapcar (lambda (x) (format " AND INSTR(url, \"%s\") == 0" x)) + (append time-wasting-websites-keywords direct-websites-keywords)) + firefox-history-sql-where)) diff --git a/modules/ide.el b/modules/ide.el @@ -0,0 +1,98 @@ +;; -*- lexical-binding: t; -*- +;;; Configuration for IDE-related functionality. + +(setq-default tab-width 4) +(setq indent-tabs-mode nil) +(add-to-list 'exec-path (format "%s/go/bin" (getenv "HOME"))) +(add-to-list 'exec-path (format "%s/.cargo/bin" (getenv "HOME"))) +(add-to-list 'exec-path "/usr/local/go/bin") + +(c-set-offset 'innamespace 0) + +(use-package deadgrep) +(use-package transient) +(use-package magit) + +(use-package hl-todo + :config + (setq hl-todo-keyword-faces + `(("TODO" . ,(doom-color 'green)) + ("FIXME" . ,(doom-color 'red)) + ("DEBUG" . ,(doom-color 'magenta)) + ("HACK" . ,(doom-color 'violet)) + ("NOTE" . ,(doom-color 'cyan)))) + :hook (prog-mode . hl-todo-mode)) + +(use-package consult-todo) + +(use-package haskell-mode) +(use-package go-mode) +(use-package sml-mode) +(use-package rust-mode) + +(use-package lsp-mode + :config + ;; Disable annoying headerline + (setq lsp-headerline-breadcrumb-enable nil) + ;; Don't show unneeded function info in completions + (setq lsp-completion-show-detail nil) + ;; Disable annoying autoformatting! + (setq-default lsp-enable-indentation nil) + (setq-default lsp-enable-on-type-formatting nil) + :commands lsp + ;; Add languages of your choice! + :hook ((c++-mode . lsp) + (python-mode . lsp) + (typescript-mode . lsp) + (rust-mode . lsp) + (lsp-mode . (lambda () (lsp-lens-mode 0))))) + +(use-package lsp-ui + :after lsp + :config + (setq lsp-ui-doc-delay 5) + (eval `(set-face-attribute 'lsp-ui-doc-background nil :background ,(doom-darken 'bg .2))) + :hook ((flycheck-mode . lsp-ui-mode) ;; HACK + (lsp-mode . lsp-ui-mode))) + +(use-package company + :config + (setq company-idle-delay 0) + (setq company-tooltip-maximum-width 40) + :hook + (lsp-mode . company-mode)) + + +(use-package eros + :config + (defun slime-eval-last-expression-eros () + (interactive) + (cl-destructuring-bind (output value) + (slime-eval `(swank:eval-and-grab-output ,(slime-last-expression))) + (eros--make-result-overlay (concat output value) + :where (point) + :duration eros-eval-result-duration)))) + +(use-package slime + :after eros + :config + (setq inferior-lisp-program "sbcl") + + :bind (:map slime-mode-map + ("C-x C-e" . slime-eval-last-expression-eros)) + :hook ((lisp-mode . slime) + (lisp-mode . slime-mode))) + +(use-package eat :ensure + (:host codeberg + :repo "akib/emacs-eat" + :files ("*.el" ("term" "term/*.el") "*.texi" + "*.ti" ("terminfo/e" "terminfo/e/*") + ("terminfo/65" "terminfo/65/*") + ("integration" "integration/*") + (:exclude ".dir-locals.el" "*-tests.el"))) + :config + (setq eat-shell-prompt-annotation-position 'right-margin) + (setq eat-shell-prompt-annotation-failure-margin-indicator "⛌ ") + (setq eat-shell-prompt-annotation-success-margin-indicator "✓ ") + (setq eat-shell-prompt-annotation-running-margin-indicator "⋯ ")) diff --git a/modules/introspection.el b/modules/introspection.el @@ -0,0 +1,22 @@ +;; -*- lexical-binding: t; -*- +;;; Configuration for things useful for introspecting Emacs. + +(use-package helpful + :init + ;; Advise describe-style functions so that Helpful appears no matter what + (advice-add 'describe-function :override #'helpful-function) + (advice-add 'describe-variable :override #'helpful-variable) + (advice-add 'describe-command :override #'helpful-callable) + (advice-add 'describe-key :override #'helpful-key) + (advice-add 'describe-symbol :override #'helpful-symbol) + + :bind + (("C-h f" . helpful-function) + ("C-h v" . helpful-variable) + ("C-h k" . helpful-key) + ("C-c C-d" . helpful-at-point) + ("C-h F" . helpful-function) + ("C-h C" . helpful-command))) + +(use-package which-key + :init (which-key-mode)) diff --git a/modules/music.el b/modules/music.el @@ -0,0 +1,36 @@ +;; -*- lexical-binding: t; -*- +;;; Music-related configuration. + +(use-package ready-player :ensure t + :init (ready-player-mode) + :config + + (defun now-playing () + (let ((out (ignore-errors + (with-current-buffer (ready-player--active-buffer) + (let ((title (ready-player--row-value + (ready-player--make-metadata-rows ready-player--metadata) + "Title:")) + (artist (ready-player--row-value + (ready-player--make-metadata-rows ready-player--metadata) + "Artist:"))) + (cond ((and title artist) (format "%s - %s" title artist)) + (title title) + (t ""))))))) + (if out out ""))) + + (setq ready-player-ask-for-project-sustainability nil) + (setq ready-player-my-media-collection-location (expand-file-name "~/soul")) + + :bind + (("s-<left>" . ready-player-seek-backward) + ("s-<right>" . ready-player-seek-forward) + ("C-c m n" . ready-player-next) + ("C-c m p" . ready-player-previous) + ("C-c m SPC" . ready-player-toggle-play-stop) + ("C-c m /" . ready-player-search) + ("C-c m i" . ready-player-show-info) + ("<XF86AudioNext>" . ready-player-next) + ("<XF86AudioPrev>" . ready-player-previous) + ("<XF86AudioPlay>" . ready-player-toggle-play-stop) + ("<XF86AudioPause>" . ready-player-toggle-play-stop))) diff --git a/modules/org.el b/modules/org.el @@ -0,0 +1,57 @@ +;; -*- lexical-binding: t; -*- +;;; Configuration for Org mode related functionality + +(use-package org :ensure nil + :config + ;; visual settings + (set-face-attribute 'org-document-title nil + :height 2.0 :weight 'bold) + (setq org-fontify-quote-and-verse-blocks t) + (setq org-fontify-emphasized-text t) + (setq org-hide-emphasis-markers t) + (setq org-ellipsis " ") + (setq org-hide-leading-stars t) + (setq org-latex-create-formula-image-program 'dvisvgm) + (setq org-highlight-latex-and-related '(native)) + (setq org-todo-keywords '((sequence "TODO(t)" "WAIT(w)" "|" "DONE(d)" "NOPE(n)"))) + ;; export settings + (setq org-html-postamble nil) + (setq org-export-with-section-numbers nil) + (setq org-export-with-toc nil) + (setq org-html-htmlize-output-type 'css) + (setq org-latex-default-packages-alist + '(("AUTO" "inputenc" t ("pdflatex")) + ("T1" "fontenc" t ("pdflatex")) + (#1="" "graphicx" t) + (#1# "longtable" nil) + (#1# "wrapfig" nil) + (#1# "rotating" nil) + ("normalem" "ulem" t) + (#1# "amsmath" t) + (#1# "amssymb" t) + (#1# "capt-of" nil) + ("colorlinks" "hyperref" nil) + (#1# "geometry" nil) + ("parfill" "parskip" nil))) + :hook (org-mode . org-indent-mode)) + +;; Auto-toggle emphasis +(use-package org-appear + :ensure (:host github :repo "awth13/org-appear") + :hook (org-mode . org-appear-mode)) + +;; Natural bulleted lists +(use-package org-autolist + :hook (org-mode . org-autolist-mode)) + +(use-package xenops + :hook (org-mode . xenops-mode) + :config (defun xenops-handle-paste ())) + +(use-package yasnippet + :hook ((prog-mode . yas-minor-mode) + (org-mode . yas-minor-mode))) + +(use-package olivetti + :config (setq-default olivetti-width 120) + :hook (org-mode . olivetti-mode)) diff --git a/modules/packages.el b/modules/packages.el @@ -0,0 +1,47 @@ +;; -*- lexical-binding: t; -*- +;;; Meta-configuration and package manager setup. + +(defvar elpaca-installer-version 0.7) +(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory)) +(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory)) +(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory)) +(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git" + :ref nil :depth 1 + :files (:defaults "elpaca-test.el" (:exclude "extensions")) + :build (:not elpaca--activate-package))) +(let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory)) + (build (expand-file-name "elpaca/" elpaca-builds-directory)) + (order (cdr elpaca-order)) + (default-directory repo)) + (add-to-list 'load-path (if (file-exists-p build) build repo)) + (unless (file-exists-p repo) + (make-directory repo t) + (when (< emacs-major-version 28) (require 'subr-x)) + (condition-case-unless-debug err + (if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*")) + ((zerop (apply #'call-process `("git" nil ,buffer t "clone" + ,@(when-let* ((depth (plist-get order :depth))) + (list (format "--depth=%d" depth) "--no-single-branch")) + ,(plist-get order :repo) ,repo)))) + ((zerop (call-process "git" nil buffer t "checkout" + (or (plist-get order :ref) "--")))) + (emacs (concat invocation-directory invocation-name)) + ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch" + "--eval" "(byte-recompile-directory \".\" 0 'force)"))) + ((require 'elpaca)) + ((elpaca-generate-autoloads "elpaca" repo))) + (progn (message "%s" (buffer-string)) (kill-buffer buffer)) + (error "%s" (with-current-buffer buffer (buffer-string)))) + ((error) (warn "%s" err) (delete-directory repo 'recursive)))) + (unless (require 'elpaca-autoloads nil t) + (require 'elpaca) + (elpaca-generate-autoloads "elpaca" repo) + (load "./elpaca-autoloads"))) +(add-hook 'after-init-hook #'elpaca-process-queues) +(elpaca `(,@elpaca-order)) + +(setq use-package-always-defer t) +(setq use-package-always-ensure t) + +(elpaca elpaca-use-package + (elpaca-use-package-mode)) diff --git a/modules/pdf.el b/modules/pdf.el @@ -0,0 +1,17 @@ +;; -*- lexical-binding: t; -*- +;;; Configuration for PDF related functionality + +(use-package pdf-tools + :init (pdf-loader-install) + :config + (defun my/pdf-fit () + "Fit the window to the PDF's width and the PDF to the window's height. + From https://emacs.stackexchange.com/questions/60537/fit-window-to-pdf-upon-loading" + (interactive) + (let ((W (- (car (pdf-view-image-size)) (window-pixel-width)))) + (window-resize (get-buffer-window) (+ 10 W) t nil t) + (pdf-view-fit-height-to-window))) + :hook + ((pdf-view-mode . pdf-view-midnight-minor-mode) + (pdf-view-mode . hide-mode-line-mode) + (pdf-view-after-change-page . my/pdf-fit))) diff --git a/modules/system.el b/modules/system.el @@ -0,0 +1,163 @@ +;; -*- lexical-binding: t; -*- +;;; Configuration for system related functionality. + +(use-package exwm + :config + (setq exwm-workspace-number 2) + (setq exwm-input-global-keys `(([?\s-r] . exwm-reset))) + (setq exwm-input-simulation-keys + '(([?\C-a] . [home]) + ([?\C-e] . [end]) + ([?\C-s] . ?\C-f) + ([?\M-w] . ?\C-c) + ([?\C-y] . ?\C-v) + ([?\C-x?\C-s] . ?\C-s) + ([?\M-v] . [prior]) + ([?\C-v] . [next]) + ([?\C-d] . [delete]) + ([?\C-k] . [S-end delete]))) + + (setq exwm-layout-show-all-buffers t) + (setq exwm-workspace-show-all-buffers t) + + (defun b3n-exwm-set-buffer-name () + (if (and (stringp exwm-title) (= (length (s-split " — " exwm-title)) 3)) + (cl-destructuring-bind (title url _) (s-split " — " exwm-title) + (setq-local buffer-file-name url) + (setq-local exwm-title (concat exwm-class-name "<" title ">"))) + (setq-local exwm-title + (concat + exwm-class-name + "<" + (if (<= (length exwm-title) 50) + exwm-title + (concat (substring exwm-title 0 50) "…")) + ">"))) + + (exwm-workspace-rename-buffer exwm-title)) + + (require 'exwm-randr) + (setq exwm-randr-workspace-monitor-plist '(0 "eDP" 1 "DisplayPort-1")) + (exwm-randr-mode) + + (defun find-and-switch-to-app (app-name) + "Looks for a buffer that probably corresponds to the window of an app." + (let* ((predicate + (lambda (b) + (and (string-match-p (regexp-quote (format "%s |" app-name)) (buffer-name b)) + (eq (buffer-local-value 'major-mode b) 'exwm-mode)))) + (cands (seq-filter predicate (buffer-list)))) + (if (car cands) + (or (switch-to-buffer (car cands)) t)))) + + (defun open-discord () + "Switches to any open EXWM buffer of Discord or opens it." + (interactive) + (if (not (find-and-switch-to-app "discord")) + (call-process-shell-command + "env MOZ_USE_XINPUT2=1 firefox --new-window discord.com/app" nil 0))) + + (defun bind-exwm-key (keystr func) + (define-key override-global-map (kbd keystr) func) + (define-key exwm-mode-map (kbd keystr) func)) + + (defmacro bind-named-cmd (keystr name cmd) + "Helper macro to define a wrapper around a shell command and bind + it to a key in both the global map and the EXWM map. + + This is useful because it means that describe-key can meaningfully + describe what is bound to the key (instead of just pointing to an + anonymous function)." + `(progn + (defun ,name () + (interactive) + (call-process-shell-command ,cmd nil 0)) + (bind-exwm-key ,keystr ',name))) + + ;; TODO consider warning in the event these aren't found? + (when (executable-find "brightnessctl") + (bind-named-cmd "<XF86MonBrightnessUp>" brightness-up-10 "brightnessctl set 10%+") + (bind-named-cmd "<XF86MonBrightnessDown>" brightness-down-10 "brightnessctl set 10%-")) + + (when (executable-find "amixer") + (bind-named-cmd "<XF86AudioRaiseVolume>" volume-up-5 "amixer set Master 5%+") + (bind-named-cmd "<XF86AudioLowerVolume>" volume-down-5 "amixer set Master 5%-") + (bind-named-cmd "<XF86AudioMute>" volume-toggle "amixer set Master toggle")) + + (when (executable-find "firefox") + (bind-named-cmd "M-f" firefox-new-tab "env MOZ_USE_XINPUT2=1 firefox --new-window") + (bind-exwm-key "M-d" 'open-discord)) + + (when (executable-find "slock") + (bind-named-cmd "M-l" lock-screen "slock")) + + (when (executable-find "flameshot") + (bind-named-cmd "M-s M-s" flameshot + "flameshot gui -r | xclip -selection clipboard -t image/png")) + + ;; Emulate various DWM behaviors. + (use-package dmenu + :init (bind-exwm-key "M-p" 'dmenu)) + (bind-exwm-key "M-k" 'other-window) + (bind-exwm-key "M-j" (lambda () (interactive) (other-window -1))) + (bind-exwm-key "C-M-j" 'other-frame) + (bind-exwm-key "C-M-k" (lambda () (interactive) (other-frame -1))) + + :hook + ((exwm-update-class . b3n-exwm-set-buffer-name) + (exwm-update-title . b3n-exwm-set-buffer-name)) + :init (exwm-enable)) + +(use-package prodigy :demand t + :config + (setq prodigy-kill-process-buffer-on-stop t) + + (defun my/prodigy-define-services () + (prodigy-define-service + :name "earlyOOM" :command "earlyoom" :args '("-r" "0") + :tags '(stability) :auto-start t) + (prodigy-define-service + :name "Unclutter" :command "unclutter" + :tags '(x11) :auto-start t) + (prodigy-define-service + :name "Picom" :command "picom" + :tags '(x11) :auto-start t) + + (dolist (x (seq-filter #'stringp exwm-randr-workspace-monitor-plist)) + (prodigy-define-service + :name (format "Polybar (%s)" x) :command "polybar" + :args (list "-q" "main" "-c" (concat (getenv "HOME") "/.config/polybar/shades/config.ini")) + :env (list (list "MONITOR" x)) + :tags '(theme) :auto-start t))) + + (defun prodigy-autostart () + "Start all unstarted services with `:auto-start' set to non-nil." + (interactive) + (prodigy-with-refresh + (--each prodigy-services + (when (and (plist-get it :auto-start) + (not (prodigy-service-started-p it))) + (prodigy-start-service it))))) + + (defun prodigy-killall () + (interactive) + (prodigy-with-refresh + (--each prodigy-services (prodigy-stop-service it)))) + + :hook ((exwm-init . (lambda () (my/prodigy-define-services) (prodigy-autostart))) + (kill-emacs . prodigy-killall))) + +(use-package exwm-outer-gaps + :ensure (:host github :repo "walseb/exwm-outer-gaps") + :config + (defun exwm-outer-gaps-redraw () + "exwm-outer gaps sometimes has artifacts in the gap area. + Quickly toggling the mode on and off works forces a redraw of + the gaps and gets rid of them." + (interactive) + (exwm-outer-gaps-mode)) + (setq exwm-outer-gaps-width [25 25 25 0]) + :hook (exwm-init . exwm-outer-gaps-mode)) + +(use-package exwm-mff + :hook (exwm-init . exwm-mff-mode)) diff --git a/modules/themeing.el b/modules/themeing.el @@ -0,0 +1,36 @@ +;; -*- lexical-binding: t; -*- +;;; Configuration for appearance related things. + +(use-package doom-themes + :config + (set-face-attribute 'header-line nil :background (doom-color 'bg) :height 0.5) + (setq doom-themes-enable-bold t + doom-themes-enable-italic t)) + +(use-package ewal) +(use-package ewal-doom-themes + :init (load-theme 'ewal-doom-one t)) + +(use-package doom-modeline :config + (setq doom-modeline-height 40) + (setq doom-modeline-buffer-encoding nil) + :init (doom-modeline-mode)) + +(use-package hide-mode-line + :init + (if (featurep 'exwm) + (add-hook 'exwm-mode-hook 'hide-mode-line-mode))) + +(use-package solaire-mode + :hook (prog-mode . solaire-mode)) + +(use-package nerd-icons-completion + :config (nerd-icons-completion-mode)) + +(use-package nerd-icons-dired + :hook (dired-mode . nerd-icons-dired-mode)) + +(use-package diredfl + :config + (setq dired-listing-switches "-alh") + :hook (dired-mode . diredfl-mode)) diff --git a/modules/tweaks.el b/modules/tweaks.el @@ -0,0 +1,74 @@ +;; -*- lexical-binding: t; -*- +;;; Configuration relating to default behavior of Emacs. + +;; Make Emacs never beep. +(setq ring-bell-function #'ignore) +(setf bell-fn #'ignore) + +;; Not personally a fan of customize.el +(setq custom-file "/dev/null") +(setq package-selected-packages "/dev/null/") + +;; These keybinds suspend Emacs (in order to mimic terminal behavior). +;; This has *only* caused me trouble in GUI Emacs. +(when (display-graphic-p) + (global-unset-key (kbd "C-z")) + (global-unset-key (kbd "C-x C-z"))) + +;; Stop making backup files everywhere, put them all in one place! +(make-directory "~/.saves/" t) +(make-directory "~/.autosaves/" t) +(setq auto-save-file-name-transforms '((".*" "~/.autosaves/" t))) +(setq backup-directory-alist `(("." . "~/.saves"))) +(setq backup-by-copying t) + +;; Stop Emacs from bothering you about disabled commands. +(setq disabled-command-function nil) + +;; Stop Emacs from trying to use dialog boxes. +(setq use-dialog-box nil) + +;; Prefer y/n over yes/no. +(fset 'yes-or-no-p 'y-or-n-p) + +;; Stuff is often here too. +(add-to-list 'exec-path "~/.local/bin") + +;; Visual preference. +(global-visual-line-mode) + +;; PgUp and PgDown are scarily close to arrow keys on my laptop. +(when (string= (system-name) "qpad") + (unbind-key "C-<prior>") + (unbind-key "C-<next>")) + +;; I don't like the character that is displayed when a line goes over +(set-display-table-slot standard-display-table 0 ?\ ) + +(use-package crux + :bind + (("C-a" . crux-move-beginning-of-line) + ("C-x 4 t" . crux-transpose-windows) + ("C-x K" . crux-kill-other-buffers) + ("C-k" . crux-smart-kill-line) + ("C-<tab>" . crux-indent-defun)) + :config + (crux-with-region-or-buffer indent-region) + (crux-with-region-or-buffer untabify) + (crux-with-region-or-point-to-eol kill-ring-save) + (defalias 'rename-file-and-buffer #'crux-rename-file-and-buffer)) + +(use-package rainbow-mode) + +(setq large-file-warning-threshold 100000000) + +(use-package sudo-edit) + +(use-package drag-stuff + :bind (("M-<up>" . drag-stuff-up) + ("M-<down>" . drag-stuff-down))) + +(global-set-key (kbd "<pinch>") 'ignore) +(global-set-key (kbd "<touchscreen-pinch>") 'ignore) +(global-set-key (kbd "<C-wheel-up>") 'ignore) +(global-set-key (kbd "<C-wheel-down>") 'ignore) diff --git a/modules/vompec.el b/modules/vompec.el @@ -0,0 +1,76 @@ +;; -*- lexical-binding: t; -*- +;;; Configuration for the Vertico (VOMPEC) completion stack. + +(use-package vertico + :init (vertico-mode) + :hook ((rfn-eshadow-update-overlay . vertico-directory-tidy))) + +;; Prior to Emacs 31, a minibuffer prompt from completing-read-multiple did +;; nothing to indicate that multiple options could be selected. This is a +;; patch from the author of vertico.el to work around it. +;; Displays [CRM<separator>], e.g., [CRM,] if the separator is a comma. +(when (< emacs-major-version 31) + (advice-add #'completing-read-multiple :filter-args + (lambda (args) + (cons (format "[CRM%s] %s" + (string-replace "[ \t]*" "" crm-separator) + (car args)) + (cdr args))))) + +;; Do not allow the cursor in the minibuffer prompt +(setq minibuffer-prompt-properties + '(read-only t cursor-intangible t face minibuffer-prompt)) + +;; Hide commands in M-x which do not work in the current mode. +(setq read-extended-command-predicate #'command-completion-default-include-p) + +(use-package orderless + :init + (setq completion-styles '(orderless basic) + completion-category-defaults nil + completion-category-overrides '((file (styles partial-completion))))) + +(use-package marginalia + :bind + (:map minibuffer-local-map + ("M-A" . marginalia-cycle)) + :init (marginalia-mode)) + +(use-package prescient) +(use-package vertico-prescient + :init (vertico-prescient-mode)) + +(use-package embark + :bind + (("C-." . embark-act) + ("M-." . embark-dwim) + ("C-h B" . embark-bindings))) + +(use-package consult + :bind ;; TODO bind the other commands + (("C-c M-x" . consult-mode-command) + ("C-c h" . consult-history) + ("C-c m" . consult-man) + ("C-c i" . consult-info) + ("C-x M-:" . consult-complex-command) + ("C-x b" . consult-buffer) + ("C-x p b" . consult-project-buffer) + ("M-g g" . consult-goto-line) + ("M-g M-g" . consult-goto-line) + ("M-g o" . consult-outline) + ("M-g m" . consult-mark) + ("M-g k" . consult-global-mark) + ("M-g i" . consult-imenu) + ("M-g I" . consult-imenu-multi) + ("M-s d" . consult-fd) + ("M-s r" . consult-ripgrep) + ("C-s" . consult-line) + ("M-s L" . consult-line-multi) + ("M-s k" . consult-keep-lines) + ("M-s u" . consult-focus-lines) + :map minibuffer-local-map + ("M-s" . consult-history) + ("M-r" . consult-history))) + +(use-package embark-consult + :hook (embark-collect-mode . consult-preview-at-point-mode)) diff --git a/readme.org b/readme.org @@ -0,0 +1,4 @@ + +#+TITLE: my emacs config + +The latest edition of my personal Emacs configuration.