;;; init --- user init file -*- lexical-binding: t; -*- (eval '(setq inhibit-startup-echo-area-message "alan")) (defvar default-file-name-handler-alist file-name-handler-alist) (setq file-name-handler-alist nil gc-cons-threshold most-positive-fixnum gc-cons-percentage 0.6 read-process-output-max (* 4 1024 1024)) (defun set-max-gc-cons () (setq gc-cons-threshold most-positive-fixnum)) (defun set-default-gc-cons () (setq gc-cons-threshold (* 16 1024 1024) gc-cons-percentage 0.1)) (add-hook 'minibuffer-setup-hook #'set-max-gc-cons) (add-hook 'minibuffer-exit-hook #'set-default-gc-cons) (add-hook 'after-init-hook #'set-default-gc-cons) (defun restore-file-name-handler-alist () (setq file-name-handler-alist default-file-name-handler-alist)) (add-hook 'emacs-startup-hook #'restore-file-name-handler-alist) (defun maybe-start-server () (require 'server) (unless (server-running-p) (server-start))) (add-hook 'after-init-hook #'maybe-start-server) (eval-when-compile (require 'fringe-helper)) (defun reload-user-init-file () "Reload init file." (interactive) (load-file user-init-file)) (setq use-package-enable-imenu-support t) (require 'use-package) (setq use-package-always-demand (daemonp) use-package-compute-statistics t) (defmacro quietly (&rest body) `(let ((inhibit-message t)) ,@body)) (defun quiet (original-function &rest args) (quietly (apply original-function args))) (use-package benchmark-init :config (progn (add-hook 'after-init-hook #'benchmark-init/deactivate 99))) ;;; Customize (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file :noerror :nomessage) (use-package crux :defer 1) (use-package general :functions (general-unbind general-define-key) :config (progn (general-override-mode +1) (when (eq system-type 'darwin) (general-unbind "s-x")))) ;;; Styles ;; I prefer an always-visible cursor. Feels less distracting. (blink-cursor-mode -1) (setq-default truncate-lines t) ;; Don't ring the bell (setq ring-bell-function #'ignore) (when (or (daemonp) window-system) (use-package stimmung-themes :config (progn (require 'stimmung-themes-light-theme) (let ((light-mode-theme 'stimmung-themes-light) (dark-mode-theme 'stimmung-themes-dark) (original-stimmung-themes-string stimmung-themes-string)) (load-theme light-mode-theme :noconfirm :noenable) (load-theme dark-mode-theme :noconfirm :noenable) (enable-theme light-mode-theme) (defun toggle-stimmung-string-highlighting () (interactive) (let ((current-theme (car custom-enabled-themes))) (setq stimmung-themes-string (if (eq stimmung-themes-string original-stimmung-themes-string) 'none original-stimmung-themes-string)) (load-theme current-theme :noconfirm))))))) (global-set-key (kbd "") 'ignore) (global-set-key (kbd "") 'ignore) (global-set-key (kbd "") 'ignore) (setq font-lock-maximum-decoration '((t . 1)) jit-lock-stealth-time 1.25 jit-lock-stealth-nice 0.5 jit-lock-chunk-size 4096) (when (and (eq system-type 'darwin) (fboundp 'mac-mouse-wheel-mode)) (mac-mouse-wheel-mode +1)) (context-menu-mode +1) ;;; Chrome (column-number-mode -1) (line-number-mode -1) (use-package nerd-icons :config (progn (setq nerd-icons-color-icons t nerd-icons-scale-factor 1.3))) (use-package doom-modeline :hook (emacs-startup . doom-modeline-mode) :config (progn (setq doom-modeline-buffer-file-name-style 'relative-from-project doom-modeline-buffer-encoding 'nondefault doom-modeline-buffer-modification-icon nil doom-modeline-check-simple-format t doom-modeline-percent-position nil doom-modeline-project-detection 'project doom-modeline-vcs-max-length 24 doom-modeline-env-version nil doom-modeline-height 28) (let ((foreground (face-attribute 'font-lock-comment-face :foreground))) (set-face-attribute 'doom-modeline-buffer-modified nil :foreground foreground)))) (when (eq system-type 'darwin) (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) (add-to-list 'default-frame-alist '(ns-appearance . 'light))) (add-to-list 'default-frame-alist '(width . 100)) (add-to-list 'default-frame-alist '(height . 40)) (setq-default display-line-numbers 'relative display-line-numbers-widen t display-line-numbers-width 4) (setq frame-resize-pixelwise t window-resize-pixelwise t display-buffer-alist `(("\\*\\(?:shell\\|compilation\\)\\*" display-buffer-in-side-window (side . bottom) (slot . 0) (preserve-size . (nil . t)) (no-other-window . t) (no-delete-other-windows . t)))) (defun noct-relative () "Show relative line numbers." (when display-line-numbers (setq-local display-line-numbers 'relative))) (defun noct-absolute () "Show absolute line numbers." (when display-line-numbers (setq-local display-line-numbers t))) (add-hook 'evil-insert-state-entry-hook #'noct-absolute) (add-hook 'evil-insert-state-exit-hook #'noct-relative) ;;; Encoding (setq-default bidi-paragraph-direction 'left-to-right bidi-display-reordering 'left-to-right) (setq bidi-inhibit-bpa t require-final-newline t) ;;; Dates & Times (defvar calendar-week-start-day 1) (defvar calendar-date-style 'iso) (defun insert-date (prefix) "Insert the current date. With PREFIX, use British format. With two prefix arguments, write out the day and month name." (interactive "P") (let ((format (cond ((not prefix) "%Y-%m-%d") ((equal prefix '(4)) "%d/%m/%Y") ((equal prefix '(16)) "%A, %d %B %Y")))) (insert (format-time-string format)))) (defun insert-datetime (prefix) "Insert current date and time. With PREFIX, use ISO8601 format." (interactive "P") (let ((format (cond ((not prefix) "%Y-%m-%d %H:%M:%S") ((equal prefix '(4)) "%Y-%m-%dT%H:%M:%SZ")))) (insert (format-time-string format)))) ;;; Keybindings (when (eq system-type 'darwin) (setq mac-option-modifier 'meta mac-right-option-modifier 'none mac-control-modifier 'control mac-right-control-modifier 'left mac-command-modifier 'super mac-right-command-modifier 'left mac-function-modifier 'hyper)) (use-package avy :defer 2 :config (setq avy-all-windows nil)) (use-package ace-link :after avy :commands (ace-link-setup-default) :config (ace-link-setup-default)) ;; Popup keybindings following a prefix automatically. (use-package which-key :defer 5 :config (progn (which-key-mode +1) (which-key-setup-side-window-right-bottom))) ;;; Minibuffer (setq enable-recursive-minibuffers t save-silently t uniquify-buffer-name-style 'forward read-extended-command-predicate #'command-completion-default-include-p) (minibuffer-depth-indicate-mode t) (use-package hydra :defer 2) (use-package recentf :defer 1 :custom (recentf-auto-cleanup 1800) :config (progn (quietly (recentf-mode +1)) (advice-add 'recentf-cleanup :around #'quiet))) (use-package savehist :init (savehist-mode +1) :config (progn (add-to-list 'savehist-additional-variables 'command-history))) (use-package persist-state :defer 1 :ghook ('server-mode-hook #'persist-state-mode)) (use-package vertico :config (progn (vertico-mode +1))) (use-package prescient :defer 1 :config (progn (setq prescient-history-length 10000) (prescient-persist-mode +1))) (use-package vertico-prescient :after prescient :ghook '(vertico-mode-hook)) (use-package marginalia :general (:keymaps 'minibuffer-local-map "M-A" #'marginalia-cycle) :init (marginalia-mode +1)) (setq completion-ignore-case t read-buffer-completion-ignore-case t read-file-name-completion-ignore-case t completion-styles '(flex substring basic) completion-category-overrides '((file (styles basic partial-completion)))) (use-package orderless :config (progn (add-to-list 'completion-styles 'orderless) (setq orderless-matching-styles '(orderless-literal-prefix orderless-prefixes orderless-regexp)))) (use-package consult :general ([remap isearch-forward] #'consult-line [remap isearch-backward] #'consult-line [remap project-switch-to-buffer] #'consult-project-buffer [remap project-search] #'consult-ripgrep) :config (progn (setq consult-ripgrep-args "rg --null --line-buffered --color=never --max-columns=1000 --path-separator / --smart-case --no-heading --with-filename --line-number --search-zip --follow"))) (use-package consult-dir :defer 10 :general ("C-x C-d" #'consult-dir) (:keymaps 'vertico-map "C-x C-d" #'consult-dir "C-x C-j" #'consult-jump)) (use-package embark :general ("C-." #'embark-act "M-." #'embark-dwim "C-h B" #'embark-bindings) :config (progn (setq embark-prompter #'embark-keymap-prompter) (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none)))))) (use-package embark-consult :ghook ('consult-preview-at-point-mode-hook #'embark-collect-mode)) (use-package smerge-mode :after magit :config (defhydra unpackaged/smerge-hydra (:color pink :hint nil :post (smerge-auto-leave)) " ^Move^ ^Keep^ ^Diff^ ^Other^ ^^-----------^^-------------------^^---------------------^^------------- _C-j_: next _b_ase _<_: upper/base _C_ombine _C-k_: prev _u_pper _=_: upper/lower _s_mart resolve ^^ _l_ower _>_: base/lower _k_ill current ^^ _a_ll _R_efine (words) ^^ _RET_: current _E_diff " ("C-j" smerge-next) ("C-k" smerge-prev) ("b" smerge-keep-base) ("u" smerge-keep-upper) ("l" smerge-keep-lower) ("a" smerge-keep-all) ("RET" smerge-keep-current) ("\C-m" smerge-keep-current) ("<" smerge-diff-base-upper) ("=" smerge-diff-upper-lower) (">" smerge-diff-base-lower) ("R" smerge-refine) ("E" smerge-ediff) ("C" smerge-combine-with-next) ("s" smerge-resolve) ("k" smerge-kill-current) ("w" (lambda () (interactive) (save-buffer) (bury-buffer)) "Save and bury buffer" :color blue) ("q" nil "cancel" :color blue)) :hook (magit-diff-visit-file . (lambda () (when (bound-and-true-p smerge-mode) (unpackaged/smerge-hydra/body))))) ;;; Windows (defun split-window-properly (&optional window) (let ((window (or window (selected-window)))) (or (and (window-splittable-p window) ;; Split window vertically. (with-selected-window window (split-window-below))) (and (window-splittable-p window t) ;; Split window horizontally. (with-selected-window window (split-window-right)))))) (setq split-window-preferred-function #'split-window-properly split-height-threshold 20 split-width-threshold 160) (use-package eyebrowse :after (evil) :config (progn (setq eyebrowse-new-workspace t eyebrowse-mode-line-left-delimiter "" eyebrowse-mode-line-right-delimiter "" eyebrowse-mode-line-separator " " eyebrowse-mode-line-style 'always) (eyebrowse-mode +1)) :general (:keymaps 'evil-window-map "0" #'eyebrowse-switch-to-window-config-0 "1" #'eyebrowse-switch-to-window-config-1 "2" #'eyebrowse-switch-to-window-config-2 "3" #'eyebrowse-switch-to-window-config-3 "4" #'eyebrowse-switch-to-window-config-4 "5" #'eyebrowse-switch-to-window-config-5 "6" #'eyebrowse-switch-to-window-config-6 "7" #'eyebrowse-switch-to-window-config-7 "8" #'eyebrowse-switch-to-window-config-8 "9" #'eyebrowse-switch-to-window-config-9) :ghook ('after-init-hook #'eyebrowse-switch-to-window-config-1) :ghook ('evil-after-load-hook #'eyebrowse-setup-evil-keys)) (use-package winner :after evil :defer 8 :config (progn (setq winner-boring-buffers '("*Completions*" "*Help*" "*Apropos*" "*Buffer List*" "*info*" "*Compile-Log*")) (winner-mode +1)) :general (:keymaps 'evil-window-map "u" #'winner-undo "r" #'winner-redo "C-r" #'winner-redo)) ;;; Evil (eval-and-compile (defvar evil-want-keybinding nil)) (use-package evil :demand t :commands (evil-mode evil-delete-buffer evil-ex-define-cmd) :config (progn (setq-default evil-shift-width 2) (setq evil-mode-line-format nil) (evil-set-undo-system 'undo-redo) (add-to-list 'evil-emacs-state-modes 'eshell-mode) (evil-mode +1)) :general (:states 'motion "C-;" #'evil-avy-goto-line) (:states 'normal ";" #'evil-ex) (:states '(normal motion) "g s" #'evil-avy-goto-symbol-1)) (add-hook 'c-mode-common-hook ; make b/w/e include underscore as *part* of a word (lambda () (modify-syntax-entry ?_ "w"))) (use-package evil-anzu :defer 2 :after evil :config (global-anzu-mode +1)) (use-package evil-collection :demand t :config (progn (setq evil-collection-magit-use-y-for-yank nil evil-collection-corfu-key-themes '(default magic-return)) (general-unbind 'normal magit-mode-map "") (evil-collection-init))) (general-create-definer my-leader-def :keymaps 'override :states '(normal motion) :prefix ",") (use-package evil-space :defer 1 :after evil :config (evil-space-mode +1)) ;; this macro was copied from here: https://stackoverflow.com/a/22418983/4921402 (defmacro define-and-bind-quoted-text-object (name key start-regex end-regex) (let ((inner-name (make-symbol (concat "evil-inner-" name))) (outer-name (make-symbol (concat "evil-a-" name)))) `(progn (evil-define-text-object ,inner-name (count &optional beg end type) (evil-select-paren ,start-regex ,end-regex beg end type count nil)) (evil-define-text-object ,outer-name (count &optional beg end type) (evil-select-paren ,start-regex ,end-regex beg end type count t)) (define-key evil-inner-text-objects-map ,key #',inner-name) (define-key evil-outer-text-objects-map ,key #',outer-name)))) (use-package evil-surround :after evil :defer 2 :config (progn (add-hook 'js-base-mode-hook (lambda () (define-and-bind-quoted-text-object "slash" "/" "\\/" "\\/") (push '(?\/ . ("/" . "/")) evil-surround-pairs-alist))) (add-hook 'emacs-lisp-mode-hook (lambda () (push '(?` . ("`" . "'")) evil-surround-pairs-alist))) (global-evil-surround-mode +1))) (use-package evil-embrace :after evil-surround :ghook ('LaTex-mode-hook #'embrace-LaTeX-mode-hook) :ghook ('org-mode-hook #'embrace-org-mode-hook) :ghook ('ruby-base-mode-hook #'embrace-ruby-mode-hook) :ghook ('emacs-lisp-mode-hook #'embrace-emacs-lisp-mode-hook) :config (progn (setq evil-embrace-show-help-p nil) (push ?\/ evil-embrace-evil-surround-keys) (evil-embrace-enable-evil-surround-integration))) (use-package evil-exchange :after evil :config (progn (evil-exchange-cx-install))) (use-package evil-commentary :after evil :defer 2 :config (evil-commentary-mode +1)) (use-package evil-lion :after evil :defer 10 :config (progn (evil-lion-mode +1))) (use-package evil-matchit :after evil :defer 2 :config (progn (global-evil-matchit-mode +1))) (use-package evil-quickscope :after evil :commands (evil-quickscope-mode) :ghook ('(magit-mode-hook git-rebase-mode-hook) #'turn-off-evil-quickscope-mode) :config (global-evil-quickscope-mode +1)) (use-package evil-numbers :after evil :general (:states '(normal visual) "C-a" #'evil-numbers/inc-at-pt "C-q" #'evil-numbers/dec-at-pt)) (use-package evil-org :after org :commands (evil-org-set-key-theme) :init (progn (add-hook 'org-agenda-mode-hook #'evil-org-agenda-set-keys)) :ghook ('org-mode-hook #'evil-org-mode) :gfhook #'evil-org-set-key-theme) (use-package evil-textobj-tree-sitter :defer 5 :config (progn (defun etts/start-of-next-function () (interactive) (evil-textobj-tree-sitter-goto-textobj "function.outer" nil nil)) (defun etts/start-of-prev-function () (interactive) (evil-textobj-tree-sitter-goto-textobj "function.outer" t nil)) (defun etts/end-of-next-function () (interactive) (evil-textobj-tree-sitter-goto-textobj "function.outer" nil t)) (defun etts/end-of-prev-function () (interactive) (evil-textobj-tree-sitter-goto-textobj "function.outer" t t)) (general-define-key :keymaps 'evil-outer-text-objects-map "f" (evil-textobj-tree-sitter-get-textobj "function.outer") "a" (evil-textobj-tree-sitter-get-textobj ("conditional.outer" "loop.outer"))) (general-define-key :keymaps 'evil-inner-text-objects-map "f" (evil-textobj-tree-sitter-get-textobj "function.inner") "a" (evil-textobj-tree-sitter-get-textobj ("conditional.inner" "loop.inner")))) :general (:states 'normal "]f" #'etts/start-of-next-function "[f" #'etts/start-of-prev-function "]F" #'etts/end-of-next-function "[F" #'etts/end-of-prev-function)) ;;; Completion (use-package corfu :defer 1 :config (progn (global-corfu-mode +1) (setq corfu-auto t corfu-auto-delay 0.1 corfu-auto-prefix 3 corfu-on-exact-match nil corfu-preview-current nil corfu-preselect 'prompt corfu-on-exact-match 'quit) (defun corfu-enable-in-minibuffer () "Enable Corfu in the minibuffer if `completion-at-point' is bound." (when (where-is-internal #'completion-at-point (list (current-local-map))) (setq-local corfu-auto nil) ;; Enable/disable auto completion (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup corfu-popupinfo-delay nil) (corfu-mode 1))) (add-hook 'minibuffer-setup-hook #'corfu-enable-in-minibuffer) (add-hook 'eshell-mode-hook (lambda () (setq-local corfu-auto nil) (corfu-mode +1))))) (use-package cape :after (corfu) :general (:states 'insert "C-x C-f" #'cape-file)) (use-package kind-icon :after (corfu) :config (progn (setq kind-icon-default-face 'corfu-default) (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))) (use-package tempel :general ("M-+" #'tempel-complete ;; Alternative tempel-expand "M-*" #'tempel-insert :keymaps 'tempel-mode-map "" #'tempel-next) :config (progn (global-tempel-abbrev-mode +1))) (use-package tempel-collection :after tempel) ;;; Documentation (use-package eldoc :defer 5 :config (progn (setq eldoc-idle-delay 0.5 eldoc-echo-area-use-multiline-p nil) (global-eldoc-mode +1))) (use-package eldoc-box :defer t) (use-package ehelp :defer 15 :general ([remap help-map] 'electric-help-map)) (use-package helpful :general (ehelp-map "k" #'helpful-key "v" #'helpful-variable "f" #'helpful-callable)) ;;; Files ;;;; Auto-saving (setq auto-save-default nil make-backup-files nil create-lockfiles nil) ;;;; Auto-reloading (use-package autorevert :config (progn (setq auto-revert-verbose nil auto-revert-use-notify t) (global-auto-revert-mode 1))) (setq delete-by-moving-to-trash t) (defun my/delete-file-and-buffer () "Kill the current buffer and deletes the file it is visiting." (interactive) (let ((filename (buffer-file-name))) (when filename (when (y-or-n-p (format "Are you sure you want to delete %s? " filename)) (delete-file filename delete-by-moving-to-trash) (message "Deleted file %s" filename) (kill-buffer))))) (use-package goto-chg :defer 1) ;;;; TRAMP (use-package tramp :defer 10 :config (progn (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:")) (add-to-list 'tramp-default-proxies-alist `(,(regexp-quote (system-name)) nil nil)))) (use-package ssh-deploy :config (progn (ssh-deploy-line-mode +1) (ssh-deploy-add-find-file-hook) (ssh-deploy-add-after-save-hook))) ;;; Directories (setq dired-dwim-target t dired-recursive-copies 'top dired-listing-switches "-alh" dired-kill-when-opening-new-dired-buffer t dired-recursive-deletes (if delete-by-moving-to-trash 'always 'top)) (add-hook 'dired-mode-hook (lambda () (dired-hide-details-mode))) (use-package dired-git-info :general (:keymaps 'dired-mode-map :states 'normal ")" #'dired-git-info-mode)) ;;; Shells (use-package eshell :defer 5 :commands (eshell) :functions (eshell/pwd) :general (:keymaps 'eshell-command-map "C-r" #'eshell-history-backwards "C-s" #'eshell-history-forwards) :init (progn (with-eval-after-load 'evil-ex (evil-ex-define-cmd "esh[ell]" #'eshell))) :config (progn (setq eshell-prompt-function (lambda () (concat (eshell/pwd) "\n$ ")) eshell-prompt-regexp "^[$][[:blank:]]" eshell-cmpl-cycle-completions nil) (add-hook 'eshell-exit-hook (lambda () (when (window-deletable-p) (delete-window)))))) (use-package eshell-toggle :commands (eshell-toggle) :general ("C-`" #'eshell-toggle) :config (progn (setq eshell-toggle-find-project-root-package 'project))) (declare-function eshell-push-command "esh-buf-stack" (CMD)) (defun my-bind-esh-push () (general-define-key :states '(normal insert) :keymaps 'local "M-q" #'eshell-push-command)) (use-package esh-buf-stack :after (eshell) :ghook ('eshell-mode-hook #'my-bind-esh-push) :config (setup-eshell-buf-stack)) (use-package esh-help :after (eshell) :config (setup-esh-help-eldoc)) (use-package eshell-fringe-status :after eshell :ghook '(eshell-mode-hook)) (use-package eshell-up :after (eshell)) (use-package shell :defer t :general (:keymaps 'shell-mode-map "C-d" #'comint-delchar-or-maybe-eof)) (use-package comint :defer t :general (:keymaps 'comint-mode-map "C-c C-l" #'counsel-shell-history)) ;;; Editing (setq-default tab-always-indent 'complete indent-tabs-mode nil tab-width 4) (electric-pair-mode +1) (use-package ws-butler :ghook ('prog-mode-hook)) (use-package dtrt-indent :commands (dtrt-indent-mode)) (use-package rainbow-mode :ghook ('(css-mode-hook conf-xdefaults-mode-hook) #'rainbow-mode)) (use-package expand-region :general (:states 'visual "SPC" #'er/expand-region)) ;;; Major modes ;;;; tree-sitter (use-package treesit-auto :config (progn (global-treesit-auto-mode) (treesit-auto-add-to-auto-mode-alist))) ;;;; golang (with-eval-after-load 'project (add-to-list 'project-vc-extra-root-markers "go.mod")) (setq-default go-ts-mode-indent-offset 2) (use-package templ-ts-mode :gfhook #'eglot-format-before-save-mode :defer t :config (progn (setq-default go-ts-mode-indent-offset 2))) ;;;; nim (use-package nim-mode :defer t :config (progn (add-to-list 'eglot-server-programs '(nim-mode "nimlsp")))) ;;;; js (setq js-enabled-frameworks '(javascript)) (add-to-list 'auto-mode-alist '("\\.[cm]js\\'" . js-ts-mode)) (add-to-list 'auto-mode-alist '("\\.lock" . json-ts-mode)) ;;;; typescript (use-package astro-ts-mode :mode (("\\.astro\\'" . astro-ts-mode))) (autoload 'ansi-color-apply-on-region "ansi-color") (defun colourise-compilation-buffer () (ansi-color-apply-on-region compilation-filter-start (point-max))) (add-hook 'compilation-filter-hook #'colourise-compilation-buffer) ;;;; shell (general-add-hook '(sh-mode-hook bash-ts-mode-hook fish-mode-hook) (lambda () (general-add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p :append :local))) (add-to-list 'auto-mode-alist '("\\.env\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("\\.zsh\\'" . shell-script-mode)) (add-to-list 'auto-mode-alist '("zshenv\\'" . shell-script-mode)) (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on) (when (eq system-type 'gnu/linux) (setenv "SSH_AUTH_SOCK" (expand-file-name "ssh-agent" (getenv "XDG_RUNTIME_DIR")))) (use-package fish-mode :mode (("\\.fish\\'" . fish-mode)) :config (progn (setq fish-enable-auto-indent t))) ;;;; nix (with-eval-after-load 'nix-mode (setq nix-mode-use-smie t nix-indent-function #'smie-indent-line)) (use-package nix-ts-mode :mode (("\\.nix\\'" . nix-ts-mode))) (use-package nix-update :commands (nix-update-fetch)) ;;;; gitlab-ci.yml (with-eval-after-load 'git-gutter-fringe (fringe-helper-define 'flycheck-fringe-bitmap-double-arrow '(center repeated) "XXX.....")) (use-package gitlab-ci-mode-flycheck :ghook ('gitlab-ci-mode-hook (list #'gitlab-ci-mode-flycheck-enable #'flycheck-mode))) ;;;; *ignore (use-package gitignore-mode :mode ((".dockerignore\\'" . gitignore-mode))) ;;;; gitolite (use-package gl-conf-mode :defer t) ;;;; lisps (use-package racket-mode :ghook ('racket-mode-hook #'racket-xp-mode)) (use-package clojure-mode :defer t) (use-package cider :defer t :after clojure-mode) (use-package rainbow-delimiters :ghook '(clojure-mode-hook emacs-lisp-mode-hook)) (use-package lispyville :ghook '(emacs-lisp-mode-hook clojure-mode-hook racket-mode-hook)) ;;;; org (use-package org :defer 10 :config (progn (setq org-ellipsis "…" org-modules nil org-directory "~/Documents/org"))) ;;;; web modes (html) (use-package css-mode :defer t) (use-package web-mode :mode (("\\.html?.erb\\'" . web-mode) ("\\.gotmpl\\'" . web-mode) ("\\.tmpl\\'" . web-mode)) :config (setq web-mode-enable-auto-pairing nil web-mode-style-padding 2 web-mode-script-padding 2 web-mode-engines-alist '(("go" . "\\.tmpl\\'") ("go" . "\\.gotmpl\\'")))) (use-package emmet-mode :ghook '(web-mode-hook sgml-mode-hook templ-ts-mode-hook)) ;;; IDE features (fringe-helper-define 'left-vertical-bar '(center repeated) "XXX.....") (use-package flymake :defer 5 :config (setq flymake-error-bitmap '(left-vertical-bar compilation-error) flymake-warning-bitmap '(left-vertical-bar compilation-warning))) (use-package flymake-popon :after flymake :ghook ('flymake-mode-hook #'flymake-popon-mode)) (use-package flycheck :defer t :config (progn (setq flycheck-check-syntax-automatically '(save idle-buffer-switch mode-enabled) flycheck-highlighting-mode 'sexps) (flycheck-define-error-level 'error :severity 100 :compilation-level 2 :overlay-category 'flycheck-error-overlay :fringe-bitmap 'left-vertical-bar :fringe-face 'flycheck-fringe-error :error-list-face 'flycheck-error-list-error) (flycheck-define-error-level 'warning :severity 10 :compilation-level 1 :overlay-category 'flycheck-warning-overlay :fringe-bitmap 'left-vertical-bar :fringe-face 'flycheck-fringe-warning :warning-list-face 'flycheck-error-list-warning) (flycheck-define-error-level 'info :severity -10 :compilation-level 0 :overlay-category 'flycheck-info-overlay :fringe-bitmap 'left-vertical-bar :fringe-face 'flycheck-fringe-info :info-list-face 'flycheck-error-list-info))) ;;; Projects (use-package project :general (:keymaps 'project-prefix-map "s" #'project-search) :config (progn (with-eval-after-load 'evil-ex (evil-ex-define-cmd "pesh[ell]" #'project-eshell) (evil-ex-define-cmd "pb" #'project-switch-to-buffer) (evil-ex-define-cmd "psw[itch]" #'project-switch-project)))) (use-package treemacs :general (:keymaps 'treemacs-mode-map [mouse-1] #'treemacs-single-click-expand-action) :config (progn (treemacs-project-follow-mode t) (setq treemacs-is-never-other-window t treemacs-select-when-already-in-treemacs 'move-back treemacs-eldoc-display nil treemacs-indentation '(8 px) treemacs-show-hidden-files nil treemacs-recenter-after-project-jump 'on-distance treemacs-missing-project-action 'remove))) (use-package treemacs-evil :after treemacs) (use-package treemacs-magit :after treemacs) (use-package treemacs-nerd-icons :after treemacs :init (progn (setq treemacs-nerd-icons-tab " ")) :config (progn (treemacs-load-theme "simple"))) (defun ap/consult-ghq-switch-project (dir) "Append a slash to avoid project.el remembering two different paths for the same project." (interactive) (project-switch-project (if (string-suffix-p "/" dir) dir (concat dir "/")))) (use-package consult-ghq :defer 5 :general (:keymaps 'project-prefix-map "o" #'consult-ghq-switch-project) :config (progn (setq consult-ghq-grep-function #'consult-grep consult-ghq-find-function #'consult-find consult-ghq-switch-project-function #'ap/consult-ghq-switch-project))) (use-package envrc :defer 2 :config (progn (setq envrc-show-summary-in-minibuffer nil) (envrc-global-mode))) (use-package magit :defer 5 :commands (magit-status magit-dispatch) :general ([remap project-vc-dir] #'magit-project-status) (:keymaps 'project-prefix-map "m" #'magit-project-status) :init (progn (defvar magit-auto-revert-mode nil) (setq magit-auto-revert-mode nil) (require 'project) (add-to-list 'project-switch-commands '(magit-project-status "Magit") t)) :config (progn (setq magit-section-visibility-indicator nil magit-diff-refine-hunk t magit-auto-revert-mode nil magit-auto-revert-immediately nil ; unnecessary when global-auto-revert-mode is enabled magit-display-buffer-function #'magit-display-buffer-fullcolumn-most-v1) (remove-hook 'magit-status-sections-hook 'magit-insert-tags-header) (remove-hook 'magit-section-highlight-hook 'magit-section-highlight) (remove-hook 'magit-section-highlight-hook 'magit-section-highlight-selection) (remove-hook 'magit-section-highlight-hook 'magit-diff-highlight) (require 'magit-extras))) (use-package magit-todos :after magit :config (progn (magit-todos-mode +1))) (use-package git-gutter-fringe :defer 5 :config (progn (add-hook 'magit-post-refresh-hook #'git-gutter:update-all-windows) ;; places the git gutter outside the margins. (setq-default fringes-outside-margins nil) ;; thin fringe bitmaps (fringe-helper-define 'git-gutter-fr:added '(center repeated) ".XXX....") (fringe-helper-define 'git-gutter-fr:modified '(center repeated) ".XXX....") (fringe-helper-define 'git-gutter-fr:deleted '(center repeated) ".XXX....") (setq git-gutter-fr:side 'left-fringe) (global-git-gutter-mode 1))) (use-package vc-msg :defer 30) (use-package git-timemachine :commands (git-timemachine)) (use-package editorconfig :defer 2 :config (progn (editorconfig-mode +1) (setq editorconfig-lisp-use-default-indent t) (setf (alist-get 'templ-ts-mode editorconfig-indentation-alist) 'go-ts-mode-indent-offset))) (setq-default ispell-dictionary "en_GB-ise-w_accents") (setq ispell-extra-args '("--sug-mode=ultra" "--camel-case")) (use-package jinx-mode :defer 1 :ghook ('(text-mode-hook prog-mode-hook conf-mode-hook)) :general ([remap ispell-word] #'jinx-correct-word [remap evil-prev-flyspell-error] #'jinx-previous [remap evil-next-flyspell-error] #'jinx-next) :config (progn (advice-add 'jinx--load-dicts :after (lambda () (unless jinx--dicts (global-jinx-mode -1)))))) (use-package feature-mode :defer t :config (progn (setq feature-cucumber-command "cucumber-js {options} \"{feature}\""))) (defvaralias 'typescript-ts-mode-hook 'typescript-mode-hook) (defvaralias 'dockerfile-ts-mode-hook 'dockerfile-mode-hook) (defvaralias 'yaml-ts-mode-hook 'yaml-mode-hook) (defvaralias 'go-ts-mode-hook 'go-mode-hook) (defvaralias 'nix-ts-mode-hook 'nix-mode-hook) (use-package consult-lsp :commands (consult-lsp-symbols consult-lsp-diagnostics)) (define-minor-mode eglot-format-before-save-mode "Whether to ask the LSP to format the buffer before saving" :init-val nil (if eglot-format-before-save-mode (add-hook 'before-save-hook #'eglot-format-buffer nil 'local) (remove-hook 'before-save-hook #'eglot-format-buffer 'local))) (use-package eglot :defer 3 :general (:states 'normal :keymaps 'eglot-mode-map "gr" #'xref-find-references "C-t" #'xref-go-back) :ghook ('(typescript-mode-hook dockerfile-mode-hook yaml-mode-hook js-base-mode-hook css-base-mode-hook lua-mode-hook nim-mode-hook html-mode-hook nix-mode-hook templ-ts-mode-hook toml-ts-mode-hook haskell-mode-hook) #'eglot-ensure) :config (progn (when (assoc 'nix-mode eglot-server-programs) (setf (car (assoc 'nix-mode eglot-server-programs)) '(nix-mode nix-ts-mode))) (nconc eglot-server-programs '((toml-ts-mode "taplo" "lsp" "stdio"))) (advice-add 'eglot--message :before-until (lambda (formatstring &rest rest) (s-starts-with-p "Connected!" formatstring))) (defun my/setup-eglot-eldoc () (push 'flymake-eldoc-function eldoc-documentation-functions)) (add-hook 'eglot-managed-mode-hook 'my/setup-eglot-eldoc) (setq-default eglot-workspace-configuration '( :yaml (:keyOrdering nil) :nil (:nix (:flake (:autoArchive t))) :gopls ( :staticcheck t :usePlaceholders t))) (defun my/eglot-capf () (setq-local completion-at-point-functions (list (cape-capf-super #'eglot-completion-at-point #'tempel-expand #'cape-file)))) (add-hook 'eglot-managed-mode-hook #'my/eglot-capf))) (use-package eglot-tempel :after eglot :config (progn (eglot-tempel-mode +1))) (use-package consult-eglot :commands (consult-eglot-symbols) :after eglot) (use-package lsp-mode :defer 3 :ghook ('(go-mode-hook) #'lsp-deferred) ('lsp-mode-hook #'lsp-enable-which-key-integration) ('lsp-completion-mode-hook #'my/lsp-mode-setup-completion) :general (:states 'normal :keymaps 'lsp-mode-map "gr" #'xref-find-references "C-t" #'xref-go-back) :config (progn (setq lsp-auto-guess-root t lsp-auto-execute-action nil lsp-headerline-breadcrumb-enable nil lsp-enable-suggest-server-download nil lsp-modeline-diagnostics-enable nil lsp-completion-provider :none ; value `:capf' is actually for company :( lsp-diagnostics-provider t ; this means prefer flymake over flycheck, why‽ ) (defun my/lsp-mode-setup-completion () (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults)) '(flex))) (lsp-register-custom-settings '(("golangci-lint.command" ["golangci-lint" "run" "--out-format" "json" "--issues-exit-code=1"]))) (lsp-register-client (make-lsp-client :new-connection (lsp-stdio-connection '("golangci-lint-langserver")) :activation-fn (lsp-activate-on "go") :language-id "go" :priority 0 :server-id 'golangci-lint :add-on? t :library-folders-fn #'lsp-go--library-default-directories :initialization-options (lambda () (gethash "golangci-lint" (lsp-configuration-section "golangci-lint"))))))) (use-package yasnippet :after lsp-mode :ghook ('lsp-completion-mode-hook #'yas-minor-mode)) (defun my/ls-rename () (interactive) (if lsp-mode (call-interactively #'lsp-rename) (call-interactively #'eglot-rename))) (defun my/ls-consult-symbol () (interactive) (if lsp-mode (call-interactively #'consult-lsp-symbols) (call-interactively #'consult-eglot-symbols))) (defun my/ls-code-actions () (interactive) (call-interactively (if lsp-mode #'lsp-execute-code-action #'eglot-code-actions))) ;;;; Reformat on save (use-package format-all :defer 10 :ghook ('prog-mode-hook) :gfhook #'format-all-ensure-formatter :init (progn (advice-add 'format-all-ensure-formatter :around #'quiet) (defun turn-off-format-all-mode () (when (bound-and-true-p format-all-mode) (format-all-mode -1)))) :config (progn (setq format-all-show-errors nil))) (use-package apheleia :defer 11 ; load after format-all for hook ordering? :ghook 'prog-mode-hook :config (progn (setf (alist-get 'shfmt apheleia-formatters) '("shfmt")) (setq apheleia-formatters (append apheleia-formatters '((nixpkgs-fmt "nixpkgs-fmt") (golines "golines") (taplo "taplo" "format" "-") (prettier-gotmpl "prettier" "--stdin-filepath" filepath "--parser=go-template" (apheleia-formatters-indent "--use-tabs" "--tab-width"))))) (setq apheleia-mode-alist (append apheleia-mode-alist '((nix-ts-mode . nixpkgs-fmt) (nix-mode . nixpkgs-fmt) (toml-ts-mode . taplo)))) (add-hook 'apheleia-mode-hook #'turn-off-format-all-mode)) :init (progn (apheleia-global-mode +1))) ;;; Take me to my leader (my-leader-def "" nil "`" #'eshell-toggle "h" '(:keymap ehelp-map :package ehelp) "w" '(:keymap evil-window-map :package evil) "x" '(:keymap ctl-x-map) "c" (general-simulate-key "C-c") "j" #'my/ls-consult-symbol "r" #'my/ls-rename "q" #'evil-delete-buffer "p" '(:keymap project-prefix-map :package project) "v" #'split-window-right "o" #'other-window "s" #'treemacs-select-window "u" #'universal-argument ";" #'execute-extended-command "a" #'my/ls-code-actions "bb" #'consult-buffer "bx" #'kill-this-buffer "br" #'revert-buffer "bk" #'kill-buffer "dd" #'dired "D" #'consult-dir "e" '(:keymap envrc-command-map :package envrc) "fs" #'save-buffer "ff" #'find-file "fw" #'write-file "fd" #'my/delete-file-and-buffer "fr" #'crux-rename-file-and-buffer "gs" #'magit-status "gm" #'vc-msg-show "gg" #'magit-dispatch "gn" #'git-gutter:next-hunk "gp" #'git-gutter:previous-hunk "gi" #'git-gutter:popup-hunk "gs" #'git-gutter:stage-hunk "go" #'git-gutter:revert-hunk "gt" #'git-timemachine "gl" #'magit-log-buffer-file "bz" #'bury-buffer "iu" #'insert-char "xe" #'eval-last-sexp "xx" #'eval-defun "xi" #'consult-imenu "z" '(:keymap ssh-deploy-prefix-map :package ssh-deploy)) (let ((mail-config (expand-file-name "mail.el" user-emacs-directory))) (if (file-readable-p mail-config) (load mail-config :noerror :nomessage))) ;; # Local Variables: ;; # flycheck-disabled-checkers: 'emacs-lisp-checkdoc ;; # End: