;;; post-init --- user init file -*- lexical-binding: t; -*- (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)) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (run-with-idle-timer 300 nil #'package-refresh-contents) (eval-when-compile (require 'use-package)) (setq use-package-always-demand (daemonp) use-package-always-ensure nil) (defmacro quietly (&rest body) `(let ((inhibit-message t)) ,@body)) (defun quiet (original-function &rest args) (quietly (apply original-function args))) ;;; 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 (use-package exec-path-from-shell :if (eq system-type 'darwin) :config (progn (exec-path-from-shell-initialize))) (global-set-key (kbd "") 'ignore) (global-set-key (kbd "") 'ignore) (global-set-key (kbd "") 'ignore) ;;; Chrome (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) (defun turn-off-display-line-numbers-mode () (interactive) (display-line-numbers-mode -1)) (defun turn-on-display-line-numbers-mode () (interactive) (display-line-numbers-mode (default-value 'display-line-numbers))) (setq frame-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)))) (use-package buffer-terminator :defer 60 :config (progn (buffer-terminator-mode +1) (setf (alist-get 'kill-buffer-major-modes buffer-terminator-rules-alist) '(magit-log-mode magit-revision-mode grep-mode emacs-lisp-compilation-mode xref--xref-buffer-mode helpful-mode) (alist-get 'kill-buffer-name-regexp buffer-terminator-rules-alist) (rx (seq string-start "*" (or "EGLOT " "envrc" "helpful" "Async-native-compliation") (zero-or-more (not "*")) "*" string-end))) (setq buffer-terminator-verbose nil))) (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) (add-to-list 'auto-coding-alist '("prescient-save\\.el\\'" . utf-8-emacs)) ;;; 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 (defun prot/keyboard-quit-dwim () "Do-What-I-Mean behaviour for a general `keyboard-quit'. The generic `keyboard-quit' does not do the expected thing when the minibuffer is open. Whereas we want it to close the minibuffer, even without explicitly focusing it. The DWIM behaviour of this command is as follows: - When the region is active, disable it. - When a minibuffer is open, but not focused, close the minibuffer. - When the Completions buffer is selected, close it. - In every other case use the regular `keyboard-quit'." (interactive) (cond ((region-active-p) (keyboard-quit)) ((derived-mode-p 'completion-list-mode) (delete-completion-window)) ((> (minibuffer-depth) 0) (abort-recursive-edit)) (t (keyboard-quit)))) (define-key global-map (kbd "C-g") #'prot/keyboard-quit-dwim) (when (eq system-type 'darwin) (setq mac-right-option-modifier 'none) (define-key global-map (kbd "s-w") #'kill-current-buffer) (define-key global-map (kbd "s-k") #'kill-current-buffer :remove)) (use-package link-hint :defer t) ;; Popup keybindings following a prefix automatically. (use-package which-key :defer 5 :custom (which-key-idle-delay 1.5) (which-key-idle-secondary-delay 0.25) (which-key-add-column-padding 1) (which-key-max-description-length 40) :config (progn (which-key-mode +1) (which-key-setup-side-window-right-bottom))) ;;; Minibuffer (setq save-silently t) (minibuffer-depth-indicate-mode t) (use-package hydra :defer 2) (use-package recentf :ensure nil :commands (recentf-mode recentf-cleanup) :custom (recentf-auto-cleanup (if (daemonp) 1800 'never)) (recentf-exclude (list "\\.tar$" "\\.tbz2$" "\\.tbz$" "\\.tgz$" "\\.bz2$" "\\.bz$" "\\.gz$" "\\.gzip$" "\\.xz$" "\\.zip$" "\\.7z$" "\\.rar$" "COMMIT_EDITMSG\\'" "\\.\\(?:gz\\|gif\\|svg\\|png\\|jpe?g\\|bmp\\|xpm\\)$" "-autoloads\\.el$" "autoload\\.el$")) :config (progn ;; A cleanup depth of -90 ensures that `recentf-cleanup' runs before ;; `recentf-save-list', allowing stale entries to be removed before the list ;; is saved by `recentf-save-list', which is automatically added to ;; `kill-emacs-hook' by `recentf-mode'. (add-hook 'kill-emacs-hook #'recentf-cleanup -90))) (use-package savehist :ensure nil :commands (savehist-mode savehist-save) :config (progn (quietly (recentf-mode +1)) (advice-add 'recentf-cleanup :around #'quiet)) :custom (savehist-autosave-interval 600) (savehist-additional-variables '(kill-ring ; clipboard register-alist ; macros mark-ring global-mark-ring ; marks search-ring regexp-search-ring))) (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 (add-to-list 'completion-styles 'prescient) (setq prescient-history-length 10000) (prescient-persist-mode +1))) (use-package vertico-prescient :after prescient :config (progn (vertico-prescient-mode +1))) (use-package marginalia :after vertico :commands (marginalia-mode marginalia-cycle) :hook (after-init . marginalia-mode) :general (:keymaps 'minibuffer-local-map "M-A" #'marginalia-cycle)) (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 consult ;; Enable automatic preview at point in the *Completions* buffer. :hook (completion-list-mode . consult-preview-at-point-mode) :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 smerge-mode :defer 2 :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 (use-package winner :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) (evil-mode +1)) :general (:states 'motion "C-;" #'evil-avy-goto-line "C-" #'evil-switch-to-windows-last-buffer) (:states 'normal ";" #'evil-ex) (:states '(normal) "g ." #'eglot-code-actions "g s" #'consult-imenu "g S" #'consult-eglot-symbols)) (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 :general ( :keymaps 'evil-collection-unimpaired-mode-map :states 'normal "[ d" #'evil-collection-unimpaired-previous-error "] d" #'evil-collection-unimpaired-next-error) :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))) (with-eval-after-load "evil" (evil-define-operator my-evil-comment-or-uncomment (beg end) "Toggle comment for the region between BEG and END." (interactive "") (comment-or-uncomment-region beg end)) (evil-define-key 'normal 'global (kbd "gc") 'my-evil-comment-or-uncomment)) (use-package evil-matchit :after evil :defer 2 :config (progn (add-hook 'dired-mode-hook #'turn-off-evil-matchit-mode) (global-evil-matchit-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-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 :commands (corfu-mode global-corfu-mode) :hook ((prog-mode . corfu-mode) (shell-mode . corfu-mode)) :custom ;; Hide commands in M-x which do not apply to the current mode. (read-extended-command-predicate #'command-completion-default-include-p) ;; Disable Ispell completion function. As an alternative try `cape-dict'. (text-mode-ispell-word-completion nil) (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) :config (progn (global-corfu-mode +1))) (use-package cape :after (corfu) :commands (cape-dabbrev cape-file cape-elisp-block) :general (:states 'insert "C-x C-f" #'cape-file "C-c p" 'cape-prefix-map) :init ;; Add to the global default value of `completion-at-point-functions' which is ;; used by `completion-at-point'. (add-hook 'completion-at-point-functions #'cape-file) (add-hook 'completion-at-point-functions #'cape-elisp-block)) (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 (with-eval-after-load 'help (setq help-window-keep-selected t)) (use-package eldoc :defer 5 :config (progn (setq eldoc-idle-delay 0.2 eldoc-echo-area-use-multiline-p nil) (global-eldoc-mode +1))) (use-package ehelp :defer 15 :general ([remap help-map] 'electric-help-map)) ;;; Files ;;;; Auto-saving (setq auto-save-default t auto-save-interval 300 auto-save-timeout 30) ;;;; Auto-reloading (use-package autorevert :ensure nil :commands (auto-revert-mode global-auto-revert-mode) :hook (after-init . global-auto-revert-mode) :custom (auto-revert-verbose nil)) (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)) ;; https://coredumped.dev/2025/06/18/making-tramp-go-brrrr./ (setq remote-file-name-inhibit-locks t remote-file-name-inhibit-auto-save-visited t tramp-use-scp-direct-remote-copying t tramp-copy-size-limit 1048576 tramp-default-method "rsync") (connection-local-set-profile-variables 'remote-direct-async-process '((tramp-direct-async-process . t))) (setq magit-tramp-pipe-stty-settings 'pty) (with-eval-after-load 'compile (remove-hook 'compilation-mode-hook #'tramp-compile-disable-ssh-controlmaster-options)))) ;;; Directories (setq dired-listing-switches "-alhv" dired-kill-when-opening-new-dired-buffer t dired-movement-style 'bounded-files dired-recursive-deletes (if delete-by-moving-to-trash 'always 'top)) (add-hook 'dired-mode-hook #'dired-hide-details-mode) (setq dired-omit-files (concat "\\`[.]\\'" "\\|\\(?:\\.js\\)?\\.meta\\'" "\\|\\.\\(?:elc|a\\|o\\|pyc\\|pyo\\|swp\\|class\\)\\'" "\\|^\\.DS_Store\\'" "\\|^\\.\\(?:svn\\|git\\)\\'" "\\|^\\.ccls-cache\\'" "\\|^__pycache__\\'" "\\|^\\.project\\(?:ile\\)?\\'" "\\|^flycheck_.*" "\\|^flymake_.*")) (add-hook 'dired-mode-hook #'dired-omit-mode) (with-eval-after-load 'dired (bind-key "%" #'dired-create-empty-file 'dired-mode-map)) ;;; Shells (use-package shell :defer t :general (:keymaps 'shell-mode-map "C-d" #'comint-delchar-or-maybe-eof)) (use-package comint :defer t :ensure nil :general (:keymaps 'comint-mode-map "C-c C-l" #'counsel-shell-history)) ;;; Editing (if (fboundp 'kill-ring-deindent-mode) (kill-ring-deindent-mode +1)) (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 ;;;; 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 (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 (use-package nix-mode :defer t :config (progn (setq nix-mode-use-smie nil nix-indent-function #'smie-indent-line))) (use-package nix-ts-mode :defer 2 :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 aggressive-indent :ghook '(emacs-lisp-mode-hook clojure-mode-hook racket-mode-hook)) (use-package elisp-refs :commands (elisp-refs-function elisp-refs-macro elisp-refs-variable elisp-refs-special elisp-refs-symbol)) ;;;; org (use-package org :defer t :config (progn (setq org-ellipsis "…" org-modules nil org-directory "~/Documents/org"))) ;;;; vimscript/tridactylrc (use-package vimscript-ts-mode :mode ("\\.?tridactylrc" . vimscript-ts-mode)) ;;;; 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 :bind (("s-p" . #'project-find-file) ("s-o" . #'project-switch-project)) :general (:keymaps 'project-prefix-map "s" #'project-search) :config (progn (setq project-switch-commands #'magit-project-status) (with-eval-after-load 'evil-ex (evil-ex-define-cmd "pb" #'project-switch-to-buffer) (evil-ex-define-cmd "psw[itch]" #'project-switch-project)))) (defun my/project-or-not-find-file (&optional include-all) (interactive "P") (if (project-current nil) (call-interactively #'project-find-file) (call-interactively #'find-file))) (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 with-editor :defer t :general ( :keymaps 'with-editor-mode-map "s-" #'with-editor-finish)) (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) (:keymaps 'magit-mode-map "z" nil) (:keymaps 'magit-status-mode-map "z" #'magit-stash) (:keymaps 'magit-section-mode-map "C-" nil) :config (progn (add-to-list 'magit-git-environment "NO_COLOR=1") (with-eval-after-load 'evil-ex (evil-ex-define-cmd "mg" #'magit-status)) (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 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 10 :ghook 'text-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) (use-package yaml-ts-mode :ensure nil :mode (("\\.ya?ml\\'" . yaml-ts-mode))) (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 (progn (add-hook 'before-save-hook #'eglot-format-buffer nil 'local) (when (bound-and-true-p apheleia-mode) (apheleia-mode -1))) (remove-hook 'before-save-hook #'eglot-format-buffer 'local))) (use-package eglot :ensure nil :defer 3 :commands (eglot-ensure eglot-rename eglot-format-buffer) :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 fish-mode-hook css-base-mode-hook lua-mode-hook markdown-mode-hook nim-mode-hook html-mode-hook nix-mode-hook templ-ts-mode-hook toml-ts-mode-hook just-ts-mode-hook haskell-mode-hook) #'eglot-ensure) :config (progn (when (eq system-type 'darwin) ;; fix jumpy line-height (setq-default eglot-code-action-indicator ">")) (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-mode toml-ts-mode) . ("taplo" "lsp" "stdio")) ((just-mode just-ts-mode) . ("just-lsp")) (fish-mode . ("fish-lsp" "start")) (markdown-mode . ,(eglot-alternatives '(("marksman" "server") ("zk" "lsp") "iwes"))))) (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) (let ((nixfiles (expand-file-name "~/projects/alin.ovh/nixfiles"))) (setq-default eglot-workspace-configuration `( :yaml (:keyOrdering :json-false) :nixd ( :nixpkgs (:expr ,(format "import (builtins.getFlake \"%s\").inputs.nixpkgs" nixfiles)) :formatting (:command ("nixfmt")) :options ( :darwin (:expr ,(format "(builtins.getFlake \"%s\").darwinConfigurations.%s.options" nixfiles (system-name))) :home-manager (:expr ,(format "(builtins.getFlake \"%s\").homeConfigurations.\"%s@%s\".options" nixfiles user-login-name (system-name))))) :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) ;;;; Reformat on save (use-package apheleia :ghook 'prog-mode-hook :config (progn (setf (alist-get 'shfmt apheleia-formatters) '("shfmt")) (setq apheleia-formatters (append apheleia-formatters '((golines "golines") (taplo "taplo" "format" "-") (sqlfluff "sqlfluff" "format" "--stdin-filename" filepath "-") (prettier-gotmpl "prettier" "--stdin-filepath" filepath "--parser=go-template" (apheleia-formatters-indent "--use-tabs" "--tab-width"))))) (setf (alist-get 'sql-mode apheleia-mode-alist) 'sqlfluff) (setq apheleia-mode-alist (append apheleia-mode-alist '((toml-ts-mode . taplo)))))) ;;; Take me to my leader (my-leader-def "" nil "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" #'consult-eglot-symbols "m" #'magit-status "r" #'eglot-rename "q" #'evil-delete-buffer "p" '(:keymap project-prefix-map :package project) "v" #'split-window-right "o" #'other-window "u" #'universal-argument ";" #'execute-extended-command "a" #'eglot-code-actions "bb" #'consult-buffer "bx" #'kill-current-buffer "br" #'revert-buffer "bk" #'kill-buffer "dd" #'dired "e" '(:keymap envrc-command-map :package envrc) "fs" #'save-buffer "ff" #'my/project-or-not-find-file "fw" #'write-file "fd" #'my/delete-file-and-buffer "fr" #'crux-rename-file-and-buffer "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 "l" #'link-hint-open-link "bz" #'bury-buffer "iu" #'insert-char "ss" #'consult-ripgrep "xe" #'eval-last-sexp "xx" #'eval-defun "xi" #'consult-imenu "z" '(:keymap ssh-deploy-prefix-map :package ssh-deploy) "/" #'swiper) (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: