Add Emacs config with install scripts for laptop and work

Shared: init.el / early-init.el with Vertico stack, Corfu, Eglot,
treesit-auto, Catppuccin Mocha, doom-modeline, Magit, avy, harpoon,
embark, wgrep, expand-region, envrc, org-mode, and drop-in lang loader.

Lang configs split by stow target:
- shared: elisp, org
- laptop: C/C++, Rust, Go, LaTeX (AUCTeX + latexmk + Zathura)
- work:   C#, JS/TS/HTML/CSS/React (apheleia+prettier), Copilot

install-personal.sh: Fedora — gcc/clang, rustup, Go, texlive, zathura
install-work.sh: Ubuntu 24.04 WSL — dotnet-sdk-10.0, Node 22 via nvm, ts-ls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-29 15:12:32 -04:00
parent b0ed92c0cf
commit 9ac5b2beff
13 changed files with 486 additions and 0 deletions

66
install-personal.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Personal workstation dev dependencies — Fedora
# Languages: C, C++, Rust, Go, Emacs Lisp, LaTeX
set -euo pipefail
# --- Core tools ---
sudo dnf install -y \
emacs \
git \
ripgrep \
fd-find \
stow
# --- C / C++ ---
sudo dnf install -y \
gcc \
gcc-c++ \
clang \
clang-tools-extra \
cmake \
ninja-build
# --- Rust (latest stable via rustup) ---
if ! command -v rustup &>/dev/null; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
fi
# shellcheck source=/dev/null
source "$HOME/.cargo/env"
rustup update stable
rustup component add rust-analyzer rust-src clippy rustfmt
# --- Go (latest stable — go.dev does not use LTS; update GO_VERSION as needed) ---
GO_VERSION="1.24.2"
INSTALLED=$(go version 2>/dev/null | awk '{print $3}' || true)
if [[ "$INSTALLED" != "go${GO_VERSION}" ]]; then
curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" -o /tmp/go.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf /tmp/go.tar.gz
rm /tmp/go.tar.gz
fi
export PATH="/usr/local/go/bin:$PATH"
# gopls language server
go install golang.org/x/tools/gopls@latest
# --- LaTeX ---
# texlive-scheme-medium covers most common packages including latexmk
# liturg is a liturgical typesetting package (plainchant, etc.)
sudo dnf install -y \
texlive-scheme-medium \
texlive-liturg \
zathura \
zathura-pdf-mupdf \
aspell \
aspell-en
# Inverse search: Zathura → Emacs via Ctrl+click
# Add to ~/.config/zathura/zathurarc if not already present:
# set synctex-editor-command "emacsclient +%{line} %{input}"
ZATHURA_RC="$HOME/.config/zathura/zathurarc"
if ! grep -q "synctex-editor-command" "$ZATHURA_RC" 2>/dev/null; then
mkdir -p "$(dirname "$ZATHURA_RC")"
echo 'set synctex-editor-command "emacsclient +%{line} %{input}"' >> "$ZATHURA_RC"
fi
echo "Personal dev dependencies installed."

50
install-work.sh Executable file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Work workstation dev dependencies — Ubuntu 24.04 LTS (WSL)
# Languages: C#, JavaScript, TypeScript, Node.js, HTML, CSS, React
set -euo pipefail
sudo apt-get update -q
# --- Core tools ---
# curl may not be present in a minimal WSL image
sudo apt-get install -y \
emacs \
git \
ripgrep \
fd-find \
stow \
curl
# Ubuntu names the fd binary 'fdfind'; add a user-local symlink so tooling finds it as 'fd'
if command -v fdfind &>/dev/null && ! command -v fd &>/dev/null; then
mkdir -p "$HOME/.local/bin"
ln -sf "$(command -v fdfind)" "$HOME/.local/bin/fd"
fi
# --- .NET 10 (in Ubuntu 24.04 main repo — no extra source needed) ---
sudo apt-get install -y dotnet-sdk-10.0
# C# language server (NuGet global tool)
dotnet tool install --global csharp-ls 2>/dev/null \
|| dotnet tool update --global csharp-ls
# --- Node.js 22 LTS via nvm (Ubuntu 24.04 repos ship 20.x; nvm adds no apt sources) ---
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [[ ! -d "$NVM_DIR" ]]; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
fi
# shellcheck source=/dev/null
source "$NVM_DIR/nvm.sh"
nvm install 22
nvm alias default 22
nvm use default
# --- JS / TS / HTML / CSS language servers and formatters ---
npm install -g \
typescript \
typescript-language-server \
vscode-langservers-extracted \
prettier \
eslint
echo "Work dev dependencies installed."

