emacs-config/init.el

1209 lines
41 KiB
EmacsLisp
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; 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
(setq use-package-always-ensure t
package-user-dir "~/.emacs.d/var/elpa")
(require 'use-package))
;; 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))
;; basic stuff
(use-package emacs
:hook ((emacs-lisp-mode . my/-emacs-lisp-mode-setup-evil-lookup)
(prog-mode . electric-pair-local-mode)
((text-mode message-mode tex-mode) . flyspell-mode)
((text-mode message-mode tex-mode) . auto-fill-mode)
(prog-mode . flyspell-prog-mode))
:init
(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 `form-at-point'."
(interactive)
(let ((form (form-at-point)))
(if (consp form)
(describe-symbol (cadr form))
(describe-symbol form))))
;; 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)
;; 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)
;; 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)
;; Set fonts
(add-to-list 'default-frame-alist '(font . "FiraCode Nerd Font Mono-12"))
;; Some settings for programming
(setq-default indent-tabs-mode nil
tab-width 4)
;; 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")
(css "https://github.com/tree-sitter/tree-sitter-css")
(go "https://github.com/tree-sitter/tree-sitter-go")
(js "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")))
;; 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)
(css-mode . css-ts-mode)
(js-mode . js-ts-mode)
(cmake-mode . cmake-ts-mode))))
(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))
;; flyspell
(use-package flyspell
:config
(setq ispell-program-name "hunspell"
flyspell-issue-message-flag nil
flyspell-issue-welcome-flag nil)
(define-key flyspell-mode-map (kbd "C-;") nil t)
(define-key flyspell-mode-map (kbd "C-,") nil t))
;; recentf
(use-package recentf
:init
(setq recentf-exclude `("^/tmp/.*"
"^~/.mail/[^/]/Drafts/.*"
,(format "^%svar/elpa/.*" user-emacs-directory)
,(format "^%svar/gnus/.*" user-emacs-directory)
,(format "^%setc/gnus/.*" user-emacs-directory)))
:bind ("C-c r" . recentf)
:config
(recentf-mode 1))
;; kitty keyboard protocol
(use-package kkp
:config
(global-kkp-mode 1))
;; evil
(use-package evil
:init
(setq evil-want-integration t
evil-want-C-d-scroll nil
evil-want-keybinding nil
evil-undo-system 'undo-redo
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))
;; 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))
;; 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))
:init
(setq embark-quit-after-action nil)
(add-to-list 'display-buffer-alist
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
;; 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)
("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)
;; 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
("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
completion-cycle-threshold nil)
(global-corfu-mode 1)
(corfu-popupinfo-mode 1)
:config
(add-to-list 'corfu-continue-commands #'my/corfu-move-to-minibuffer))
(use-package corfu-terminal
:init
(corfu-terminal-mode 1))
;; cape (a bunch of capfs!)
(use-package cape
:bind (("C-c p" . cape-dabbrev)
([remap dabbrev-expand] . cape-dabbrev)
("C-c P" . cape-line))
:hook (text-mode . my/-cape-setup-text-mode)
:init
(defun my/-cape-setup-text-mode ()
(setq-local completion-at-point-functions
'(cape-dict cape-dabbrev)
corfu-auto nil))
(add-to-list 'completion-at-point-functions #'cape-file))
;; 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)
;; 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)
(if-let ((pos (point))
(diag (and flymake-mode
(get-char-property pos 'flymake-diagnostic)))
(message (flymake--diag-text diag)))
(if (display-graphic-p)
(progn
(posframe-show " *flymake-error-posframe*"
:string message
:position (point)
:max-width 80
:border-width 2
:border-color "white")
(clear-this-command-keys)
(push (read-event) unread-command-events)
(posframe-hide " *flymake-error-posframe*"))
(popup-tip message)))))
;; eldoc
(use-package eldoc
:diminish eldoc-mode
:init
(setq-default eldoc-echo-area-use-multiline-p nil))
;; eglot
(use-package eglot
:demand t
: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)
(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-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"
"--malloc-trim" "--function-arg-placeholders"))))
;; realgud
(use-package realgud
:hook (realgud-short-key-mode . my/-realgud-setup-shortkey-mode)
:bind (:map realgud:shortkey-mode-map
("a" . realgud:cmd-frame))
:init
(setq realgud-srcbuf-lock t
realgud-short-key-on-tracing? t)
(defun my/-realgud-setup-shortkey-mode ()
(when realgud-short-key-mode
(keymap-local-set "C-d" realgud:shortkey-mode-map))))
;; dumb-jump
(use-package dumb-jump
:init
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate))
;; yasnippet
(use-package yasnippet
:bind ("C-c s" . yas-expand)
:init
(yas-global-mode 1))
;; project.el
(use-package project
:bind (("C-c v" . my/project-eshell-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)))
(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 my/project-run-command
(car my/-project-run-history))
(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)))
;; (La)TeX
(use-package tex-mode
:hook (latex-mode . eglot-ensure))
;; 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)
:bind (:map c-ts-mode-map
("C-c d" . realgud:gdb)
:map c++-ts-mode-map
("C-c d" . realgud:gdb)
:map objc-mode-map
("C-c d" . realgud:gdb))
: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)))
;; rust
(use-package rust-mode)
(use-package rust-ts-mode
:ensure nil
:hook (rust-ts-mode . eglot-ensure))
;; markdown
(use-package markdown-mode
:hook ((markdown-mode . auto-fill-mode)
(markdown-mode . eglot-ensure)))
;; 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)
(add-hook 'cmake-ts-mode-hook #'eglot-ensure))
;; json
(use-package json-mode)
;; firejail
(require 'firejail-mode)
;; yaml
(use-package yaml-mode)
;; yuck (config language for eww)
(use-package yuck-mode)
;; sly
(use-package sly
:hook (lisp-mode . my/common-lisp-autoconnect-sly)
:autoload sly-connected-p
:init
(defun my/common-lisp-autoconnect-sly ()
(unless (sly-connected-p)
(save-excursion (sly))))
(setq inferior-lisp-program "/usr/bin/sbcl"))
;; pdf-tools
(use-package pdf-tools
:hook (pdf-view-mode . my/setup-pdf-view-mode)
:init
(defun my/setup-pdf-view-mode ()
(display-line-numbers-mode -1)
(setq-local cursor-type nil))
(pdf-tools-install))
;; calc
(use-package calc
:ensure nil
:bind (: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)
(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 "<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.
Take directly from doom-modeline."
(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"))))))))
;; eat
(use-package eat
: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 "*")))
(puthash default-directory
(eat)
my/project-eaeat-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 (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))
: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")))
(defun eshell/bm (&optional name)
"Change to directory of bookmark NAME.
If no name is given, list all bookmarks instead."
(if name
(eshell/cd (bookmark-get-filename name))
(eshell-print (string-join (bookmark-all-names) " "))))
(defun my/-replace-home-with-tilda (path)
(let ((home (getenv "HOME")))
(if (equal home path)
"~"
(setq home (file-name-as-directory home))
(if (string-prefix-p home path)
(concat "~/" (seq-subseq path (length home)))
path))))
(defun my/-eshell-prompt-cut-path (num path)
"Cut PATH down to NUM components."
(let ((parts (string-split path "/" t nil)))
(concat
(when (and (file-name-absolute-p path)
(not (equal "~" (car parts)))
(<= (length parts) num))
"/")
(string-join (last parts num) "/"))))
(defun my/-eshell-prompt-get-dir ()
"Get dir for `my/-eshell-prompt-function'"
(my/-eshell-prompt-cut-path 3
(if-let ((worktree (vc-root-dir))
(parent (file-name-parent-directory worktree)))
(file-relative-name default-directory parent)
(my/-replace-home-with-tilda default-directory))))
(defun my/-eshell-prompt-status-char-for-branch (branch remote)
"Get the status char representing the relation between BRANCH and REMOTE."
(let ((lines (process-lines vc-git-program
"rev-list"
"--left-right"
(concat branch "..." remote)))
(to-remote nil)
(to-local nil))
(dolist (line lines)
(if-let (((not (string-empty-p line)))
(dir-char (aref line 0)))
(if (= dir-char ?<)
(setq to-remote t)
(setq to-local t))))
(cond
((and to-remote to-local) ?󰹺)
(to-remote ?󰜷)
(to-local ?󰜮))))
(defun my/-eshell-prompt-current-branch-status ()
"Get the status char for the current branch and its remote."
(let ((refs (process-lines vc-git-program
"for-each-ref"
"--format=%(HEAD)%00%(refname:short)%00%(upstream:short)"
"refs/heads")))
(catch 'break
(dolist (ref refs)
(if-let ((split-ref (split-string ref "\0" nil nil))
((equal (car split-ref) "*")))
(throw 'break (my/-eshell-prompt-status-char-for-branch
(cadr split-ref)
(caddr split-ref))))))))
(defun my/-eshell-prompt-git-state-chars ()
"Get chars, like + and ✘ for `my/-eshell-prompt-function'."
(let ((lines (process-lines vc-git-program "status" "--porcelain=v1"))
(branch-status (my/-eshell-prompt-current-branch-status))
(status-arr))
(dolist (line lines)
(cl-loop with fields = (string-split line " " t " *")
with status-str = (car-safe fields)
for status-char across status-str
do
(cond ((or (= status-char ?M) (= status-char ?T))
(add-to-list 'status-arr ?!))
((= status-char ??)
(add-to-list 'status-arr ??))
((or (= status-char ?A) (= status-char ?C))
(add-to-list 'status-arr ?+))
((= status-char ?D)
(add-to-list 'status-arr ?))
((= status-char ?R)
(add-to-list 'status-arr )))))
(sort status-arr #'<)
(when branch-status
(push branch-status status-arr))
(apply 'string status-arr)))
(defun my/-eshell-prompt-git-get-operation ()
"Return the current git operation. For example, a revert."
(let ((git-dir (expand-file-name ".git" (vc-git-root default-directory))))
(cond
((file-exists-p (expand-file-name "REVERT_HEAD" git-dir))
"REVERTING"))))
(defun my/-eshell-prompt-git-status ()
"Get git status for `my/-eshell-prompt-function'"
(let ((branch (car (vc-git-branches)))
(state (my/-eshell-prompt-git-state-chars))
(operation (my/-eshell-prompt-git-get-operation)))
(concat
(propertize (concat " 󰊢 " branch) 'face '(:foreground "medium purple"))
(unless (string-empty-p state)
(propertize (concat " [" state "]") 'face '(:foreground "red")))
(when operation
(concat " (" (propertize operation 'face
'(:weight 'bold :foreground "yellow")) ")")))))
(defun my/-eshell-prompt-vc-status ()
"Get vc status for `my/-eshell-prompt-function'."
(if-let (backend (vc-responsible-backend default-directory t))
(if (eq backend 'Git)
(my/-eshell-prompt-git-status)
(my/-eshell-prompt-set-face-color
(concat "" (downcase (symbol-name backend)))
"purple"))))
(defvar-local my/-eshell-prompt-last-start-time nil
"Start time of last eshell command.")
(defun my/-eshell-prompt-timer-pre-cmd ()
"Command run before each eshell program to record the time."
(setq my/-eshell-prompt-last-start-time (current-time)))
(add-hook 'eshell-pre-command-hook #'my/-eshell-prompt-timer-pre-cmd)
(defun my/-eshell-prompt-format-span (span)
"Format SPAN as \"XhXms.\""
(let* ((hours (/ span 3600))
(mins (% (/ span 60) 60))
(secs (% span 60)))
(concat (unless (= hours 0)
(format "%dh" hours))
(unless (= mins 0)
(format "%dm" mins))
(format "%ds" secs))))
(defun my/-eshell-prompt-last-command-time (end-time)
"Return the prompt component for the time of the last command."
(if-let ((my/-eshell-prompt-last-start-time)
(len (time-subtract end-time
my/-eshell-prompt-last-start-time))
(float-len (float-time len))
((< 3 float-len))
(int-len (round float-len)))
(concat " time "
(propertize (my/-eshell-prompt-format-span int-len)
'face '(:foreground "gold1")))))
(defun my/-eshell-prompt-function ()
"Function for `eshell-prompt-function'"
(let* ((end-time (current-time))
(dir (my/-eshell-prompt-get-dir))
(prompt (concat
"\n"
(propertize dir 'face '(:foreground "dark turquoise"))
(unless (file-writable-p dir)
"")
(my/-eshell-prompt-vc-status)
(my/-eshell-prompt-last-command-time end-time)
(propertize "\n" 'read-only t 'rear-nonsticky t)
(propertize
" " 'face `(:foreground
,(if (= eshell-last-command-status 0)
"lime green"
"red"))
'rear-nonsticky t))))
(setq my/-eshell-prompt-last-start-time nil)
prompt))
(setq eshell-prompt-function #'my/-eshell-prompt-function
eshell-prompt-regexp "^ "
eshell-highlight-prompt nil))
(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))
;; proced
(use-package proced
:bind ("C-x j" . proced)
:init
(setq proced-auto-update-flag t
proced-auto-update-interval 1))
;; 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))
: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))
(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)
;; mu4e
(require 'auth-source-pass)
(auth-source-pass-enable)
(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))
:bind (("C-x C-m" . mu4e)
("C-x m" . mu4e-compose-new)
:map message-mode-map
("C-c k" . khard-insert-email-contact))
:init
(require 'mu4e)
(evil-define-key '(normal motion) mu4e-main-mode-map "q" #'bury-buffer)
(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))
(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-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 . ,(auth-source-pass-get "email" "emacs/mu4e-protonmail"))
(user-full-name . ,(auth-source-pass-get "name" "emacs/mu4e-protonmail"))
(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")
;; rainbow-delimiters
(use-package rainbow-delimiters
:hook (prog-mode . rainbow-delimiters-mode))
;; 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))
;; 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))
;;; init.el ends here