diff --git a/install-personal.sh b/install-personal.sh new file mode 100755 index 0000000..cba45e1 --- /dev/null +++ b/install-personal.sh @@ -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." diff --git a/install-work.sh b/install-work.sh new file mode 100755 index 0000000..e9c76bd --- /dev/null +++ b/install-work.sh @@ -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." diff --git a/laptop/.config/emacs/lisp/langs/c-cpp.el b/laptop/.config/emacs/lisp/langs/c-cpp.el new file mode 100644 index 0000000..3ab6abf --- /dev/null +++ b/laptop/.config/emacs/lisp/langs/c-cpp.el @@ -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) diff --git a/laptop/.config/emacs/lisp/langs/golang.el b/laptop/.config/emacs/lisp/langs/golang.el new file mode 100644 index 0000000..0da0e78 --- /dev/null +++ b/laptop/.config/emacs/lisp/langs/golang.el @@ -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)) diff --git a/laptop/.config/emacs/lisp/langs/latex.el b/laptop/.config/emacs/lisp/langs/latex.el new file mode 100644 index 0000000..952c964 --- /dev/null +++ b/laptop/.config/emacs/lisp/langs/latex.el @@ -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) diff --git a/laptop/.config/emacs/lisp/langs/rust.el b/laptop/.config/emacs/lisp/langs/rust.el new file mode 100644 index 0000000..063cd05 --- /dev/null +++ b/laptop/.config/emacs/lisp/langs/rust.el @@ -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)) diff --git a/shared/.config/emacs/early-init.el b/shared/.config/emacs/early-init.el new file mode 100644 index 0000000..2deef40 --- /dev/null +++ b/shared/.config/emacs/early-init.el @@ -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)))) diff --git a/shared/.config/emacs/init.el b/shared/.config/emacs/init.el new file mode 100644 index 0000000..119dbbc --- /dev/null +++ b/shared/.config/emacs/init.el @@ -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)))) diff --git a/shared/.config/emacs/lisp/langs/elisp.el b/shared/.config/emacs/lisp/langs/elisp.el new file mode 100644 index 0000000..b2d2e41 --- /dev/null +++ b/shared/.config/emacs/lisp/langs/elisp.el @@ -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) diff --git a/shared/.config/emacs/lisp/langs/org.el b/shared/.config/emacs/lisp/langs/org.el new file mode 100644 index 0000000..9710bf9 --- /dev/null +++ b/shared/.config/emacs/lisp/langs/org.el @@ -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)) diff --git a/work/.config/emacs/lisp/langs/copilot.el b/work/.config/emacs/lisp/langs/copilot.el new file mode 100644 index 0000000..c113666 --- /dev/null +++ b/work/.config/emacs/lisp/langs/copilot.el @@ -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 + ("" . 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)) diff --git a/work/.config/emacs/lisp/langs/csharp.el b/work/.config/emacs/lisp/langs/csharp.el new file mode 100644 index 0000000..ebe6f52 --- /dev/null +++ b/work/.config/emacs/lisp/langs/csharp.el @@ -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)) diff --git a/work/.config/emacs/lisp/langs/web.el b/work/.config/emacs/lisp/langs/web.el new file mode 100644 index 0000000..8ad0674 --- /dev/null +++ b/work/.config/emacs/lisp/langs/web.el @@ -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)))