;;; -*- lexical-binding: t; -*- (setq gc-cons-threshold (* 800000 100)) (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (require 'use-package) (require 'use-package-ensure) (require 'bind-key) ;; If startup times are slow ;; (setq use-package-verbose t) (setq use-package-compute-statistics t) (add-to-list 'load-path (concat user-emacs-directory "user-lisp/")) (add-to-list 'load-path "/opt/homebrew/Cellar/mu/1.12.14/share/emacs/site-lisp/mu/mu4e") (global-auto-revert-mode 1) (recentf-mode 1) (setq tab-bar-show nil) (setq inhibit-startup-message t) (setq initial-scratch-message nil) (setq ring-bell-function 'ignore) (add-hook 'window-setup-hook 'toggle-frame-maximized t) (defalias 'yes-or-no-p 'y-or-n-p) (setq make-backup-files nil) (setq custom-file (concat user-emacs-directory "custom.el")) (unless (file-exists-p custom-file) (make-empty-file custom-file)) (load custom-file) (scroll-bar-mode -1) (tool-bar-mode -1) (menu-bar-mode -1) (delete-selection-mode 1) (electric-pair-mode 1) (add-hook 'before-save-hook 'delete-trailing-whitespace) (setq reb-re-syntax 'string) (setq default-tab-width 4) (setq-default tab-width 4) (set-face-attribute 'default nil :height 160 :family "Berkeley Mono") (global-hl-line-mode 1) (pixel-scroll-precision-mode 1) (kill-ring-deindent-mode 1) (setopt auto-revert-avoid-polling t) (setopt auto-revert-interval 5) (setopt auto-revert-check-vc-info t) (global-auto-revert-mode 1) (setq global-auto-revert-non-file-buffers t) (setq-default grep-command "rg") (setq use-package-always-ensure t) (setq mac-command-modifier 'control) (setq is-mac (string= system-type "darwin")) (setq-default cursor-type 'bar) (defun hgh/disable-cursor-blink () (blink-cursor-mode -1)) (defun hgh/enable-cursor-blink () (blink-cursor-mode 1)) (add-hook 'activate-mark-hook 'hgh/disable-cursor-blink) (add-hook 'deactivate-mark-hook 'hgh/enable-cursor-blink) (when is-mac (setq dired-use-ls-dired t insert-directory-program "gls" dired-listing-switches "-aBhl --group-directories-first") (setq-default manual-program "gman")) ;; Add .asdf to exec-path (when (file-exists-p (file-truename "~/.asdf")) (push (file-truename "~/.asdf/shims") exec-path) (push (file-truename "~/.asdf/bin") exec-path)) ;; Remove "/mnt/c/" for WSL PATH (setq exec-path (cl-remove-if (lambda (s) (and (< 5 (length s)) (string= (substring s 0 6) "/mnt/c"))) exec-path)) (when exec-path (setenv "PATH" (string-join exec-path ":"))) (defun hgh/duplicate-dwim () (interactive) (duplicate-dwim) (next-line)) (defun hgh/visit-init-file () (interactive) (find-file user-init-file)) (defun hgh/project-ripgrep (regexp) (interactive (list (read-from-minibuffer "Search (regexp): " (thing-at-point 'symbol)))) (rg regexp "*" (project-root (project-current)))) (defun hgh/current-date-time () (interactive) (insert (format-time-string "%Y-%m-%d %H:%M:%S"))) (add-hook 'compilation-filter-hook 'ansi-color-compilation-filter) (defun hgh/next-error () (interactive) (if (eglot-managed-p) (flymake-goto-next-error) (next-error))) ;; key bindings (keymap-global-set "M-o" #'other-window) (keymap-global-set "M-i" #'imenu) (keymap-global-set "" #'compile) (keymap-global-set "C-c r r" #'revert-buffer) (keymap-global-set "M-]" #'forward-paragraph) (keymap-global-set "M-[" #'backward-paragraph) (keymap-global-set "C-h h" #'eldoc) (keymap-global-set "C-]" #'hgh/next-error) (keymap-global-set "C-c e i" #'hgh/visit-init-file) (keymap-global-set "C-c c" #'compile) (keymap-global-set "C-x C-b" #'ibuffer) (keymap-global-set "M-0" #'delete-window) (keymap-global-set "M-1" #'delete-other-windows) (keymap-global-set "M-2" #'split-window-below) (keymap-global-set "M-3" #'split-window-right) (keymap-global-set "C-," #'hgh/duplicate-dwim) (keymap-global-set "C-c f n" #'flymake-goto-next-error) (keymap-global-set "C-c f p" #'flymake-goto-prev-error) (keymap-global-set "C-c f b" #'flymake-show-buffer-diagnostics) (keymap-set completion-list-mode-map "M-n" #'minibuffer-next-completion) (keymap-set completion-list-mode-map "M-p" #'minibuffer-previous-completion) (keymap-set completion-in-region-mode-map "M-n" #'minibuffer-next-completion) (keymap-set completion-in-region-mode-map "M-p" #'minibuffer-previous-completion) (require 'dired) (setq dired-dwim-target t) (keymap-set dired-mode-map "C-c C-c" #'wdired-change-to-wdired-mode) (load-theme 'modus-vivendi t) (use-package corfu :custom (corfu-auto t) (corfu-cycle t) :config (global-corfu-mode 1)) (use-package exec-path-from-shell :when is-mac :config (exec-path-from-shell-initialize)) (use-package magit :defer t) (use-package tree-sitter :mode (("\\.tsx\\'" . tsx-ts-mode) ("\\.js\\'" . typescript-ts-mode) ("\\.mjs\\'" . typescript-ts-mode) ("\\.mts\\'" . typescript-ts-mode) ("\\.cjs\\'" . typescript-ts-mode) ("\\.ts\\'" . typescript-ts-mode) ("\\.jsx\\'" . tsx-ts-mode) ("\\.json\\'" . json-ts-mode)) :custom (treesit-extra-load-path `("~/repos/tree-sitter-module/dist" ,(concat user-emacs-directory "/tree-sitter")))) (use-package tree-sitter-langs :defer t) (use-package haskell-mode :mode "\\.hs\\'") (use-package company) (defun enable-parinfer () (require 's) (let ((buf (or (buffer-file-name) (buffer-name) ""))) (when (and (not (s-contains? "sbcl" buf)) (not (s-contains? "ocicl" buf)) (not (s-contains? "elpa" buf)) (not (s-contains? "emacs-plus" buf)) (not (string-prefix-p "*sly" buf))) (parinfer-rust-mode 1)))) (use-package parinfer-rust-mode :defer t :hook ((lisp-mode emacs-lisp-mode) . enable-parinfer) :custom (parinfer-rust-disable-troublesome-modes t)) (use-package sly :commands (sly) :custom (inferior-lisp-program "/opt/homebrew/bin/sbcl")) (defun hgh/--project-execute (command name) (require 'project) (when (not command) (user-error (concat "No command specified for project-" name))) (let ((default-directory (project-root (project-current t)))) (compilation-start command nil (lambda (mode-name) (concat "*" (project-name (project-current)) " " name "*"))))) (defmacro hgh/define-project-command (name) "Define a project command with NAME. Creates a variable `hgh/project-NAME-command' and function `hgh/project-NAME'." (let ((var-sym (intern (format "hgh/project-%s-command" name))) (fn-sym (intern (format "hgh/project-%s" name)))) `(progn (defvar ,var-sym nil ,(format "Command to %s the project." name)) (put ',var-sym 'safe-local-variable #'stringp) (defun ,fn-sym () ,(format "Execute the project %s command." name) (interactive) (hgh/--project-execute ,var-sym ,name))))) (hgh/define-project-command "run") (hgh/define-project-command "test") (hgh/define-project-command "lint") (use-package project :bind (("C-x p s" . hgh/project-ripgrep) ("C-x p S" . project-shell) ("C-x p t" . hgh/project-test) ("C-x p r" . hgh/project-run) ("C-x p l" . hgh/project-lint))) (use-package vertico :custom (vertico-cycle t) :config (vertico-mode 1)) (use-package savehist :init (savehist-mode)) (use-package marginalia :config (marginalia-mode 1)) (use-package orderless :init (setq completion-styles '(orderless basic) completion-category-overrides '((file (styles basic partial-completion))))) (use-package cape :init (add-to-list 'completion-at-point-functions #'cape-dabbrev)) (defun hgh/org-mode-visual-fill () (setq visual-fill-column-width 120 visual-fill-column-center-text t) (visual-fill-column-mode 1)) (use-package visual-fill-column :hook (org-mode . hgh/org-mode-visual-fill)) (use-package eglot :hook ((clojure-mode . eglot-ensure) (go-mode . eglot-ensure) (rust-ts-mode . eglot-ensure) (typescript-ts-base-mode . eglot-ensure) (elixir-ts-mode . eglot-ensure) (heex-ts-mode . eglot-ensure) (java-ts-mode . eglot-ensure) (svelte-mode . eglot-ensure) (haskell-mode . eglot-ensure) (terraform-mode . eglot-ensure) (odin-mode . eglot-ensure) (tuareg-mode . eglot-ensure) (ruby-mode . eglot-ensure) (dart-mode . eglot-ensure)) :bind (:map eglot-mode-map ("C-c r" . eglot-rename) ("C-c a" . eglot-code-actions) ("C-c f" . eglot-format-buffer) ("C-c d" . xref-find-definitions)) :init (setq eglot-events-buffer-size 0) :config ;; Set up using clippy with rust analyzer (setf eglot-server-programs (cl-remove-if (lambda (c) (equal (car c) (or 'rust-mode 'tuareg-mode '(dart-mode dart-ts-mode)))) eglot-server-programs)) (setf eglot-server-programs (append (list (list '(rust-ts-mode rust-mode) "rust-analyzer" :initializationOptions '(:checkOnSave (:command "clippy"))) '(svelte-mode "svelteserver" "--stdio") '(haskell-mode "haskell-language-server-wrapper" "--lsp") '(tuareg-mode "dune" "exec" "ocamllsp") '(reason-mode "dune" "exec" "ocamllsp") '(ruby-mode "solargraph" "socket" "--port" :autoport) '(dart-mode "dart" "language-server" "--client-id" "emacs.eglot-dart") eglot-server-programs)))) (setenv "DOTNET_ROOT" "~/.local/share/mise/installs/dotnet/9") (push '("\\.csproj$" . xml-mode) auto-mode-alist) (push '("\\.vs$" . c-mode) auto-mode-alist) (push '("\\.fs$" . c-mode) auto-mode-alist) (push '("\\.sbclrc" . lisp-mode) auto-mode-alist) (push '("dune-project\\'" . lisp-mode) auto-mode-alist) (push '("dune\\'" . lisp-mode) auto-mode-alist) (push '("\\.ml\\'" . tuareg-mode) auto-mode-alist) (setf auto-mode-alist (cl-remove-if (lambda (tup) (or (and (string-equal (car tup) "\\.ml\\'") (eq (cdr tup) 'ocaml-ts-mode)) (and (string-equal (car tup) "\\.ml\\'") (eq (cdr tup) 'lisp-mode)))) auto-mode-alist)) ;; paredit ; (autoload 'enable-paredit-mode "paredit" "Turn on pseudo-structural editing of Lisp code." t) (defun setup-paredit () (load (string-join (list user-emacs-directory "paredit.el"))) (setcdr paredit-mode-map nil) (define-key paredit-mode-map (kbd "M-'") #'paredit-forward-slurp-sexp) (define-key paredit-mode-map (kbd "M-;") #'paredit-backward-slurp-sexp) (define-key paredit-mode-map (kbd "M-\"") #'paredit-forward-barf-sexp) (define-key paredit-mode-map (kbd "M-:") #'paredit-backward-barf-sexp) (enable-paredit-mode)) (defun hgh/rg (search) (interactive "sSearch: ") (compilation-start (concat "rg -S --no-heading '" search "'") 'compilation-mode (lambda (s) (concat "*" (file-name-nondirectory (directory-file-name default-directory)) " rg*")))) (defun hgh/set-cursor-type-box (&rest _args) (setq-local cursor-type 'box)) (defun hgh/set-cursor-type-bar (&rest _args) (setq-local cursor-type 'bar)) (use-package multiple-cursors :hook ((multiple-cursors-mode-disabled . hgh/set-cursor-type-bar)) :bind (("C->" . mc/mark-next-like-this) ("C-<" . mc/mark-previous-like-this)) :init ;; NOTE(grant): I have no idea if the package is deferred or autoloaded anymore (advice-add 'mc/mark-next-like-this :before #'hgh/set-cursor-type-box) (advice-add 'mc/mark-previous-like-this :before #'hgh/set-cursor-type-box)) (use-package yasnippet :diminish :defer t :config (yas-global-mode 1)) (use-package yasnippet-snippets :after (yasnippet)) (use-package markdown-mode :mode "\\.md\\'") ;; Run Prettier only in certain major-modes before saving: (defvar hgh/prettier-modes '(tsx-ts-mode typescript-ts-mode typescript-ts-base-mode json-ts-mode javascript-mode js2-mode typescript-mode web-mode) "List of major modes where Prettier should auto-run on save.") (defun hgh/run-prettier-if-appropriate () "Run Prettier on the buffer's file if `major-mode` is in `my/prettier-modes`." (when (and (buffer-file-name) (member major-mode hgh/prettier-modes)) ;; call the prettier CLI; output errors to *Prettier Errors* buffer (call-process "prettier" nil "*Prettier Errors*" t "--write" (buffer-file-name)) ;; reload the buffer if prettier modified the file (revert-buffer :ignore-auto :noconfirm :preserve-modes))) (add-hook 'after-save-hook #'hgh/run-prettier-if-appropriate) (use-package terraform-mode :mode ("\\.tf\\'" "\\.tfvars\\'")) (use-package cider :defer t) (use-package org :ensure nil :mode ("\\.org\\'" . org-mode) :hook (org-mode . org-indent-mode) :custom (org-todo-keywords '((sequence "TODO" "INPROGRESS" "DONE"))) (org-support-shift-select t) (org-default-notes-file (concat org-directory "/captures.org")) (org-html-validation-link nil) (org-html-head-include-default-style nil) (org-html-head-include-scripts nil) (org-html-head "") (org-capture-templates '(("t" "Task" entry (file+headline "~/org/inbox.org" "Tasks") "* TODO %?\n%U\n%i\n%a") ("n" "Note" entry (file+headline "~/org/inbox.org" "Notes") "* %?\n%U\n%i\n%a") ("i" "Idea" entry (file+headline "~/org/inbox.org" "Ideas") "* %?\n%U\n%i\n%a"))) (org-publish-use-timestamps-flag nil) (org-publish-project-alist (list (list "writings" :base-directory "~/Documents/writings/content" :publishing-directory "~/Documents/writings/public" :exclude "~/Documents/writings/notes" :recursive t :time-stamp-file nil :section-numbers nil :with-creator t :with-author nil))) :config (require 'ox-publish) (org-babel-do-load-languages 'org-babel-load-languages '((shell . t)))) (use-package dumber-jump :defer t :init (add-hook 'xref-backend-functions #'dumber-jump-xref-activate)) (use-package idris-mode :mode "\\.idr\\'") (use-package htmlize) (use-package zig-mode :mode "\\.zig\\'") (use-package fsharp-mode :mode "\\.fs\\'") (use-package caddyfile-mode :defer t) (use-package dockerfile-mode :defer t) (use-package odin-mode :mode "\\.odin\\'" :ensure nil) (use-package emacs-lisp-mode :ensure nil :bind (:map emacs-lisp-mode-map ("C-c C-c" . eval-defun))) (use-package codex :commands codex :custom (codex-provider nil) (codex-model nil) :ensure nil) (use-package move-text :bind (("M-" . move-text-up) ("M-" . move-text-down))) (use-package emmet-mode :hook (sgml-mode tsx-ts-mode) :custom (emmet-jsx-major-modes '(rjsx-mode typescript-tsx-mode js-jsx-mode js2-jsx-mode jsx-mode js-mode ;; Need to add this tsx-ts-mode))) (use-package mu4e :ensure nil :commands (mu4e) :custom (mu4e-maildir "~/Mail") (mu4e-get-mail-command "mbsync -a") (mu4e-update-interval 300) (mu4e-change-filenames-when-moving t) (mu4e-context-policy 'pick-first) (mu4e-compose-context-policy 'ask-if-none) (mu4e-contexts (list (make-mu4e-context :name "Grant" :match-func (lambda (msg) (when msg (string-prefix-p "/grant" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "grant@granthorner.dev") (user-full-name . "Grant Horner") (mu4e-drafts-folder . "/grant/Drafts") (mu4e-sent-folder . "/grant/Sent Mail") (mu4e-trash-folder . "/grant/Trash") (mu4e-rstrash-folder . "/grant/Trash"))) (make-mu4e-context :name "Gmail" :match-func (lambda (msg) (when msg (string-prefix-p "/gmail" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "gmail@granthorner.dev") (user-full-name . "Grant Horner") (mu4e-drafts-folder . "/gmail/Drafts") (mu4e-sent-folder . "/gmail/Sent Mail") (mu4e-trash-folder . "/gmail/Trash") (mu4e-rstrash-folder . "/gmail/Trash")))))) (use-package tuareg :mode "\\.ml\\'") (defun hgh/eglot-restart () (interactive) (require 'eglot) (when eglot--managed-mode (message "Shutting down eglot servers...") (eglot-shutdown-all) (message "Restarting eglot for current buffer...") (eglot))) (use-package reason-mode :mode "\\.re\\'") (use-package expand-region :bind ("C-=" . er/expand-region)) (use-package ediff :defer t :custom (ediff-window-setup-function 'ediff-setup-windows-plain) (ediff-split-window-function 'split-window-horizontally) (ediff-diff-options "-w") :config (set-face-attribute 'ediff-odd-diff-A nil :background "#75383b") (set-face-attribute 'ediff-even-diff-A nil :background "#75383b") (set-face-attribute 'ediff-odd-diff-B nil :background "#265441") (set-face-attribute 'ediff-even-diff-B nil :background "#265441")) (use-package dart-mode) (use-package flutter)