;;; 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 (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-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 tabnine-core :config (progn (setq tabnine-binaries-folder "~/.local/tabnine") (with-demoted-errors "TabNine error: %s" (when (tabnine--executable-path) (global-tabnine-mode))) (define-key tabnine-completion-map (kbd "TAB") #'tabnine-accept-completion) (define-key tabnine-completion-map (kbd "") #'tabnine-accept-completion) (define-key tabnine-completion-map (kbd "M-f") #'tabnine-accept-completion-by-word) (define-key tabnine-completion-map (kbd "M-") #'tabnine-accept-completion-by-line) (define-key tabnine-completion-map (kbd "C-e") #'tabnine-accept-completion-by-line) (define-key tabnine-completion-map (kbd "") #'tabnine-accept-completion-by-line) (define-key tabnine-completion-map (kbd "C-g") #'tabnine-clear-overlay) (define-key tabnine-completion-map (kbd "M-[") #'tabnine-next-completion) (define-key tabnine-completion-map (kbd "M-]") #'tabnine-previous-completion)) :init (progn (advice-add 'tabnine-start-process :around #'quiet) (add-hook 'kill-emacs-hook #'tabnine-kill-process))) (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: