;;; 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) ;; 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)) ;; 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)) (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))) (kill-buffer buffer)) ((equal ans "nosave") (with-current-buffer buffer (set-buffer-modified-p nil)) (kill-buffer buffer)) ;; 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))))) (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-fix-save-some-buffers (oldfun &optional arg pred) "Fix `save-some-buffers' 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))) (funcall oldfun arg pred)) (when (and status (not (kkp--terminal-has-active-kkp-p))) (kkp--terminal-setup))))) (advice-add #'save-some-buffers :around #'my/-kkp-fix-save-some-buffers)) ;; 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)) (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) (define-key evil-cleverparens-mode-map (kbd " M-o") nil 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)))) ;; 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/normal-state-lisp-indent-adjust-parens () "Like `lisp-indent-adjust-parens', but got to first char on line first." (interactive) (back-to-indentation) (lisp-indent-adjust-parens)) (defun my/normal-state-lisp-dedent-adjust-parens () "Like `lisp-dedent-adjust-parens', but got to first char on line first." (interactive) (back-to-indentation) (lisp-dedent-adjust-parens)) (evil-define-key 'normal adjust-parens-mode-map (kbd "") #'my/normal-state-lisp-indent-adjust-parens (kbd "") #'my/normal-state-lisp-dedent-adjust-parens)) ;; for when the files are just too large (use-package vlf :demand t :config (require 'vlf-setup)) ;; allow copy from termainl (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-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 minibuffer-prompt-properties '(read-only t 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)) :init (setq embark-quit-after-action nil) (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none)))) :config (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 (nconc completion-at-point-functions '(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 :bind (:map flymake-mode-map ("C-c e" . my/flymake-show-diagnostic-at-point) ("C-c C-e" . consult-flymake)) ;; :hook (emacs-lisp-mode . flymake-mode) :init (defun my/flymake-show-diagnostic-at-point () (interactive) (when flymake-mode (let* ((diag (get-char-property (point) 'flymake-diagnostic)) (diag-msg (when diag (apply 'concat (mapcar (lambda (msg) (concat "•" msg "\n")) (split-string (flymake--diag-text diag) "\n"))))) (jinx-msg (when (jinx--get-overlays (point) (1+ (point))) "•misspelled word\n"))) (unless (and (zerop (length diag-msg)) (zerop (length jinx-msg))) (my/floating-tooltip " *flymake-error-posframe*" (concat diag-msg jinx-msg))))))) ;; flycheck (use-package flycheck :hook (emacs-lisp-mode . flycheck-mode) :bind (:map flycheck-mode-map ("C-c e" . my/flycheck-show-diagnostic-at-point)) :custom (flycheck-indication-mode 'left-margin) :init (setq flycheck-display-errors-function nil) (defun my/flycheck-show-diagnostic-at-point () (interactive) (if-let ((flycheck-mode) (message (apply 'concat (nconc (mapcar (lambda (error) (concat "•" (flycheck-error-message error) "\n")) (flycheck-overlay-errors-at (point))) (when (jinx--get-overlays (point) (1+ (point))) '("•misspelled word\n"))))) ((not (zerop (length message))))) (my/floating-tooltip " *flycheck-error-posframe*" (substring message 0 (1- (length message))))))) (use-package consult-flycheck :defer nil :bind (:map flycheck-mode-map ("C-c C-e" . consult-flycheck) :map emacs-lisp-mode-map ("C-c C-e" . consult-flycheck))) ;; 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 (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")))) ;; 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) (define-key bibtex-mode-map (kbd "RET") 'newline-and-indent) (define-key bibtex-mode-map (kbd "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)) :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)) :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) (setq-default TeX-master nil) (require 'tex) (TeX-global-PDF-mode 1)) ;; 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) ;; 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)) ; noindent :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) (line-number-mode -1) (column-number-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 :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 (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-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"))))) (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) ("C-x m" . mu4e-compose-new) :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) (setq message-kill-buffer-on-exit 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 `(,(make-mu4e-context :name "Personal" :enter-func (lambda () (mu4e-message "Entered personal context")) :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 "flag:unread AND NOT flag:trashed AND NOT maildir:/protonmail/Spam" :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 "flag:unread AND NOT flag:trashed AND NOT maildir:/protonmail/Spam") :config (mu4e-alert-set-default-style 'libnotify)) (mu4e t) (mu4e-context-switch nil "Personal") ;; 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)) (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