emacs-config/init.el

2452 lines
90 KiB
EmacsLisp

;;; 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))
(had-valid-buffer)
(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))
(setq had-valid-buffer t)
(act-on (ask-about buffer t) buffer t)))
(unless had-valid-buffer
(message "Nothing to do..."))
(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
:defer nil
:config
(global-kkp-mode 1)
(defun my/-kkp-after-terminal-setup ()
;; Make tab and backtab work properly
(define-key input-decode-map [(control ?i)] [tab])
(define-key input-decode-map [(control ?I)] [backtab])
(define-key input-decode-map [(control ?m)] [return]))
(defun my/-kkp-after-terminal-teardown (term)
(with-selected-frame (car (frames-on-display-list term))
(define-key input-decode-map [(control ?i)] nil t)
(define-key input-decode-map [(control ?I)] nil t)
(define-key input-decode-map [(control ?m)] 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-disable-in-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-<return>" . 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 "<normal-state> 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))
;; better lisp editing
(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 "<tab>") #'my/lisp-indent-adjust-parens
(kbd "<backtab>") #'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
(setq xclip-method 'wl-copy
xclip-program (symbol-name xclip-method))
(xclip-mode 1)
(defun my/-xclip-detect-wl-paste-error (oldfun type)
(if (eq xclip-method 'wl-copy)
;; Direct from `xclip-get-selection'
(when (and (getenv "WAYLAND_DISPLAY")
(memq type '(clipboard CLIPBOARD primary PRIMARY)))
(let* ((exit-code 0)
(output
(with-output-to-string
(setq exit-code
(apply #'call-process (replace-regexp-in-string
"\\(.*\\)copy" "\\1paste"
xclip-program 'fixedcase)
nil standard-output nil
"-n" (if (memq type '(primary PRIMARY))
'("-p")))))))
(if (zerop exit-code)
output
"")))
(funcall oldfun type)))
(advice-add 'xclip-get-selection :around 'my/-xclip-detect-wl-paste-error))
;; 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-<return>" . 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-<tab>" . 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/sly-notes-at-point (&optional pos buffer)
"Return the sly notes at POS in BUFFER.
If BUFFER is nil, the current buffer is used."
(with-current-buffer (or buffer (current-buffer))
(unless pos
(setq pos (point)))
(cl-loop for overlay in (overlays-at pos)
for note = (overlay-get overlay 'sly-note)
when note
collect note)))
(defun my/diagnostic-at-point ()
"Show the diagnostics under point."
(interactive)
(let ((message))
(when-let (((bound-and-true-p flymake-mode))
(diag (get-char-property (point) 'flymake-diagnostic)))
(cl-callf nconc message (string-split (flymake--diag-text diag) "\n" t)))
(when (bound-and-true-p flycheck-mode)
(cl-callf nconc message
(mapcar 'flycheck-error-message (flycheck-overlay-errors-at (point)))))
;; sly (lazy-loaded)
(when (featurep 'sly)
(cl-callf nconc message (mapcar (lambda (note)
(plist-get note :message))
(my/sly-notes-at-point))))
;; jinx
(when-let (((bound-and-true-p jinx-mode))
(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 "<mouse-1>") #'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 "<mouse-1>") #'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 "<mouse-1>") #'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 "<mouse-2>") #'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
("<tab>" . 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))
;; 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/diagnostic-at-point))
:autoload sly-connected-p
:init
(defun my/-lisp-mode-autoconnect-sly ()
(unless (sly-connected-p)
(sly)))
(setq inferior-lisp-program "/usr/bin/sbcl")
(defun my/-sly-fix-special-buffers ()
(when (string-match-p (rx bos "*" (* any) "*" eos) (buffer-name))
(setq-local show-trailing-whitespace nil)))
(add-hook 'lisp-mode-hook 'my/-sly-fix-special-buffers)
: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-<tab>" . 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 "<return>") '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)
("<tab>" . 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
(evil-define-key '(motion visual normal) proced-mode-map
"u" 'proced-unmark)
(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
("<mouse-8>" . my/helpful-history-back)
("<mouse-9>" . my/helpful-history-forward)
("<normal-state><" . my/helpful-history-back)
("<normal-state>>" . 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