View File

@@ -0,0 +1,11 @@
;;; lisp/langs/c-cpp.el --- C and C++ -*- lexical-binding: t -*-
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'((c-mode c-ts-mode c++-mode c++-ts-mode)
. ("clangd" "--background-index" "--clang-tidy"))))
(dolist (hook '(c-mode-hook c-ts-mode-hook c++-mode-hook c++-ts-mode-hook))
(add-hook hook #'eglot-ensure))
(use-package cmake-mode)

View File

@@ -0,0 +1,14 @@
;;; lisp/langs/golang.el --- Go -*- lexical-binding: t -*-
(use-package go-mode)
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'((go-mode go-ts-mode) . ("gopls"))))
(defun my/go-format-on-save ()
(add-hook 'before-save-hook #'eglot-format-buffer nil t))
(dolist (hook '(go-mode-hook go-ts-mode-hook))
(add-hook hook #'eglot-ensure)
(add-hook hook #'my/go-format-on-save))

View File

@@ -0,0 +1,30 @@
;;; lisp/langs/latex.el --- LaTeX via AUCTeX -*- lexical-binding: t -*-
(use-package tex
:ensure auctex
:custom
(TeX-auto-save t)
(TeX-parse-self t)
(TeX-master nil) ; ask per-project, supports multi-file docs
(TeX-PDF-mode t)
(TeX-source-correlate-mode t)
(TeX-source-correlate-method 'synctex)
:config
;; latexmk handles multi-pass builds (bibtex, makeindex, etc.) automatically
(add-to-list 'TeX-command-list
'("LatexMk" "latexmk -pdf -interaction=nonstopmode %s"
TeX-run-command nil t :help "Run latexmk"))
(setq TeX-command-default "LatexMk")
;; Forward search: C-c C-v jumps PDF to current source position
;; Inverse search: Ctrl+click in Zathura calls emacsclient (requires server-start)
(add-to-list 'TeX-view-program-list
'("Zathura"
("zathura"
(mode-io-correlate " --synctex-forward %n:0:%b") " %o")
"zathura"))
(setq TeX-view-program-selection '((output-pdf "Zathura"))))
(add-hook 'LaTeX-mode-hook #'LaTeX-math-mode)
(add-hook 'LaTeX-mode-hook #'turn-on-reftex)
(add-hook 'LaTeX-mode-hook #'flyspell-mode)
(setq reftex-plug-into-AUCTeX t)

View File

@@ -0,0 +1,10 @@
;;; lisp/langs/rust.el --- Rust -*- lexical-binding: t -*-
(use-package rust-mode)
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'((rust-mode rust-ts-mode) . ("rust-analyzer"))))
(dolist (hook '(rust-mode-hook rust-ts-mode-hook))
(add-hook hook #'eglot-ensure))

View File

@@ -0,0 +1,7 @@
;;; early-init.el -*- lexical-binding: t -*-
;; Defer GC during startup; restore to a reasonable limit after
(setq gc-cons-threshold (* 128 1024 1024))
(add-hook 'emacs-startup-hook
(lambda () (setq gc-cons-threshold (* 16 1024 1024))))

View File

@@ -0,0 +1,198 @@
;;; init.el --- Emacs configuration -*- lexical-binding: t -*-
;;; Package management
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
(unless package-archive-contents
(package-refresh-contents))
(require 'use-package)
(setq use-package-always-ensure t)
;;; Sane defaults
(setq-default
inhibit-startup-message t
visible-bell t
make-backup-files nil
auto-save-default nil
create-lockfiles nil
vc-follow-symlinks t
find-file-visit-truename t
fill-column 80
tab-width 4
indent-tabs-mode nil
truncate-lines t
sentence-end-double-space nil)
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(tooltip-mode -1)
(setq display-line-numbers-type 'relative)
(global-display-line-numbers-mode 1)
(column-number-mode 1)
(global-hl-line-mode 1)
(show-paren-mode 1)
(electric-pair-mode 1)
(delete-selection-mode 1)
(setq global-auto-revert-non-file-buffers t)
(global-auto-revert-mode 1)
(setq scroll-margin 4
scroll-conservatively 101)
(setq uniquify-buffer-name-style 'forward)
;;; Font — matches ghostty / tmux setup
(set-face-attribute 'default nil
:family "Hack Nerd Font Mono"
:height 110)
;;; Theme
(use-package catppuccin-theme
:config
(setq catppuccin-flavor 'mocha)
(load-theme 'catppuccin t))
;;; Discoverability
(use-package which-key
:config
(setq which-key-idle-delay 0.5)
(which-key-mode))
;;; Minibuffer — Vertico + Orderless + Marginalia + Consult
(use-package vertico
:config (vertico-mode))
(use-package orderless
:custom
(completion-styles '(orderless basic))
(completion-category-overrides '((file (styles basic partial-completion)))))
(use-package marginalia
:config (marginalia-mode))
(use-package consult
:bind (("C-x b" . consult-buffer)
("M-y" . consult-yank-pop)
("M-g M-g" . consult-goto-line)
("M-g i" . consult-imenu)
("M-s l" . consult-line)
("M-s r" . consult-ripgrep)))
;; Contextual actions on any minibuffer candidate or thing at point
(use-package embark
:bind (("C-." . embark-act)
("C-;" . embark-dwim))
:custom (embark-indicators '(embark-minimal-indicator)))
(use-package embark-consult
:hook (embark-collect-mode . consult-preview-at-point-mode))
;; Edit consult-ripgrep / grep results as a buffer; save to apply across files
(use-package wgrep
:custom (wgrep-auto-save-buffer t))
;;; In-buffer completion — Corfu + Cape
(use-package corfu
:custom
(corfu-auto t)
(corfu-auto-delay 0.2)
(corfu-auto-prefix 2)
(corfu-quit-no-match 'separator)
:config
(global-corfu-mode))
(use-package cape
:init
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-dabbrev))
;;; Visual aids
(use-package rainbow-delimiters
:hook (prog-mode . rainbow-delimiters-mode))
;; nerd-icons supplies glyphs for doom-modeline; Hack Nerd Font Mono already
;; includes them, so M-x nerd-icons-install-fonts is only needed on first use
;; if you see boxes instead of icons.
(use-package nerd-icons)
(use-package doom-modeline
:hook (after-init . doom-modeline-mode)
:custom
(doom-modeline-height 28)
(doom-modeline-bar-width 4)
(doom-modeline-icon t)
(doom-modeline-major-mode-icon t)
(doom-modeline-buffer-file-name-style 'truncate-upto-project)
(doom-modeline-vcs-max-length 24))
;;; Tree-sitter — auto-switch to *-ts-mode when grammars are available
(use-package treesit-auto
:custom (treesit-auto-install 'prompt)
:config (global-treesit-auto-mode))
;;; LSP — Eglot (built-in since Emacs 29)
(use-package eglot
:ensure nil
:custom
(eglot-autoshutdown t)
(eglot-confirm-server-initiated-edits nil)
(eglot-events-buffer-size 0))
;;; Navigation
;; Jump to any visible character — type a few chars of the target, pick the label
(use-package avy
:bind (("M-j" . avy-goto-char-timer)
("C-c j" . avy-goto-line)))
;; Pin files to numbered slots per project; jump to them instantly
(use-package harpoon
:bind (("C-c h a" . harpoon-add-file)
("C-c h h" . harpoon-toggle-file)
("C-c h 1" . harpoon-go-to-1)
("C-c h 2" . harpoon-go-to-2)
("C-c h 3" . harpoon-go-to-3)
("C-c h 4" . harpoon-go-to-4)))
;; Expand selection by semantic units: word → symbol → string → block → …
(use-package expand-region
:bind ("C-=" . er/expand-region))
;;; Git
(use-package magit
:bind ("C-x g" . magit-status))
;;; Direnv — respect .envrc per buffer (extends your shell direnv hook into Emacs)
(use-package envrc
:hook (after-init . envrc-global-mode))
;;; Server — enables emacsclient ('e' alias) and AUCTeX inverse search
(use-package server
:ensure nil
:config (unless (server-running-p) (server-start)))
;;; Drop-in language configs (stow'd from laptop/ or work/)
(let ((lang-dir (expand-file-name "lisp/langs" user-emacs-directory)))
(when (file-directory-p lang-dir)
(dolist (f (directory-files lang-dir t "\\.el$"))
(load f nil t))))

View File

@@ -0,0 +1,7 @@
;;; lisp/langs/elisp.el --- Emacs Lisp -*- lexical-binding: t -*-
;; Inline macro expansion — C-c e to expand, q to collapse
(use-package macrostep
:bind (:map emacs-lisp-mode-map ("C-c e" . macrostep-expand)))
(add-hook 'emacs-lisp-mode-hook #'flymake-mode)

View File

@@ -0,0 +1,26 @@
;;; lisp/langs/org.el --- Org mode -*- lexical-binding: t -*-
(use-package org
:ensure nil
:custom
(org-directory "~/org")
(org-startup-indented t)
(org-startup-folded 'content)
(org-hide-emphasis-markers t)
(org-pretty-entities t)
(org-return-follows-link t)
(org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "WAITING(w)" "|" "DONE(d)" "CANCELLED(c)")))
:bind (("C-c a" . org-agenda)
("C-c c" . org-capture)
("C-c l" . org-store-link)))
;; Modern, clean visual style for org buffers and agenda
(use-package org-modern
:hook ((org-mode . org-modern-mode)
(org-agenda-finalize . org-modern-agenda)))
;; Reveal emphasis markers and links when cursor is on them
(use-package org-appear
:hook (org-mode . org-appear-mode)
:custom (org-appear-autolinks t))

View File

@@ -0,0 +1,16 @@
;;; lisp/langs/copilot.el --- GitHub Copilot -*- lexical-binding: t -*-
;; copilot.el is not on MELPA; installed via package-vc (Emacs 29+)
(use-package copilot
:vc (:url "https://github.com/copilot-emacs/copilot.el" :rev :newest)
:hook (prog-mode . copilot-mode)
:bind (:map copilot-completion-map
;; copilot-completion-map is only active when ghost text is showing,
;; so these bindings don't interfere with Corfu's popup
("<tab>" . copilot-accept-completion)
("TAB" . copilot-accept-completion)
("M-f" . copilot-accept-completion-by-word)
("M-n" . copilot-next-completion)
("M-p" . copilot-previous-completion))
:custom
(copilot-idle-delay 0.5))

View File

@@ -0,0 +1,9 @@
;;; lisp/langs/csharp.el --- C# -*- lexical-binding: t -*-
;; csharp-ts-mode is built-in since Emacs 29
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'((csharp-mode csharp-ts-mode) . ("csharp-ls"))))
(dolist (hook '(csharp-mode-hook csharp-ts-mode-hook))
(add-hook hook #'eglot-ensure))

View File

@@ -0,0 +1,42 @@
;;; lisp/langs/web.el --- JS / TS / HTML / CSS / React -*- lexical-binding: t -*-
;; web-mode for JSX, TSX, and HTML template files
(use-package web-mode
:mode (("\\.jsx\\'" . web-mode)
("\\.tsx\\'" . web-mode)
("\\.html?\\'" . web-mode))
:custom
(web-mode-markup-indent-offset 2)
(web-mode-css-indent-offset 2)
(web-mode-code-indent-offset 2))
;; typescript-language-server handles JS, TS, JSX, TSX
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'((js-mode js-ts-mode typescript-ts-mode tsx-ts-mode web-mode)
. ("typescript-language-server" "--stdio")))
(add-to-list 'eglot-server-programs
'((html-mode mhtml-mode)
. ("vscode-html-language-server" "--stdio")))
(add-to-list 'eglot-server-programs
'((css-mode css-ts-mode)
. ("vscode-css-language-server" "--stdio"))))
(dolist (hook '(js-mode-hook js-ts-mode-hook
typescript-ts-mode-hook tsx-ts-mode-hook
web-mode-hook
html-mode-hook mhtml-mode-hook
css-mode-hook css-ts-mode-hook))
(add-hook hook #'eglot-ensure))
;; Async formatting via Prettier (apheleia runs formatters without blocking)
(use-package apheleia
:hook ((js-mode . apheleia-mode)
(js-ts-mode . apheleia-mode)
(typescript-ts-mode . apheleia-mode)
(tsx-ts-mode . apheleia-mode)
(css-mode . apheleia-mode)
(css-ts-mode . apheleia-mode)
(web-mode . apheleia-mode))
:config
(setf (alist-get 'web-mode apheleia-mode-alist) '(prettier)))