;;; init.el --- Configuration entry point -*- lexical-binding: t -*- ;;; Commentary: ;;; Code: ;; Some other config files (add-to-list 'load-path "~/.emacs.d/elisp") ;; Set package dir to follow no-littering conventions (setq package-user-dir "~/.emacs.d/var/elpa") ;; Use melpa (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) ;; Ensure use-package is installed (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) ;; use-package (eval-when-compile (require 'use-package) (setq use-package-always-ensure t package-user-dir "~/.emacs.d/var/elpa")) ;; no-littering (use-package no-littering :autoload (no-littering-theme-backups no-littering-expand-etc-file-name) :init (no-littering-theme-backups) (setq custom-file (no-littering-expand-etc-file-name "custom.el"))) ;; diminish (use-package diminish :config (diminish 'visual-line-mode) (diminish 'abbrev-mode)) ;; Private config loading (require 'private nil t) (defun my/get-private (key) "Get the private config variable KEY from the private configuration file." (alist-get key my/private-config)) ;; basic stuff (use-package emacs :hook (;;(emacs-lisp-mode . my/-emacs-lisp-mode-setup-evil-lookup) ;;(prog-mode . electric-pair-local-mode) ((text-mode tex-mode prog-mode) . auto-fill-mode) ((text-mode tex-mode prog-mode) . my/-enable-show-trailing-whitespace)) :init (defun my/-enable-show-trailing-whitespace () (setq-local show-trailing-whitespace t)) ;; (defun my/-emacs-lisp-mode-setup-evil-lookup () ;; (setq-local evil-lookup-func ;; #'my/describe-symbol-at-point)) (defun my/describe-symbol-at-point () "Calls `describe-symbol' on the return value of `symbol-at-point'." (interactive) (let ((form (symbol-at-point))) (if (consp form) (describe-symbol (cadr form)) (describe-symbol form)))) ;; Increase responsiveness (setq gc-cons-threshold 80000000 read-process-output-max (* 1024 1024)) ;; 1mb (global-so-long-mode 1) ;; Terminal mouse support (xterm-mouse-mode 1) ;; Make cursor more visible (global-hl-line-mode 1) (blink-cursor-mode -1) ;; Enable all disabled stuff (setq disabled-command-function nil) ;; Stop some annoying stuff (setq extended-command-suggest-shorter nil suggest-key-bindings nil) ;; Better scrolling (setq mouse-scroll-delay 0 scroll-conservatively 10 scroll-margin 2 scroll-preserve-screen-position t) ;; Make show paren instant (setq show-paren-delay 0) (show-paren-mode 1) ;; Display line numbers (global-display-line-numbers-mode 1) ;; Allow the frame to be any size (setq frame-resize-pixelwise t) ;; Don't use a gtk file picker (setq use-file-dialog nil) ;; Make yes-or-no-p less verbose (and not use windows) (setq use-dialog-box nil use-short-answers t) ;; Disable startup screen (setq inhibit-startup-screen t server-client-instructions nil) ;; show column numbers (column-number-mode 1) ;; Disable the menu and tool bars (menu-bar-mode -1) (tool-bar-mode -1) ;; No scroll bars (scroll-bar-mode -1) ;; Visual line mode (global-visual-line-mode 1) ;; Make some commands easier to enter multiple times (repeat-mode 1) ;; Easier buffer navigation (keymap-global-set "C-c <" #'previous-buffer) (keymap-global-set "C-c >" #'next-buffer) (keymap-global-set "C-c k" #'previous-buffer) (keymap-global-set "C-c j" #'next-buffer) ;; Set fonts (add-to-list 'default-frame-alist '(font . "FiraCode Nerd Font Mono-12")) (add-hook 'server-after-make-frame-hook (lambda () (set-fontset-font t 'japanese-jisx0208 "IPAGothic"))) ;; Enable color in compilation buffers (add-hook 'compilation-filter-hook 'ansi-color-compilation-filter) ;; Some settings for programming (setq-default indent-tabs-mode nil tab-width 4 fill-column 80 comment-multi-line t comment-empty-lines 'eol) (add-to-list 'auto-mode-alist '("\\.[cC][nN][fF]\\'" . conf-mode)) (keymap-set emacs-lisp-mode-map "C-c C-r" #'eval-region) (defun my/-fix-emacs-lisp-mode-system-files () (when (string-prefix-p lisp-directory buffer-file-name) ;; system Emacs files use tab characters and look weird without this. (setq-local tab-width 8))) (add-hook 'emacs-lisp-mode-hook #'my/-fix-emacs-lisp-mode-system-files) ;; Tree sitter download locations (setq treesit-language-source-alist '((c "https://github.com/tree-sitter/tree-sitter-c") (cpp "https://github.com/tree-sitter/tree-sitter-cpp") (java "https://github.com/tree-sitter/tree-sitter-java") (python "https://github.com/tree-sitter/tree-sitter-python") (rust "https://github.com/tree-sitter/tree-sitter-rust") (json "https://github.com/tree-sitter/tree-sitter-json") (yaml "https://github.com/ikatyang/tree-sitter-yaml") (css "https://github.com/tree-sitter/tree-sitter-css") (go "https://github.com/tree-sitter/tree-sitter-go") (gomod "https://github.com/camdencheek/tree-sitter-go-mod") (javascript "https://github.com/tree-sitter/tree-sitter-javascript") (bash "https://github.com/tree-sitter/tree-sitter-bash") (cmake "https://github.com/uyha/tree-sitter-cmake") (blueprint "https://github.com/huanie/tree-sitter-blueprint") (kdl "https://github.com/tree-sitter-grammars/tree-sitter-kdl"))) ;; Tree sitter major mode conversions (setq major-mode-remap-alist '((c-mode . c-ts-mode) (c++-mode . c++-ts-mode) (c-or-c++-mode . c-or-c++-ts-mode) (python-mode . python-ts-mode) (java-mode . java-ts-mode) (rust-mode . rust-ts-mode) (json-mode . json-ts-mode) (yaml-mode . yaml-ts-mode) (css-mode . css-ts-mode) (js-mode . js-ts-mode) (cmake-mode . cmake-ts-mode))) (defun my/treesit-compile-all (force) "Compile all the modules defined in `treesit-language-source-alist'. If FORCE, recompile all modules, even ones that are already compiled. Interactively, force the recompile if called with a prefix." (interactive "P") (let ((did-build nil)) (dolist (lang treesit-language-source-alist) (when (or force (not (treesit-language-available-p (car lang)))) (treesit-install-language-grammar (car lang)) (setq did-build t))) (unless did-build (message "All defined parsers installed!"))))) (use-package midnight :ensure nil :config (add-to-list 'clean-buffer-list-kill-never-buffer-names "*mu4e-main*") (add-to-list 'clean-buffer-list-kill-never-buffer-names "*Async-native-compile-log*") (add-to-list 'clean-buffer-list-kill-never-buffer-names "*dashboard*") (add-to-list 'clean-buffer-list-kill-never-buffer-names "*elfeed-search*") (midnight-mode 1)) (defvar my/kill-some-buffers-exclude-names '("*mu4e-main*" "*Async-native-compile-log*" "*dashboard*" "*elfeed-search*" "*Messages*" "*scratch*") "List of literal buffer names that `my/kill-some-buffers' should not kill.") (defun my/kill-some-buffers-excluded-buffer-p (buffer) "Return non-nil if BUFFER should be excluded from `my/kill-some-buffers'." (cl-find (buffer-name buffer) my/kill-some-buffers-exclude-names :test 'equal)) (defun my/buffer-visible-p (buffer) "Return non-nil if BUFFER is visible. BUFFER can be a string or a buffer." (cond ((stringp buffer) (not (string-prefix-p " " buffer))) ((bufferp buffer) (and (stringp (buffer-name buffer)) (my/buffer-visible-p (buffer-name buffer)))) (t (signal 'wrong-type-argument `((or bufferp stringp) ,buffer))))) (defvar my/kill-some-buffers-default-pred 'my/buffer-visible-p "Default predicate for `my/kill-some-buffers'.") (defun my/kill-some-buffers-prompt-for (buffer) "Generate a prompt for BUFFER." (let* ((process (get-buffer-process buffer)) (process-p (and (process-live-p process) (not (process-query-on-exit-flag process)))) (modified-p (and (buffer-file-name buffer) (buffer-modified-p buffer)))) (format "Buffer \"%s\" %s. Kill? " (buffer-name buffer) (cond ((and process-p modified-p) "HAS BEEN EDITED AND HAS A LIVE PROCESS") (modified-p "HAS BEEN EDITED") (process-p "HAS A LIVE PROCESS") (t "is unmodified"))))) (cl-defun my/kill-some-buffers (&optional auto-unmod pred) "Improved version of `kill-some-buffers'. Ask the user weather to kill each visible buffer whose name is not in `my/kill-some-buffers-exclude-names'. When AUTO-UNMOD is non-nil, as it is with a prefix argument, automatically kill unmodified buffers, and then ask about the rest. When PRED is non-nil, it is a function that will be run in each buffer (not just visible ones). If it returns t, that buffer will be considered for killing. If PRED is nil, the value of `my/kill-some-buffers-default-pred' is used." (interactive "P") ;; we already ask, no need to do it again (let ((kill-buffer-query-functions nil) (all-action (when auto-unmod 'unmod)) (ask-again-buffers) (to-kill)) (cl-flet ((ask-about (buffer allow-unmod) (unless all-action (read-answer (my/kill-some-buffers-prompt-for buffer) `(("yes" ?y "save and kill this buffer") ("no" ?n "skip this buffer") ("all" ?! "save and kill all remaining buffers") ("nosave" ?l "kill this buffer without saving") ,@(when allow-unmod '(("unmod" ?a "kill unmodified buffers, ask about the rest"))) ("quit" ?q "exit"))))) (act-on (ans buffer allow-unmod) (when (equal ans "all") (setq all-action 'all)) (when (and allow-unmod (equal ans "unmod")) (setq all-action 'unmod)) (cond ((and (eq all-action 'unmod) (buffer-file-name buffer) (buffer-modified-p buffer)) (push buffer ask-again-buffers)) ((or (eq all-action 'all) (eq all-action 'unmod) (equal ans "yes")) (when (buffer-file-name buffer) (with-current-buffer buffer (save-buffer))) (push buffer to-kill)) ((equal ans "nosave") (with-current-buffer buffer (set-buffer-modified-p nil)) (push buffer to-kill)) ;; Skip buffer ;; ((equal ans "no")) ((equal ans "quit") (cl-return-from my/kill-some-buffers))))) (dolist (buffer (buffer-list)) (when (and (not (my/kill-some-buffers-excluded-buffer-p buffer)) (funcall (or pred my/kill-some-buffers-default-pred) buffer)) (act-on (ask-about buffer t) buffer t))) (setq all-action nil) (dolist (buffer ask-again-buffers) (act-on (ask-about buffer nil) buffer nil)) ;; Do this last so that tty frames don't auto-close half way through (dolist (buffer to-kill) (kill-buffer buffer))))) (keymap-global-set "C-x K" 'my/kill-some-buffers) (use-package tab-bar :ensure nil :init (setq tab-bar-show 1 tab-bar-tab-hints t icon-preference '(symbol text image emoji)) (tab-bar-mode 1)) ;; jinx (better flyspell) (use-package jinx :hook (emacs-startup . global-jinx-mode) :config (evil-define-key 'normal 'global "z=" #'jinx-correct)) ;; recentf (use-package recentf :init (setq recentf-exclude `("^/tmp/.*" "^~/.mail/[^/]/Drafts/.*" ,(format "^%svar/elpa/.*" user-emacs-directory) ,(format "^%svar/elfeed/.*" user-emacs-directory) ,(format "^%svar/gnus/.*" user-emacs-directory) ,(format "^%svar/ellama-sessions/.*" user-emacs-directory) ,(format "^%setc/gnus/.*" user-emacs-directory) ,(format "^%svar/bookmark-default.el" user-emacs-directory))) :bind ("C-c r" . recentf) :config (recentf-mode 1)) ;; bookmarks (use-package bookmark :ensure nil :bind ("C-c b" . my/bookmark-find-file) :config (defun my/bookmark-find-file (&optional name) "Run `find-file' in or on bookmark NAME. If NAME points to a directory, run `find-file' with `default-directory' in that directory. Otherwise, run `find-file' on that file." (interactive (list (bookmark-completing-read "Find file in" bookmark-current-bookmark))) (unless name (error "No bookmark specified")) (bookmark-maybe-historicize-string name) (when-let ((file (bookmark-get-filename name))) (if (file-directory-p file) (let ((default-directory (file-name-as-directory file))) (call-interactively 'find-file)) (find-file file))))) ;; kitty keyboard protocol (use-package kkp :config (global-kkp-mode 1) (defun my/-kkp-after-terminal-setup (&rest _) ;; Make tab and backtab work properly (define-key input-decode-map [(control ?i)] [tab]) (define-key input-decode-map [(control ?I)] [backtab])) (defun my/-kkp-after-terminal-teardown (&rest _) (define-key input-decode-map [(control ?i)] nil t) (define-key input-decode-map [(control ?I)] nil t)) (advice-add 'kkp--terminal-setup :after 'my/-kkp-after-terminal-setup) (advice-add 'kkp--terminal-teardown :after 'my/-kkp-after-terminal-teardown) (defun my/quoted-insert (arg) "Insert the next character using read-key, not read-char." (interactive "*p") ;; Source: https://github.com/benjaminor/kkp/issues/11 (let ((char (read-key))) ;; Ensure char is treated as a character code for insertion (unless (characterp char) (user-error "%s is not a valid character" (key-description (vector char)))) (when (numberp char) (while (> arg 0) (insert-and-inherit char) (setq arg (1- arg)))))) (keymap-global-set "C-q" #'my/quoted-insert) (defun my/-kkp-fix-map-y-or-n-p (oldfun &rest args) "Fix `map-y-or-n-p' when used in a terminal with kkp enabled." (let ((status (kkp--terminal-has-active-kkp-p))) (unwind-protect (progn (when status (kkp--terminal-teardown (kkp--selected-terminal))) (apply oldfun args)) (when (and status (not (kkp--terminal-has-active-kkp-p))) ;; this does async stuff that will make kitty send characters after ;; Emacs exits. We prevent that by not re-enabling if this frame (or ;; Emacs) is about to die (let ((will-die)) (mapbacktrace (lambda (_evald func _args _flags) (when (or (eq func 'save-buffers-kill-emacs) (eq func 'server-save-buffers-kill-terminal)) (setq will-die t)))) (unless will-die (kkp-enable-in-terminal))))))) (advice-add #'map-y-or-n-p :around #'my/-kkp-fix-map-y-or-n-p)) ;; mozc (require 'mozc nil t) (setq default-input-method "japanese-mozc") ;; undo-tree (use-package undo-tree :defer nil :hook (undo-tree-visualizer-mode . my/-undo-tree-visualizer-mode-setup) :config (defun my/-undo-tree-visualizer-mode-setup () (visual-line-mode -1) (setq truncate-lines t)) (global-undo-tree-mode)) ;; evil (use-package evil :init (setq evil-want-integration t evil-want-C-d-scroll nil evil-want-keybinding nil evil-undo-system 'undo-tree evil-search-module 'isearch evil-respect-visual-line-mode t) :config (evil-mode 1) (evil-define-key '(normal visual motion) proced-mode-map "u" #'proced-unmark) (evil-define-key '(normal visual motion) dired-mode-map "u" #'dired-unmark) (evil-define-key '(normal visual motion) profiler-report-mode-map (kbd "TAB") #'profiler-report-toggle-entry) (eldoc-add-command 'evil-insert 'evil-append 'evil-insert-line 'evil-append-line)) (use-package evil-collection :after evil :diminish evil-collection-unimpaired-mode :config (evil-collection-init)) (use-package evil-surround :after evil :config (evil-define-key 'operator evil-surround-mode-map "z" #'evil-surround-edit "Z" #'evil-Surround-edit) (evil-define-key 'visual evil-surround-mode-map "gz" #'evil-surround-region "gZ" #'evil-Surround-region) (global-evil-surround-mode 1)) (use-package evil-terminal-cursor-changer :after evil :config (evil-terminal-cursor-changer-activate)) (use-package evil-numbers :after evil :bind (("C-c =" . evil-numbers/inc-at-pt) ("C-c +" . evil-numbers/inc-at-pt) ("C-c -" . evil-numbers/dec-at-pt) ("C-c C-=" . evil-numbers/inc-at-pt-incremental) ("C-c C-+" . evil-numbers/inc-at-pt-incremental) ("C-c C--" . evil-numbers/dec-at-pt-incremental))) (use-package evil-cleverparens :hook ((prog-mode . my/-enable-evil-cleverparens) (evil-cleverparens-mode . paredit-mode)) :bind (:map paredit-mode-map ("C-" . paredit-RET) ("C-RET" . paredit-RET) :map evil-cleverparens-mode-map ("C-c o" . evil-cp-open-below-form)) :custom (evil-cleverparens-use-s-and-S nil) :config (eldoc-add-command 'paredit-RET 'paredit-open-round 'paredit-open-angled 'paredit-open-bracket 'paredit-open-angled 'paredit-open-parenthesis 'delete-indentation 'evil-cp-insert 'evil-cp-append 'evil-cp-insert-at-beginning-of-form 'evil-cp-insert-at-end-of-form) (keymap-unset evil-cleverparens-mode-map " M-o" t) (defun my/-enable-evil-cleverparens () (if (member major-mode '(lisp-mode emacs-lisp-mode lisp-interaction-mode)) (evil-cleverparens-mode 1) (electric-pair-local-mode 1))) (cl-defun my/range-inside-thing-p (thing beg end &optional no-edge) "Return non-nil if BEG and END fall inside the bounds of THING. With NO-EDGE, return nil if beg or end fall on the edge of the range." (save-excursion ;; this fixes that fact that `thing-at-point-bounds-of-string-at-point' ;; errors if called at the end of the buffer (condition-case _ (let ((sb (progn (goto-char beg) (bounds-of-thing-at-point thing))) (eb (progn (goto-char end) (bounds-of-thing-at-point thing)))) (and sb eb (equal sb eb) (or (not no-edge) (and (/= beg (car sb)) (< beg (cdr sb)) (/= end (car sb)) (< end (cdr sb)))))) ;; if the error happens, we aren't in a string (wrong-type-argument nil)))) (defun my/-evil-cp-region-ok-p-no-string (oldfun beg end) (or (and (sp-point-in-comment beg) (sp-point-in-comment end)) (and (sp-point-in-string beg) (sp-point-in-string end) (my/range-inside-thing-p 'string beg end t)) (funcall oldfun beg end))) (defun my/column-num-at-pos (pos) "Return the column number at POS." (save-excursion (goto-char pos) (current-column))) (defun my/-evil-cp-block-ok-p-no-string (oldfun beg end) (when (> beg end) (cl-rotatef beg end)) (or (save-excursion (goto-char beg) (let ((start-off (current-column)) (end-off (my/column-num-at-pos end))) (cl-block nil (dotimes (_ (count-lines beg end) t) (let ((bol (pos-bol))) (unless (sp-region-ok-p (+ bol start-off) (+ bol end-off)) (cl-return)) (forward-line)))))) (funcall oldfun beg end))) (advice-add 'sp-region-ok-p :around 'my/-evil-cp-region-ok-p-no-string) (advice-add 'evil-cp--balanced-block-p :around 'my/-evil-cp-block-ok-p-no-string)) ;; make lisp editing nicer (use-package aggressive-indent :hook (prog-mode . aggressive-indent-mode) :config (add-to-list 'aggressive-indent-protected-commands #'evil-undo)) ;; and nicer (use-package adjust-parens :hook (prog-mode . adjust-parens-mode) :config (defun my/lisp-indent-adjust-parens () "Like `lisp-indent-adjust-parens', but got to first char on line first. Also, this works even if the region is active (it indents every line in the region)." (interactive) (save-mark-and-excursion (let ((end (mark t)) (line-count 1) (indent-cols)) (when (and (region-active-p) end) (setq mark-active nil line-count (count-lines (point) end)) (when (> (point) end) (let ((start (point))) (goto-char end) (setq end start)))) ;; find the indentation column of each line (save-excursion (dotimes (_ line-count) (back-to-indentation) (push (- (point) (pos-bol)) indent-cols) (forward-line)) (cl-callf nreverse indent-cols)) (cl-loop repeat line-count for indent-col in indent-cols for bol = (pos-bol) do (back-to-indentation) ;; skip this line if the indentation has changed when (= (- (point) bol) indent-col) do (lisp-indent-adjust-parens) ;; if the indent failed, stop (when (= (- (point) bol) indent-col) (cl-return)) do (forward-line))))) (defun my/lisp-dedent-adjust-parens () "Like `lisp-dedent-adjust-parens', but got to first char on line first. Also, this works even if the region is active (it just jumps to the first line in the region and indents once)." (interactive) (save-mark-and-excursion (let ((end (mark t))) (when (and (region-active-p) end) (setq mark-active nil) (when (> (point) end) (goto-char end)))) (back-to-indentation) (lisp-dedent-adjust-parens))) (eldoc-add-command 'my/lisp-indent-adjust-parens 'my/lisp-dedent-adjust-parens 'lisp-indent-adjust-parens 'lisp-dedent-adjust-parens) (evil-define-key '(normal visual) adjust-parens-mode-map (kbd "") #'my/lisp-indent-adjust-parens (kbd "") #'my/lisp-dedent-adjust-parens)) ;; for when the files are just too large (use-package vlf :demand t :config (require 'vlf-setup)) ;; allow copy from terminal (use-package xclip :config (xclip-mode 1)) ;; which-key (use-package which-key :diminish which-key-mode :config (which-key-mode 1)) ;; avy (use-package avy :bind (("C-c C-j" . avy-resume) ("M-s s" . evil-avy-goto-char-2) ("M-s S" . evil-avy-goto-line)) :init (define-minor-mode my/evil-avy-mode "A minor mode for binding avy commands to s and S in evil's normal and visual states." :keymap (make-sparse-keymap)) (evil-define-key '(normal visual operator motion) my/evil-avy-mode-map "s" #'evil-avy-goto-char-2 "S" #'evil-avy-goto-line) (define-globalized-minor-mode my/evil-avy-global-mode my/evil-avy-mode (lambda () (my/evil-avy-mode 1)) :predicate '((not magit-mode dired-mode proced-mode mu4e-main-mode mu4e-view-mode mu4e-headers-mode ibuffer-mode calc-mode calc-trail-mode gnus-group-mode) t)) (my/evil-avy-global-mode 1) :config (avy-setup-default)) ;; ace-window (use-package ace-window :diminish ace-window-mode :bind ("M-o" . ace-window) :init (setq aw-scope 'frame aw-minibuffer-flag t)) ;; savehist (use-package savehist :config (savehist-mode 1)) ;; vertico (use-package vertico :bind (:map vertico-map ("C-RET" . vertico-exit-input) ("C-" . vertico-exit-input) ("C-S-k" . kill-line) ("C-k" . vertico-previous) ("C-j" . vertico-next) ("RET" . vertico-directory-enter) ("DEL" . vertico-directory-delete-char) ("M-DEL" . vertico-directory-delete-word)) :hook (minibuffer-setup . cursor-intangible-mode) :init (defun my/crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'my/crm-indicator) (setq vertico-cycle t enable-recursive-minibuffers t ;; read-extended-command-predicate #'command-completion-default-include-p read-extended-command-predicate nil minibuffer-prompt-properties '(read-only t ;; noindent 3 cursor-intangible t face minibuffer-prompt)) (vertico-mode 1) ;; for jinx (require 'vertico-multiform) (add-to-list 'vertico-multiform-categories '(jinx grid (vertico-grid-annotate . 20))) (vertico-multiform-mode 1)) ;; orderless (use-package orderless :autoload orderless-define-completion-style :hook (text-mode . my/-setup-text-mode-completion-styles) :init (defun my/-setup-text-mode-completion-styles () (setq-local completion-styles '(basic))) (orderless-define-completion-style my/orderless-with-initialism (orderless-matching-styles '(orderless-initialism orderless-regexp))) (setq orderless-matching-styles '(orderless-regexp) completion-styles '(orderless basic) completion-category-defaults nil completion-category-overrides '((file (styles basic partial-completion)) (command (styles my/orderless-with-initialism basic))))) ;; marginalia (use-package marginalia :bind (:map minibuffer-local-map ("M-a" . marginalia-cycle)) :init (marginalia-mode 1)) ;; embark (use-package embark :bind (("C-," . embark-act) ("C-;" . embark-dwim) :map help-map ("B" . embark-bindings) :map embark-symbol-map ("h" . helpful-symbol) :map embark-become-file+buffer-map ("b" . consult-buffer) ("B" . switch-to-buffer)) :init (setq embark-quit-after-action nil embark-indicators '(embark-minimal-indicator embark-isearch-highlight-indicator embark-highlight-indicator)) :config (defvar-keymap my/embark-string-map :doc "Keymap for Embark string actions." :parent embark-expression-map "R" 'repunctuate-sentences) (defun my/embark-target-string () "Target the string at point." (if-let (((not (eobp))) ; prevent next line from causing errors (bounds (bounds-of-thing-at-point 'string))) (append (list 'string (buffer-substring-no-properties (car bounds) (cdr bounds))) bounds))) (add-to-list 'embark-around-action-hooks '(repunctuate-sentences embark--mark-target)) (add-to-list 'embark-keymap-alist '(string my/embark-string-map)) (add-to-list 'embark-target-finders 'my/embark-target-string) (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none)))) (evil-define-key '(normal motion) org-mode-map (kbd "C-,") #'embark-act)) ;; consult (use-package consult :bind (("C-s" . consult-line) ("C-x b" . consult-buffer) ("C-S-s" . consult-ripgrep) ("C-x C-S-f" . consult-fd) ("C-x c k" . consult-keep-lines) ("C-x c f" . consult-focus-lines) ("C-x c r" . consult-recent-file) ("C-x c b" . consult-bookmark) ("C-x c d" . consult-fd) ("C-x c g" . consult-ripgrep) ("C-x c y" . consult-yank-from-kill-ring) ("M-g i" . consult-imenu) ("M-g I" . consult-imenu-multi) ("M-g r" . consult-imenu-multi) :map help-map ("TAB". consult-info) ("C-m" . consult-man)) :hook (minibuffer-setup . my/consult-setup-minibuffer-completion) :init (defun my/consult-setup-minibuffer-completion () (setq-local completion-in-region-function #'consult-completion-in-region)) (evil-declare-motion #'consult-line)) (use-package consult-eglot :commands consult-eglot-symbols) ;; wgrep (use-package wgrep) ;; integration for embark and consult (use-package embark-consult :hook (embark-collect-mode . consult-preview-at-point-mode)) ;; corfu (autocomplete) (use-package corfu :bind (("M-" . completion-at-point) :map corfu-map ("C-j" . corfu-next) ("C-k" . corfu-previous) ("M-SPC" . corfu-insert-separator) ("M-m" . my/corfu-move-to-minibuffer)) :init (defun my/corfu-move-to-minibuffer () (interactive) (when completion-in-region--data (let ((completion-extra-properties corfu--extra) (completion-cycle-threshold completion-cycling)) (apply #'consult-completion-in-region completion-in-region--data)))) (setq corfu-cycle t corfu-auto t corfu-on-exact-match nil corfu-popupinfo-delay '(1.0 . 0.5) completion-cycle-threshold nil global-corfu-minibuffer ;; only enable corfu in the minibuffer in graphical frames (lambda () (and (display-graphic-p) (not (eq (current-local-map) read-passwd-map))))) (global-corfu-mode 1) (corfu-popupinfo-mode 1) :config (add-to-list 'corfu-continue-commands #'my/corfu-move-to-minibuffer) (defun my/help-buffer-exists-p () "Return if the buffer that `help-buffer' would, or nil if it doesn't exist." (or (and help-xref-following (derived-mode-p 'help-mode)) (get-buffer "*Help*"))) (defun my/-corfu-popupinfo-close-help-buffer (oldfun &rest args) (if (derived-mode-p 'emacs-lisp-mode) (let ((help-buf (my/help-buffer-exists-p))) (prog1 (apply oldfun args) (when-let (((not help-buf)) (buf (help-buffer))) ;; Ensure that, even if `help-buffer' returns nil in the future, we ;; don't kill the current buffer (kill-buffer buf)))) (apply oldfun args))) (advice-add 'corfu-popupinfo--get-documentation :around 'my/-corfu-popupinfo-close-help-buffer)) (use-package corfu-terminal :init (corfu-terminal-mode 1) :config (require 'corfu-terminal-popupinfo) (corfu-terminal-popupinfo-mode 1)) (use-package dabbrev :ensure nil :config (add-to-list 'dabbrev-ignored-buffer-regexps "\\` ") (add-to-list 'dabbrev-ignored-buffer-modes 'doc-view-mode) (add-to-list 'dabbrev-ignored-buffer-modes 'pdf-view-mode) (add-to-list 'dabbrev-ignored-buffer-modes 'tags-table-mode)) ;; cape (a bunch of capfs!) (use-package cape :bind (("C-c p" . cape-dabbrev) ([remap dabbrev-expand] . cape-dabbrev) ("C-c P" . cape-line) ("C-c f" . cape-file)) :hook (text-mode . my/-cape-setup-text-mode) :init (defun my/-cape-setup-text-mode () ;; Only run this if we are not in `TeX-mode' (unless (bound-and-true-p TeX-mode-p) (setq-local completion-at-point-functions (append completion-at-point-functions (list 'cape-dict 'cape-dabbrev)) corfu-auto nil)))) ;; xref (use-package xref :init (evil-define-key '(normal motion) 'global "gr" #'xref-find-references) (setq xref-show-xrefs-function #'consult-xref xref-show-definitions-function #'consult-xref)) ;; popup.el (use-package popup) ;; posframe (use-package posframe :init (defun my/posframe-tip (name msg) "Like `popup-tip', but with a posframe. NAME should be the buffer name to pass to `posframe-show'. MSG is the message to display." (unwind-protect (progn (posframe-show name :string msg :position (point) :max-width 80 :border-width 2 :border-color "white") (clear-this-command-keys) (push (read-event) unread-command-events) (posframe-hide name)) (posframe-hide name)))) (defun my/floating-tooltip (name msg) "If `display-graphic-p', call `my/posframe-tip', otherwise `popup-tip'. MSG is the message to show in the popup. NAME is the name of the buffer to pass to `posframe-show' if the display is graphical." (if (display-graphic-p) (my/posframe-tip name msg) (popup-tip msg))) ;; flymake (use-package flymake :config (require 'consult-flymake)) ;; flycheck (use-package flycheck :hook ((sh-mode emacs-lisp-mode) . flycheck-mode) :custom (flycheck-indication-mode 'left-margin) :init (setq flycheck-display-errors-function nil)) (use-package consult-flycheck) (defun my/diagnostic-at-point () "Show the diagnostics under point." (interactive) (let ((message)) (when-let ((flymake-mode) (diag (get-char-property (point) 'flymake-diagnostic))) (cl-callf nconc message (string-split (flymake--diag-text diag) "\n" t))) (when flycheck-mode (cl-callf nconc message (mapcar 'flycheck-error-message (flycheck-overlay-errors-at (point))))) ;; jinx (when-let ((jinx-msg (jinx--get-overlays (point) (1+ (point))))) (push "misspelled word" message)) (when message (my/floating-tooltip " *my-diagnostic-posframe*" (mapconcat (lambda (msg) (concat "•" msg)) message "\n"))))) (defconst my/consult-flymake-flycheck-narrow '((?e . "Error") (?w . "Warning") (?i . "Info") (?n . "Info"))) (defun my/-consult-replace-flymake-error-level (candidates) "Return CANDIDATES with the flymake error level note replaced with info." (cl-loop for cand in candidates collect (cl-loop with start = nil for i below (length cand) for props = (text-properties-at i cand) for face = (plist-get props 'face) when (eq face 'compilation-info) do (setq start (or start i)) else when start do (setf (substring cand start i) (propertize (string-pad "info" (- i start)) 'face (flycheck-error-level-error-list-face 'info))) (cl-return cand) finally return cand))) (defun my/consult-flymake-flycheck-candidates (&optional project) "Return combined candidate list for flymake and flycheck. With PROJECT, return the candiadeets for that project." (let ((had-errors)) (prog1 (seq-uniq (append (when-let (((bound-and-true-p flymake-mode)) (diags (if project (flymake--project-diagnostics project) (flymake-diagnostics)))) (setq had-errors t) (my/-consult-replace-flymake-error-level (consult-flymake--candidates diags))) (when (boundp 'flycheck-mode) (if project (cl-loop for buf in (project-buffers project) append (with-current-buffer buf (when (and flycheck-mode flycheck-current-errors) (setq had-errors t) (consult-flycheck--candidates)))) (when (and flycheck-mode flycheck-current-errors) (setq had-errors t) (consult-flycheck--candidates)))))) (unless had-errors (user-error "No errors (Flymake: %s | Flycheck: %s)" (cond ((not (bound-and-true-p flymake-mode)) "not running") ((seq-difference (flymake-running-backends) (flymake-reporting-backends)) "running") (t "finished")) (if (boundp 'flycheck-last-status-change) flycheck-last-status-change "not running")))))) (defun my/consult-flymake-flycheck (&optional project) "Jump to flymake or flycheck error. With PROJECT, give diagnostics for all buffers in the current project." (interactive "P") (consult--read (consult--with-increased-gc (my/consult-flymake-flycheck-candidates (and project (project-current)))) :prompt "Error: " :category 'flymake-flycheck-error :history t :require-match t :sort nil :narrow (consult--type-narrow my/consult-flymake-flycheck-narrow) :group (consult--type-group my/consult-flymake-flycheck-narrow) :lookup #'consult--lookup-candidate :state (consult--jump-state))) (with-eval-after-load 'flymake (keymap-set flymake-mode-map "C-c e" 'my/diagnostic-at-point) (keymap-set flymake-mode-map "C-c E" 'my/consult-flymake-flycheck)) (with-eval-after-load 'flycheck (keymap-set flycheck-mode-map "C-c e" 'my/diagnostic-at-point) (keymap-set flycheck-mode-map "C-c E" 'my/consult-flymake-flycheck)) (with-eval-after-load 'jinx (keymap-set jinx-mode-map "C-c e" 'my/diagnostic-at-point)) ;; eldoc (use-package eldoc :diminish eldoc-mode :init (setq-default eldoc-echo-area-use-multiline-p nil)) ;; eglot (use-package eglot :demand t :pin gnu ;; try to force Elpa version to fix warnings :hook ((eglot-managed-mode . my/-eglot-setup)) :init ;; (defun my/eglot-in-text-mode-only () ;; (when (eq major-mode 'text-mode) ;; (eglot-ensure))) (defvar my/-eglot-documentation-buffer nil "Buffer for showing documentation for `my/eglot-documentation-at-point'.") (defun my/eglot-documentation-at-point () "Show documentation for a symbol at point." (interactive) (if-let (server (eglot-current-server)) (progn (if-let* (((not (buffer-live-p my/-eglot-documentation-buffer))) (name (generate-new-buffer-name "*eglot documentation*"))) (setq my/-eglot-documentation-buffer (generate-new-buffer name))) (eglot-hover-eldoc-function (lambda (info _ _) (if-let (((not (seq-empty-p info))) (buff (current-buffer))) (with-current-buffer my/-eglot-documentation-buffer (read-only-mode -1) (erase-buffer) (insert info) (goto-char (point-min)) (special-mode) (read-only-mode 1) (when (not (get-buffer-window my/-eglot-documentation-buffer nil)) (switch-to-buffer-other-window my/-eglot-documentation-buffer t) (switch-to-buffer-other-window buff t))))))))) (defun my/-eglot-cleanup-doc-buffer (_server &optional _interactive _timeout preserve-buffers) (when (and (not preserve-buffers) (buffer-live-p my/-eglot-documentation-buffer) (cl-every (lambda (buffer) (with-current-buffer buffer (let ((server (eglot-current-server))) (or (not (eglot-lsp-server-p server)) (eglot--shutdown-requested server))))) (buffer-list))) (kill-buffer my/-eglot-documentation-buffer))) (advice-add 'eglot-shutdown :after 'my/-eglot-cleanup-doc-buffer) (defun my/-eglot-setup () "Setup eldoc variables for `eglot-managed-mode-hook'." (setq-local evil-lookup-func #'my/eglot-documentation-at-point) (evil-define-key '(normal motion) 'local "K" #'evil-lookup "gR" #'eglot-rename "gA" #'eglot-code-actions "gs" #'consult-eglot-symbols) (eglot-inlay-hints-mode -1)) (setq eglot-autoshutdown t) :config (add-to-list 'eglot-server-programs (cons '(c-mode c-ts-mode c++-mode c++-ts-mode objc-mode) '("clangd" "--all-scopes-completion" "--background-index" "--clang-tidy" "--completion-style=detailed" "--header-insertion=never" "--pch-storage=memory" "--function-arg-placeholders")))) ;; LTeX (languagetool) (require 'ltex-eglot) ;; gud (use-package gud :demand t :ensure nil :after (project evil) :bind (:map project-prefix-map ("U" . my/project-gdb)) :config (setq gdb-debuginfod-enable-setting t) (defvar my/project-gdb-command nil "Command to use in `my/project-gdb'.") (put 'my/project-gdb-command 'safe-local-variable (lambda (val) (stringp val))) (defun my/project-gdb (project command-line) "Run gdb in the project root" (interactive (let* ((project (project-current t)) (default-directory (project-root project))) (list project (gud-query-cmdline 'gdb)))) (let ((default-directory (project-root project))) (gdb command-line))) (evil-set-initial-state 'gdb-locals-mode 'motion) (evil-collection-inhibit-insert-state 'gdb-locals-mode-map) (evil-define-key '(normal motion visual) gdb-locals-mode-map (kbd "TAB") (keymap-lookup gdb-locals-mode-map "TAB") (kbd "RET") #'gdb-edit-locals-value (kbd "") #'gdb-edit-locals-value "q" #'kill-current-buffer) (evil-set-initial-state 'gdb-registers-mode 'motion) (evil-collection-inhibit-insert-state 'gdb-registers-mode-map) (evil-define-key '(normal motion visual) gdb-registers-mode-map (kbd "TAB") (keymap-lookup gdb-registers-mode-map "TAB") (kbd "RET") #'gdb-edit-register-value (kbd "") #'gdb-edit-register-value "q" #'kill-current-buffer (kbd "C-c f") #'gdb-registers-toggle-filter (kbd "C-c F") (lambda () "Customize the filter for the registers buffer." (interactive) (customize-option-other-window 'gdb-registers-filter-pattern-list))) (evil-set-initial-state 'gdb-frames-mode 'motion) (evil-collection-inhibit-insert-state 'gdb-frames-mode-map) (evil-define-key '(normal motion visual) gdb-frames-mode-map "q" #'kill-current-buffer (kbd "RET") #'gdb-select-frame) (evil-set-initial-state 'gdb-breakpoints-mode 'motion) (evil-collection-inhibit-insert-state 'gdb-breakpoints-mode-map) (evil-define-key '(normal motion visual) gdb-breakpoints-mode-map (kbd "TAB") (keymap-lookup gdb-breakpoints-mode-map "TAB") "q" #'gdb-delete-frame-or-window "D" #'gdb-delete-breakpoint (kbd "RET") #'gdb-goto-breakpoint (kbd "") #'gdb-goto-breakpoint (kbd "SPC") #'gdb-toggle-breakpoint) (evil-set-initial-state 'gdb-threads-mode 'motion) (evil-collection-inhibit-insert-state 'gdb-threads-mode-map) (evil-define-key '(normal motion visual) gdb-threads-mode-map (kbd "TAB") (keymap-lookup gdb-threads-mode-map "TAB") "q" #'gdb-delete-frame-or-window "D" #'gdb-frame-disassembly-for-thread (kbd "C-c f") #'gdb-display-stack-for-thread (kbd "C-c i") #'gdb-interrupt-thread (kbd "C-c l") #'gdb-display-locals-for-thread (kbd "C-c r") #'gdb-display-registers-for-thread (kbd "C-c c") #'gdb-continue-thread (kbd "C-c d") #'gdb-display-disassembly-for-thread (kbd "C-c s") #'gdb-step-thread (kbd "C-c F") #'gdb-frame-stack-for-thread (kbd "C-c L") #'gdb-frame-locals-for-thread (kbd "C-c R") #'gdb-frame-registers-for-thread (kbd "RET") #'gdb-select-thread (kbd "") #'gdb-select-thread)) ;; dape (use-package dape :hook ((after-init . dape-breakpoint-load) (kill-emacs . dape-breakpoint-save) (dape-start . save-some-buffers) (dape-display-source . pulse-momentary-highlight-one-line)) :bind (:map dape-info-parent-mode-map ("" . dape--info-buffer-tab)) :init (setopt dape-default-breakpoints-file (no-littering-expand-var-file-name "dape-breakpoints")) :config (setopt dape-buffer-window-arrangement 'right) (dape-breakpoint-global-mode 1)) ;; dumb-jump (use-package dumb-jump :init (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)) ;; yasnippet (use-package yasnippet :demand t :bind ("C-c s" . yas-expand) :config (yas-global-mode 1)) ;; project.el (use-package project :bind (("C-c v" . my/project-eshell-or-default) ([remap project-compile] . my/project-compile-or-default) :map project-prefix-map ("s" . my/project-eshell) ("u" . my/project-run)) :init (defvar eshell-buffer-name) (defun my/project-eshell (prompt &optional arg) "Switch to or create an eshell buffer in the current projects root." (interactive (list t current-prefix-arg)) (if-let ((proj (project-current prompt)) (default-directory (project-root proj)) (eshell-buffer-name (concat "*eshell for project " default-directory "*"))) (eshell arg))) (defun my/project-eshell-or-default (&optional arg) "Open an eshell for the current project, otherwise, open a normal eshell." (interactive "P") (unless (my/project-eshell nil arg) (eshell arg))) (defun my/project-compile-or-default () "If in a project, run `project-compile', otherwise run `compile'." (interactive) (if (project-current) (call-interactively 'project-compile) (call-interactively 'compile))) (defvar my/project-run-command nil "Command to run with `my/project-run'.") (put 'my/project-run-command 'safe-local-variable (lambda (val) (stringp val))) (defvar my/project-run-dir nil "Directory to run project in with `my/project-run'.") (put 'my/project-run-dir 'safe-local-variable (lambda (val) (stringp val))) (defvar my/-project-run-history '() "Commands previously run with `my/project-run'") (defvar my/project-root-marker ".project-root" "Marker file to look for in non-vc backed projects.") (defun my/project-get-root-dir () "Get the root dir for the current project" (let* ((proj (project-current nil)) (default-directory (if proj (project-root proj) default-directory))) (if my/project-run-dir (expand-file-name my/project-run-dir) default-directory))) (defun my/project-run (command comint) "Like `project-compile', but for running a project. COMMAND and COMINT are like `compile'." (interactive (list (let ((default-directory (my/project-get-root-dir))) (read-shell-command "Run Command: " (or (car my/-project-run-history) my/project-run-command) (if (and my/project-run-command (equal my/project-run-command (car-safe my/-project-run-history))) '(my/-project-run-history . 1) 'my/-project-run-history))) (consp current-prefix-arg))) (let* ((default-directory (my/project-get-root-dir)) (compilation-buffer-name-function (lambda (_) (progn "*run project*"))) (compilation-directory default-directory) (compile-history nil) (compile-command nil)) (compile command comint) (when (not my/project-run-command) (setq my/project-run-command command)))) :config (defun my/project-try-dotfile (dir) (if-let (root (locate-dominating-file dir my/project-root-marker)) (list 'vc nil root))) (add-hook 'project-find-functions #'my/project-try-dotfile)) ;; nxml (use-package nxml-mode :ensure nil :hook (nxml-mode . my/-nxml-setup) :init (defun my/-nxml-setup () "Setup `nxml-mode'." (sgml-electric-tag-pair-mode 1) (setq-local completion-at-point-functions '(rng-completion-at-point cape-file))) (add-to-list 'auto-mode-alist `(,(concat (regexp-opt '("gschema" "gresource" "ui")) "\\'") . nxml-mode))) ;; Bibtex (built in) (require 'bibtex) (defun my/bibtex-in-entry-p (&optional exclude-braces) "Return t is point is inside a BibTeX entry. When EXCLUDE-BRACES is non-nil, don't count the first and last brace of the entry as in the entry. That is, if the point is on the first { or last } of the entry, return nil." (save-excursion (when (and exclude-braces (eq ?\} (char-after))) (forward-char)) ;; go to top level and check if the character at point is { (let ((start-pos (point)) (last-valid (point))) (condition-case _ (while t (backward-up-list 1 t t) (setq last-valid (point))) (error (and (eq ?\{ (char-after last-valid)) (or (not exclude-braces) (not (= start-pos last-valid))))))))) (defvar my/bibtex-indent-width 4 "Width to indent for `my/bibtex-calculate-indentation'.") (defun my/bibtex-calculate-indentation () "Calculate the column to indent to on the current line." (save-excursion (back-to-indentation) (if (my/bibtex-in-entry-p t) my/bibtex-indent-width 0))) (defun my/bibtex-empty-line-p () "Return t if the current line is only blank characters." (save-excursion (beginning-of-line) (looking-at (rx (* blank) eol)))) (defun my/bibtex-indent-line () "Indent the current line." (interactive) (save-excursion (beginning-of-line) (when (looking-at (rx (+ blank))) (delete-region (point) (match-end 0))) (indent-to (my/bibtex-calculate-indentation))) (when (looking-at (rx (+ blank) eol)) (end-of-line))) (defun my/bibtex-indent-or-find-text () "Either indent the current line or jump to the current fields text. If the current line is only whitespace call `my/bibtex-calculate-indentation', otherwise, call `bibtex-find-text'." (interactive) (if (my/bibtex-empty-line-p) (my/bibtex-indent-line) (bibtex-find-text))) (defun my/bibtex-indent-or-find-text-and-insert () "Like `my/bibtex-indent-or-find-text', but enter insert mode after." (interactive) (my/bibtex-indent-or-find-text) (if (my/bibtex-empty-line-p) (evil-append 1) (evil-insert 1))) (defun my/-bibtex-setup-indent () "Set up `bibtex-mode' indentation stuff." (setq-local indent-line-function 'my/bibtex-indent-line electric-indent-chars '(?\n ?\{ ?\} ?,))) (defun my/-bibtex-fix-fill-prefix () "`bivtex-mode' has a bad habbit of messing up `fill-prefix'." (when (eq major-mode 'bibtex-mode) (setq-local fill-prefix nil))) (advice-add 'bibtex-mode :after 'my/-bibtex-fix-fill-prefix) (add-hook 'bibtex-mode-hook 'my/-bibtex-setup-indent) (keymap-set bibtex-mode-map "RET" 'newline-and-indent) (keymap-set bibtex-mode-map "TAB" 'my/bibtex-indent-or-find-text) (evil-define-key 'normal bibtex-mode-map (kbd "TAB") 'my/bibtex-indent-or-find-text-and-insert) ;; Latex help (from elisp file) (require 'latex-help) ;; AUCTeX (use-package auctex :hook ((LaTeX-mode . turn-on-reftex) (LaTeX-mode . LaTeX-math-mode) (LaTeX-mode . my/-setup-LaTeX-mode) (LaTeX-mode . flycheck-mode)) :bind (:map TeX-mode-map ("C-c ?" . latex-help)) :init (add-to-list 'major-mode-remap-alist '(plain-tex-mode . plain-TeX-mode)) (add-to-list 'major-mode-remap-alist '(latex-mode . LaTeX-mode)) (add-to-list 'major-mode-remap-alist '(ams-tex-mode . AmSTeX-mode)) (add-to-list 'major-mode-remap-alist '(context-mode . ConTeXt-mode)) (add-to-list 'major-mode-remap-alist '(texinfo-mode . Texinfo-mode)) (add-to-list 'major-mode-remap-alist '(doctex-mode . docTeX-mode)) (add-to-list 'auto-mode-alist '("/\\.latexmkrc\\'" . perl-mode)) (add-to-list 'auto-mode-alist '("\\.[tT]e[xX]\\'" . LaTeX-mode)) :config (defun my/-auctex-texdoc-setup-env (oldfun &rest args) (let ((process-environment process-environment) (emacs-cmd (concat "emacsclient" (and (not (display-graphic-p)) " -nw")))) (setenv "PDFVIEWER_texdoc" "evince") (setenv "MDVIEWER_texdoc" emacs-cmd) (setenv "PAGER_texdoc" emacs-cmd) (apply oldfun args))) (advice-add 'TeX-documentation-texdoc :around 'my/-auctex-texdoc-setup-env) (defun my/-setup-LaTeX-mode () (setq evil-lookup-func 'latex-help-at-point)) (setq TeX-auto-save t TeX-parse-self t reftex-plug-into-AUCTeX t) (evil-define-operator my/evil-LaTeX-fill (beg end) "Like `evil-fill', but using auctex." ;; The code here came straight from `evil-fill' :move-point nil :type line (save-excursion (ignore-errors (LaTeX-fill-region beg end)))) (evil-define-operator my/evil-LaTeX-fill-and-move (beg end) "Like `evil-fill-and-move', but using auctex." ;; The code here came straight from `evil-fill-and-move' :move-point nil :type line (let ((marker (make-marker))) (move-marker marker (1- end)) (ignore-errors (LaTeX-fill-region beg end) (goto-char marker) (evil-first-non-blank)))) (evil-define-key 'normal TeX-mode-map "gq" 'my/evil-LaTeX-fill-and-move "gw" 'my/evil-LaTeX-fill) (setq-default TeX-master nil) (require 'tex) (TeX-global-PDF-mode 1) (add-to-list 'TeX-command-list `("LuaLaTeX" ,(concat "lualatex %(file-line-error) %`%(extraopts) " "%S%(PDFout)%(mode)%' %(output-dir) %t") TeX-run-TeX nil (LaTeX-mode) :help "Run LuaLaTeX"))) ;; blueprint (use-package blueprint-ts-mode :hook (blueprint-ts-mode . eglot-ensure) :after eglot) ;; python-ts-mode (use-package python-ts-mode :ensure nil :hook (python-ts-mode . eglot-ensure)) ;; java-ts-mode (use-package java-ts-mode :hook (java-ts-mode . eglot-ensure)) ;; c-ts-mode (use-package c-ts-mode :after evil :hook ((c-ts-mode c++-ts-mode) . eglot-ensure) :init (setq-default c-ts-mode-indent-offset 4) :config (evil-define-key 'normal 'c-ts-mode-map "go" #'ff-find-other-file "gO" #'ff-find-other-file-other-window) (evil-define-key 'normal 'c++-ts-mode-map "go" #'ff-find-other-file "gO" #'ff-find-other-file-other-window) (evil-define-key 'normal 'objc-mode-map "go" #'ff-find-other-file "gO" #'ff-find-other-file-other-window)) ;; php-mode (use-package php-mode :hook (php-mode . eglot-ensure)) ;; web-mode (use-package web-mode :hook (web-mode . eglot-ensure) :init (add-to-list 'eglot-server-programs '(web-mode . ("vscode-html-language-server" "--stdio")))) ;; Polymode (use-package polymode :config (define-hostmode my/poly-web-hostmode :mode 'web-mode) (define-innermode my/poly-php-innermode :mode 'php-mode :head-matcher "\<\?php" :tail-matcher "\?\>" :head-mode 'body :tail-mode 'body) (define-polymode my/poly-web-mode :hostmode 'my/poly-web-hostmode :innermodes '(my/poly-php-innermode)) (add-to-list 'auto-mode-alist '("\\.php\\|\\.phtml\\'" . my/poly-web-mode))) ;; shell-mode (use-package sh-script :ensure nil :hook (sh-mode . my/-setup-sh-mode) :init (defun my/-setup-sh-mode () (add-to-list 'completion-at-point-functions #'cape-file))) ;; go mode (use-package go-mode :defer nil :hook (go-mode . eglot-ensure)) (use-package go-ts-mode :ensure nil :hook (go-ts-mode . eglot-ensure)) ;; rust (use-package rust-mode) (use-package rust-ts-mode :ensure nil :hook (rust-ts-mode . eglot-ensure)) ;; zig (use-package zig-mode :hook (zig-mode . eglot-ensure)) ;; lua (use-package lua-mode :hook (lua-mode . eglot-ensure)) ;; markdown (use-package markdown-mode :hook (markdown-mode . auto-fill-mode)) ;; groovy (use-package groovy-mode) ;; cmake (require 'cmake-mode) (with-eval-after-load 'cmake-mode (defun my/setup-cmake-ts-mode () "Setup `cmake-ts-mode' buffers." (setq-local indent-line-function #'cmake-indent)) (add-hook 'cmake-ts-mode-hook #'my/setup-cmake-ts-mode)) ;; kdl (require 'kdl-ts-mode) ;; json (use-package json-ts-mode :hook (json-ts-mode . eglot-ensure)) (use-package json-mode) ;; csv (use-package csv-mode) ;; firejail (require 'firejail-mode) ;; yaml (use-package yaml-ts-mode :hook ((yaml-ts-mode . eglot-ensure) (yaml-ts-mode . my/-setup-yaml-ts-mode)) :init (defun my/-setup-yaml-ts-mode () (setq indent-line-function #'yaml-indent-line))) (use-package yaml-mode) ;; yuck (config language for eww) (use-package yuck-mode) ;; Some Elisp indentation stuff ;; Source: https://github.com/magit/emacsql ;; emacsql.el line 394 (defun my/lisp-inside-plist-p () "Return t if point is inside a plist." (save-excursion (let ((start (point))) (beginning-of-defun) (when-let ((sexp (nth 1 (parse-partial-sexp (point) start)))) (goto-char sexp) (looking-at (rx "(" (* (syntax whitespace)) ":")))))) (defun my/-calculate-indent-fix-plists (oldfun &rest args) "This function is meant to advise `calculate-lisp-indent'. It calls OLDFUN with ARGS in such an environment as to prevent the default indentation of plists." (if (and (eq major-mode 'emacs-lisp-mode) (save-excursion (beginning-of-line) (my/lisp-inside-plist-p))) (let ((lisp-indent-offset 1)) (apply oldfun args)) (apply oldfun args))) (advice-add 'calculate-lisp-indent :around 'my/-calculate-indent-fix-plists) (defvar my/max-lisp-noindent-comment-search-lines 30 "Max lines to search for the noindent comment.") (defun my/-calculate-lisp-indent-noindent-comment (oldfun &rest args) "This function is meant to advise `calculate-lisp-indent'. It calls OLDFUN with ARGS, unless the line ends with the comment ; noindent [LINES] In this case, it just returns the current amount of indentation. LINES is the number of lines that this comment affects. This is limited by `my/max-lisp-noindent-comment-search-lines'. This only works if its on the first or second form in a block. I think this is because the indentation code only checks those and then assumes the same indentation for every following line in the same block. This is probably OK as I can't imagine too many instances where you need to randomly change the indent midway through a block, and in those cases you can just stick this on the first line in the block and manually deal with indentation." (if (and (save-excursion (end-of-line) (re-search-backward (rx (+ ";") (syntax whitespace) "noindent" (? (syntax whitespace) (group (+ num))) line-end) (pos-bol (- my/max-lisp-noindent-comment-search-lines)) t)) (save-excursion ;; if we are on a blank line, move forward a line (when (zerop (length (buffer-substring-no-properties (pos-bol) (pos-eol)))) (beginning-of-line 2)) (<= (count-lines (match-beginning 0) (pos-eol)) (if-let ((match (match-string 1))) (string-to-number match) 1)))) (save-excursion (beginning-of-line) (looking-at (rx (* blank))) (length (match-string 0))) (apply oldfun args))) (advice-add 'calculate-lisp-indent :around 'my/-calculate-lisp-indent-noindent-comment) ;; sly (use-package sly ;; :hook (lisp-mode . my/-lisp-mode-autoconnect-sly) :bind (:map sly-mode-map ("C-c e" . my/sly-show-notes-at-point)) :autoload sly-connected-p :init (defun my/-lisp-mode-autoconnect-sly () (unless (sly-connected-p) (sly))) (defun my/sly-notes-at-point (pos &optional buffer) "Returns the sly notes at POS in BUFFER. If BUFFER is nil, the current buffer is used." (with-current-buffer (or buffer (current-buffer)) (cl-loop for overlay in (overlays-at pos) for note = (overlay-get overlay 'sly-note) when note collect note))) (defun my/sly-show-notes-at-point () "Show all sly notes at point in a floating window." (interactive) (my/floating-tooltip " *sly-note-posframe*" (with-output-to-string (dolist (note (my/sly-notes-at-point (point))) (when-let (msg (plist-get note :message)) (princ "·") (princ msg) (terpri)))))) (setq inferior-lisp-program "/usr/bin/sbcl") :config (sly-symbol-completion-mode -1)) ;; pdf-tools (use-package pdf-tools :hook (pdf-view-mode . my/setup-pdf-view-mode) :init (setq pdf-misc-print-program-executable "lp") (defun my/setup-pdf-view-mode () (display-line-numbers-mode -1) (evil-define-key '(motion normal visual) 'local (kbd "C-s") #'isearch-forward (kbd "C-r") #'isearch-backward) (setq-local cursor-type nil)) (pdf-tools-install)) ;; doc view (use-package doc-view :ensure nil :hook (doc-view-mode . my/-setup-doc-view-mode) :init (defun my/-setup-doc-view-mode () (display-line-numbers-mode -1) (evil-define-key '(motion normal visual) 'local (kbd "C-s") #'isearch-forward (kbd "C-r") #'isearch-backward))) ;; calc (use-package calc :ensure nil :bind (("C-c m" . quick-calc) :map calc-mode-map ("M-" . calc-roll-up) ("M-TAB" . calc-roll-up)) :hook ((calc-mode calc-trail-mode) . my/setup-calc-calc-trail-mode) :init (defun my/setup-calc-calc-trail-mode () (setq-local doom-modeline-percent-position '() truncate-partial-width-windows nil) (visual-line-mode -1) (display-line-numbers-mode -1) (toggle-truncate-lines 1)) :config (evil-define-key '(normal visual motion) calc-edit-mode-map (kbd "RET") 'calc-edit-return (kbd "") 'calc-edit-return) (defun my/-calc-float-mode-string () (cl-destructuring-bind (mode prec) calc-float-format (concat (upcase-initials (symbol-name mode)) (unless (zerop prec) (concat ": " (number-to-string prec)))))) (doom-modeline-def-segment calc "Display calculator icons and info." (concat (doom-modeline-spc) (when-let ((icon (doom-modeline-icon 'faicon "nf-fa-calculator" "🖩" ""))) (concat (doom-modeline-display-icon icon) (doom-modeline-vspc))) (doom-modeline--buffer-simple-name) (when (eq major-mode 'calc-mode) (concat (doom-modeline-spc) (number-to-string calc-internal-prec) (doom-modeline-spc) (upcase-initials (symbol-name calc-angle-mode)) (doom-modeline-spc) (my/-calc-float-mode-string) (when calc-prefer-frac (concat (doom-modeline-spc) "Frac")) (cond (calc-algebraic-mode (concat (doom-modeline-spc) "Alg")) (calc-incomplete-algebraic-mode (concat (doom-modeline-spc) "IAlg")))))))) ;; sage (for when calc is not enough) (use-package sage-shell-mode :demand :bind ("C-c g" . my/run-sage) :hook (sage-shell-mode . my/-setup-sage-shell-mode) :init (defun my/-setup-sage-shell-mode () (setq-local comint-dynamic-complete-functions '(comint-c-a-p-replace-by-expanded-history))) :config (defun my/run-sage (p) "Like `sage-shell:run-sage', but does not ask anything without a prefix argument." (interactive "P") (let ((sage-shell:ask-command-options p)) (funcall-interactively #'sage-shell:run-sage (sage-shell:read-command))))) ;; fricas (because I like calculators) (add-to-list 'load-path "/usr/lib/fricas/emacs/") (use-package fricas :ensure nil :custom (fricas-run-command "fricas -nosman") :init ;; Fix `fricas-mode' messing up `completion-at-point-functions' (advice-add #'fricas-mode :around #'(lambda (oldfun &rest r) (let ((temp-capfs)) (let ((completion-at-point-functions '(t))) (apply oldfun r) (setq temp-capfs completion-at-point-functions)) (setq-local completion-at-point-functions temp-capfs))) '((name . "my/-fricas-fix-capfs"))) :config (face-spec-set 'fricas-type-time '((t (:foreground unspecified :background unspecified :inherit font-lock-type-face)))) (face-spec-set 'fricas-message '((t (:foreground unspecified :background unspecified :inherit error)))) (face-spec-set 'fricas-undefined '((t (:foreground unspecified :background unspecified :inherit nerd-icons-lblue)))) (face-spec-set 'fricas-algebra '((t (:foreground unspecified :background unspecified :weight bold :inherit fricas-prompt)))) (face-spec-set 'fricas-TeX '((t (:foreground "black" :background "white" :inherit fricas-prompt))))) ;; gnuplot (mostly for org-plot) (use-package gnuplot) ;; eat (use-package eat :bind (("C-c V" . my/project-eat-or-default) :map eat-mode-map ("M-o" . ace-window) :map eat-semi-char-mode-map ("M-o" . ace-window) :map eat-eshell-emacs-mode-map ("M-o" . ace-window) :map eat-eshell-semi-char-mode-map ("M-o" . ace-window)) :config (defvar my/project-eat-hash-table (make-hash-table :test 'equal) "Hash table that maps project root dirs to eat buffers.") (defun my/project-eat (prompt) "Switch to or create a eat buffer in the current projects root." (interactive (list t)) (if-let ((proj (project-current prompt)) (default-directory (project-root proj))) (if-let ((eat-buff (gethash default-directory my/project-eat-hash-table)) ((buffer-live-p eat-buff))) (switch-to-buffer eat-buff) (let ((eat-buffer-name (concat "*eat for project " default-directory "*")) (eat-term-name (if (file-remote-p default-directory) "xterm-256color" eat-term-name))) (puthash default-directory (eat) my/project-eat-hash-table))))) (defun my/project-eat-or-default () "Open an eat for the current project, otherwise, open a normal eat." (interactive) (unless (my/project-eat nil) (if-let ((eat-buff (gethash nil my/project-eat-hash-table)) ((buffer-live-p eat-buff))) (switch-to-buffer eat-buff) (puthash nil (let ((eat-term-name (if (file-remote-p default-directory) "xterm-256color" eat-term-name))) (eat)) my/project-eat-hash-table))))) ;; eshell stuff (use-package eshell :ensure nil :defer nil :hook ((eshell-load . eat-eshell-visual-command-mode) (eshell-load . eat-eshell-mode) (eshell-mode . my/-eshell-mode-setup)) :bind (:map eshell-mode-map ("TAB" . completion-at-point) ("" . completion-at-point)) :init (defun my/-eshell-mode-setup () "Setup function run from `eshell-mode-hook'" (setq-local corfu-auto nil)) (setq-default eshell-command-aliases-list '(("clear" "clear t") ("e" "find-file $1") ("n" "find-file $1") ("emacs" "find-file $1") ("nvim" "find-file $1") ("ls" "eza --git -F $*") ("la" "ls -a $*") ("l" "ls -l $*") ("ll" "la -l $*") ("gt" "git status $*") ("gp" "git push $*") ("gu" "git pull $*") ("gf" "git fetch $*") ("ga" "git add $*") ("gcm" "git commit -m ${string-join $* \" \"}") ("ldg" "ledger -f \"$HOME/docs/finance/finances.ledger\" $*") ("tp" "trash-put $*") ("trr" "trash-restore $*") ("tre" "trash-empty $*") ("tre" "trash-empty $*") ("trm" "trash-rm $*") ("rm" "echo 'rm: I''m unsafe! Don''t use me.'; false") ("\\rm" "eshell/rm"))) (defvar my/eshell-bm-auto-ls t "Weather or not to run ls after `eshell/bm'") (defun eshell/bm (&optional name) "Change to directory of bookmark NAME. If no name is given, list all bookmarks instead." (if name (progn (eshell/cd (bookmark-get-filename name)) (when my/eshell-bm-auto-ls (eshell/ls))) (eshell-print (string-join (bookmark-all-names) " "))))) (use-package esh-help :hook (eshell-mode . my/-setup-eshell-help-func) :init (defun my/-setup-eshell-help-func () (eldoc-mode 1) (setq-local evil-lookup-func #'esh-help-run-help)) (setup-esh-help-eldoc)) (use-package eshell-syntax-highlighting :init (eshell-syntax-highlighting-global-mode 1)) (use-package eshell-starship :ensure nil :demand t :hook (eshell-prompt-mode . eshell-starship-prompt-mode)) ;; proced (use-package proced :bind ("C-x j" . proced) :init (setq proced-auto-update-flag t proced-auto-update-interval 1) (defun my/-setup-proced-mode () (visual-line-mode -1) (setq-local truncate-lines t)) (add-hook 'proced-mode-hook 'my/-setup-proced-mode)) ;; dired (use-package dired :ensure nil :init (setq-default dired-kill-when-opening-new-dired-buffer t) (setq delete-by-moving-to-trash t dired-recursive-copies 'always dired-recursive-deletes 'always dired-dwim-target t dired-create-destination-dirs 'ask dired-create-destination-dirs-on-trailing-dirsep t dired-isearch-filenames 'dwim dired-do-revert-buffer (lambda (dir) (not (file-remote-p dir))) dired-clean-up-buffers-too t dired-clean-confirm-killing-deleted-buffers t) (evil-define-key '(normal visual motion) dired-mode-map "u" #'dired-unmark "U" #'dired-unmark-all-marks)) ;; ibuffer (use-package ibuffer :bind ("C-x C-b" . ibuffer)) ;; magit (use-package magit :init (evil-define-key '(normal visual motion) magit-mode-map "s" #'magit-stage-file "S" #'magit-stage-modified)) ;; org-mode (use-package org :pin gnu :bind (("C-c c" . org-capture) ("C-c a" . org-agenda) ("C-c l" . org-store-link) :map org-mode-map ("C-c t" . org-table-create)) :hook (org-mode . org-table-header-line-mode) :init (font-lock-add-keywords 'org-mode `((,(rx bol (* " ") (group "-") " ") (0 (prog1 nil (compose-region (match-beginning 1) (match-end 1) "•")))))) (setq org-directory "~/org" org-agenda-files '("~/org/") org-log-into-drawer t org-log-done 'time org-log-redeadline 'time org-log-reschedule 'time org-preview-latex-default-process 'dvisvgm org-highlight-latex-and-related '(native entities) org-startup-with-inline-images t org-adapt-indentation t org-hide-leading-stars t org-html-with-latex 'dvisvgm org-preview-latex-process-alist '((dvisvgm :image-input-type "dvi" :image-output-type "svg" :image-size-adjust (1.7 . 1.5) :latex-compiler ("pdflatex -interaction nonstopmode -output-format=dvi -output-directory=%o %f") :image-converter ("dvisvgm %o%b.dvi --no-fonts --exact-bbox --scale=%S --output=%O")))) (defun my/-org-allow-in-derived-mode (oldfun &rest r) "Allow OLDFUN to run, even if `major-mode' is only derived from `org-mode'. R is rest of the arguments to OLDFUN." (let ((major-mode (if (derived-mode-p 'org-mode) 'org-mode major-mode))) (apply oldfun r))) (advice-add 'org-element-at-point :around 'my/-org-allow-in-derived-mode) (advice-add 'org-table-header-line-mode :around 'my/-org-allow-in-derived-mode)) (use-package evil-org :after org :hook (org-mode . evil-org-mode) :init (require 'evil-org-agenda) (evil-org-agenda-set-keys)) ;; ledger (use-package ledger-mode) (use-package flycheck-ledger :hook (ledger-mode . flycheck-mode)) ;; khard contacts (require 'khard) ;; This is also in khard (see above), it's just also here so that if I remove ;; that file ever, other things will not break. (defun my/message-in-header-p (name &optional testfn) "If in field NAME, return the start of the header, otherwise, return nil. The name is compared with the field name using TESTFN (defaults to `equal')." (save-excursion (when (and (message-point-in-header-p) (message-beginning-of-header t)) (beginning-of-line) (when (and (looking-at (rx bol (group (+? any)) ":" (? " "))) (funcall (or testfn 'equal) (match-string 1) name)) (match-end 0))))) ;; mu4e (use-package mu4e :ensure nil :defer nil :hook ((mu4e-index-updated . my/-mu4e-enable-index-messages) (mu4e-main-mode . my/-mu4e-setup-main-mode) (mu4e-view-mode . my/-mu4e-setup-view-mode) (mu4e-compose-mode . my/-mu4e-setup-compose-mode)) :bind (("C-x C-m" . mu4e) :map message-mode-map ("C-c k" . khard-insert-email-contact) :map mu4e-headers-mode-map ([remap mu4e-headers-mark-for-trash] . my/mu4e-headers-mark-for-trash) :map mu4e-view-mode-map ([remap mu4e-view-mark-for-trash] . my/mu4e-view-mark-for-trash)) :init (require 'mu4e) (evil-define-key '(normal motion) mu4e-main-mode-map "q" #'bury-buffer) (evil-define-key '(normal motion) mu4e-view-mode-map "gy" #'mu4e-view-save-url) (defun my/-mu4e-setup-view-mode () (setq-local global-hl-line-mode nil)) (defun my/-mu4e-setup-main-mode () (setq-local default-directory "~/")) (defun my/-mu4e-enable-index-messages () (setq mu4e-hide-index-messages nil)) (defun my/mu4e-update-mail-and-index-silent () "Run `mu4e-update-mail-and-index' without any messages in the background." (setq mu4e-hide-index-messages t) (mu4e-update-mail-and-index t)) (defun my/mu4e-headers-mark-for-trash () "Move the message a point to the trash without marking it was deleted (trashed)." (interactive) (when (mu4e-thread-message-folded-p) (mu4e-warn "Cannot mark folded messages")) (mu4e-mark-at-point 'move mu4e-trash-folder) (when mu4e-headers-advance-after-mark (mu4e-headers-next))) (defun my/mu4e-view-mark-for-trash () "Like `my/mu4e-headers-mark-for-trash', but for `mu4e-view-mode'." (interactive) (mu4e--view-in-headers-context (my/mu4e-headers-mark-for-trash))) (defun my/-mu4e-enable-autocomplete-in-header () ;; corfu auto must be t (not the integer returned by ;; `my/message-in-header-p' (setq-local corfu-auto (and (not (window-minibuffer-p)) (my/message-in-header-p "To") t))) (defun my/-mu4e-setup-compose-mode () (add-hook 'post-command-hook 'my/-mu4e-enable-autocomplete-in-header nil t) (add-to-list (make-local-variable 'completion-at-point-functions) (cape-capf-super #'mu4e-complete-contact #'khard-message-mode-capf))) (defun my/-mu4e-fix-cycle-threshold () (setq-local completion-cycle-threshold nil)) (advice-add 'mu4e--compose-setup-completion :after 'my/-mu4e-fix-cycle-threshold) (defvar my/mu4e-interesting-mail-query (concat "flag:unread AND NOT flag:trashed AND NOT " "maildir:/protonmail/Trash AND NOT maildir:/protonmail/Spam") "Flag for mail which will appear as \"unread\" and will be notified.") (setq message-kill-buffer-on-exit t message-confirm-send t message-send-mail-function 'sendmail-send-it mu4e-change-filenames-when-moving t mu4e-context-policy 'pick-first mu4e-attachment-dir "~/downloads/" mu4e-last-update-buffer " *mu4e-last-update*" mu4e-index-update-error-warning nil mu4e-get-mail-command "mbsync protonmail" mu4e-completing-read-function #'completing-read-default mu4e-compose-context-policy 'ask-if-none mu4e-contexts (list (make-mu4e-context :name "Personal" :match-func (lambda (msg) (when msg (string-match-p "^/protonmail/" (mu4e-message-field msg :maildir)))) :vars `((user-mail-address . ,(my/get-private 'mu4e-email)) (user-full-name . ,(my/get-private 'mu4e-name)) (message-signature . nil) (mu4e-refile-folder . "/protonmail/Archive") (mu4e-sent-folder . "/protonmail/Sent") (mu4e-drafts-folder . "/protonmail/Drafts") (mu4e-trash-folder . "/protonmail/Trash") (mu4e-bookmarks . ((:name "Inbox" :query "maildir:/protonmail/Inbox" :key ?i) (:name "Unread" :query ,my/mu4e-interesting-mail-query :key ?u)))))))) (use-package mu4e-alert :after mu4e :hook (after-init . mu4e-alert-enable-notifications) :init (setq mu4e-alert-set-window-urgency nil mu4e-alert-interesting-mail-query my/mu4e-interesting-mail-query) :config (mu4e-alert-set-default-style 'libnotify)) (mu4e t) (mu4e-context-switch nil "Personal") ;; mu4e compose HTML messages (use-package org-mime) (require 'org-mu4e-compose) (setq mail-user-agent 'org-mu4e-user-agent org-mime-org-html-with-latex-default 'dvisvgm org-mime-export-options '(:with-latex dvisvgm :with-footnotes t)) (evil-define-key '(normal visual) org-mu4e-compose-mode-map "G" #'mu4e-compose-goto-bottom "gg" #'mu4e-compose-goto-top) (evil-define-key 'normal org-mu4e-compose-mode-map "ZZ" #'message-send-and-exit "ZD" #'message-dont-send "ZQ" #'message-kill-buffer "ZF" #'mml-attach-file) (evil-define-key 'normal mu4e-view-mode-map "R" 'org-mu4e-compose-reply "cr" 'org-mu4e-compose-reply) (evil-define-key 'normal mu4e-headers-mode-map "R" 'org-mu4e-compose-reply "cr" 'org-mu4e-compose-reply) (defun my/-setup-org-mu4e-compose-mode () "Setup up stuff in `org-mu4e-compose' buffers." (setq-local ltex-eglot-variable-save-method 'file) ;; this should come last so it can pick up the above ;; (eglot-ensure) ) (add-hook 'org-mu4e-compose-mode-hook #'my/-setup-org-mu4e-compose-mode) ;; elfeed (use-package elfeed :bind (("C-c d" . elfeed)) :custom (elfeed-feeds '(("https://archlinux.org/feeds/news/" linux arch) ("https://9to5linux.com/feed/atom" linux news))) :config (setq elfeed-log-buffer-name " *elfeed-log*") (evil-define-key '(normal motion) elfeed-search-mode-map "r" #'elfeed-search-fetch) (elfeed-db-load)) ;; helpful (use-package helpful :hook ((emacs-lisp-mode . my/-helpful-setup-emacs-lisp-mode) (helpful-mode . my/-setup-helpful-mode)) :bind (:map help-map ("f" . helpful-callable) ("v" . helpful-variable) ("k" . helpful-key) ("o" . helpful-symbol) ("x" . helpful-command) ("F" . helpful-function) :map helpful-mode-map ("" . my/helpful-history-back) ("" . my/helpful-history-forward) ("<" . my/helpful-history-back) (">" . my/helpful-history-forward)) :init (defun my/-helpful-setup-emacs-lisp-mode () (setq-local evil-lookup-func #'helpful-at-point)) (defun my/-setup-helpful-mode () (setq-local evil-lookup-func #'helpful-at-point tab-width 8)) (defvar my/helpful-symbol-history-size 50 "Max size of `my/helpful-symbol-history'.") (defvar my/helpful-symbol-history '() "History of helpful symbols.") (defvar my/-helpful-inhibit-history nil "If non-nil, don't add symbols to `my/helpful-symbol-history'.") (defvar my/-helpful-last-entry nil "Last entry looked up with helpful.") (defun my/helpful-history-back (count) "Go back COUNT symbols in `my/helpful-symbol-history'. If called interactively, COUNT defaults to 1." (interactive "p") (my/helpful-history-forward (- count))) (defun my/helpful-history-forward (count) "Move COUNT symbols in `my/helpful-symbol-history'. If COUNT is negative, move back. If COUNT is larger than the history, go to the newest entry. Go to the oldest entry if -COUNT is larger than the history." (interactive "p") (when helpful--sym (let* ((hist-len (length my/helpful-symbol-history)) (current-pos (seq-position my/helpful-symbol-history (cons helpful--sym helpful--callable-p) 'equal)) (new-pos (- current-pos count))) (cond ;; if already at the newest element, signal an error ((and (> count 0) (= current-pos 0)) (message "%s" "No newer symbol!")) ;; if already at the oldest element, signal an error ((and (< count 0) (= (1+ current-pos) hist-len)) (message "%s" "No older symbol!")) (t (let ((my/-helpful-inhibit-history t) (entry (cond ((<= new-pos 0) (seq-first my/helpful-symbol-history)) ((>= new-pos hist-len) (car (last my/helpful-symbol-history))) (t (nth new-pos my/helpful-symbol-history))))) (if (cdr entry) (helpful-callable (car entry)) (helpful-variable (car entry))))))))) (defun my/-helpful-switch-buffer-function (helpful-buf) "Like `pop-to-buffer', but kill previous helpful buffers and save the new buffers `helpful--sym' to `my/helpful-symbol-history'." (cl-loop with window = nil for buf in (buffer-list) when (and (not (eq buf helpful-buf)) (eq (buffer-local-value 'major-mode buf) 'helpful-mode)) do (when-let (cur-window (get-buffer-window buf nil)) (setq window cur-window)) (kill-buffer buf) finally (let ((entry (cons (buffer-local-value 'helpful--sym helpful-buf) (buffer-local-value 'helpful--callable-p helpful-buf)))) (unless my/-helpful-inhibit-history (when-let (from-current-hist (member my/-helpful-last-entry my/helpful-symbol-history)) (setq my/helpful-symbol-history from-current-hist)) (cl-pushnew entry my/helpful-symbol-history :test 'equal) (setq my/helpful-symbol-history (seq-take my/helpful-symbol-history my/helpful-symbol-history-size))) (setq my/-helpful-last-entry entry)) (if window (window--display-buffer helpful-buf window 'reuse) (pop-to-buffer helpful-buf)))) (setq helpful-switch-buffer-function 'my/-helpful-switch-buffer-function helpful-max-buffers 2)) (defun my/greyify-color (color percent &optional frame) "Make COLOR closer to black by PERCENT on FRAME. Color can be any color which can be passed to `color-values'." (cl-destructuring-bind (&optional r g b) (color-name-to-rgb color frame) (when (and r g b) (let ((scale (- 1.0 (/ percent 100.0)))) (color-rgb-to-hex (* r scale) (* g scale) (* b scale)))))) ;; rainbow-delimiters (use-package rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode) :config ;; generate dark version of the rainbow delimiters faces (defun my/-rainbow-delimiters-recalc-dark-faces (&optional frame) (unless frame (setq frame (selected-frame))) (dotimes (i 9) (when-let ((old-face (intern-soft (format "rainbow-delimiters-depth-%d-face" (1+ i)))) (new-face (intern (format "my/rainbow-delimiters-depth-%d-dark-face" (1+ i)))) (old-color (face-attribute old-face :foreground frame)) (new-color (my/greyify-color old-color 50 frame))) (set-face-attribute new-face frame :foreground new-color)))) (add-hook 'after-make-frame-functions #'my/-rainbow-delimiters-recalc-dark-faces) (add-hook 'server-after-make-frame-hook #'my/-rainbow-delimiters-recalc-dark-faces) (defun my/rainbow-delimiters-parinfer-pick-face (depth match loc) "Version of `rainbow-delimiters-default-pick-face' that colors closing parenthesis darker than opening ones. This function defers to `rainbow-delimiters-default-pick-face' and just changes the output if it returns one of the normal rainbow-delimiters-depth-N-face faces." (save-match-data (let* ((base-face (rainbow-delimiters-default-pick-face depth match loc)) (base-name (symbol-name base-face))) (if (and evil-cleverparens-mode (eq ?\) (char-syntax (elt (buffer-substring-no-properties loc (1+ loc)) 0))) (string-match (rx string-start "rainbow-delimiters-depth-" (group (+ num)) "-face" string-end) base-name)) (or (intern-soft (format "my/rainbow-delimiters-depth-%s-dark-face" (match-string 1 base-name))) base-face) base-face)))) (setopt rainbow-delimiters-pick-face-function 'my/rainbow-delimiters-parinfer-pick-face)) ;; auto-highlight-symbol (use-package auto-highlight-symbol :hook (lisp-data-mode . auto-highlight-symbol-mode) :init (setq ahs-face 'bold ahs-face-unfocused 'bold ahs-definition-face 'bold ahs-definition-face-unfocused 'bold ahs-plugin-default-face 'bold ahs-plugin-default-face-unfocused 'bold)) ;; Theme (doom-themes) (use-package doom-themes :config (load-theme 'doom-molokai t) (doom-themes-org-config)) ;; solaire-mode (use-package solaire-mode :config (solaire-global-mode 1)) ;; Highlight todos (use-package hl-todo :config (global-hl-todo-mode 1)) (use-package magit-todos :after (hl-todo magit) :config (magit-todos-mode 1)) ;; icons (use-package nerd-icons) (use-package nerd-icons-completion :config (nerd-icons-completion-mode)) (use-package nerd-icons-dired :hook (dired-mode . nerd-icons-dired-mode)) (use-package kind-icon :after corfu :init (setq kind-icon-default-face 'corfu-default kind-icon-default-style '(:padding -1 :stroke 0 :margin 0 :radius 0 :height 0.5 :scale 1)) :config (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)) ;; modeline (doom-modeline) (use-package doom-modeline :init (setq doom-modeline-support-imenu t) (doom-modeline-mode 1)) ;; dashboard.el (use-package dashboard :config (dashboard-setup-startup-hook) (setq initial-buffer-choice (lambda () (get-buffer-create "*dashboard*")) dashboard-force-refresh t dashboard-display-icons-p t dashboard-icon-type 'nerd-icons dashboard-set-file-icons t dashboard-projects-backend 'project-el dashboard-items '((recents . 5) (projects . 5) (bookmarks . 5)))) ;; page break lines (use-package page-break-lines :config (global-page-break-lines-mode 1) (add-to-list 'page-break-lines-modes 'prog-mode) (add-to-list 'page-break-lines-modes 'text-mode) (add-to-list 'page-break-lines-modes 'helpful-mode)) ;; fun! (use-package mines) ;;; init.el ends here