emacs-config/init.el

1075 lines
37 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)
(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)
;; Spell stuff
(setq ispell-program-name "hunspell"
flyspell-issue-message-flag nil
flyspell-issue-welcome-flag nil)
;; 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)
;; 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))))
;; c-ts-mode
(use-package c-ts-mode
:after evil
: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))
;; recentf
(use-package recentf
:init
(setq recentf-exclude '("^/tmp/.*"
"^~/.mail/[^/]/Drafts/.*"
(format "^%svar/elpa/.*" 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-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) 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-literal
orderless-regexp)))
(setq completion-styles '(orderless basic)
completion-category-defaults nil
completion-category-overrides '((file
(styles basic partial-completion))
(command
(my/orderless-with-initialism basic))
(eglot
(orderless 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))
:init
(evil-define-key '(normal visual motion operator insert replace) 'global
(kbd "M-e") #'embark-act
(kbd "M-E") #'embark-dwim)
(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 3)
(global-corfu-mode 1)
(corfu-popupinfo-mode 1)
:config
(add-to-list 'corfu-continue-commands #'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)
("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 'truncate-sym-name-if-fit))
;; dumb-jump
(use-package dumb-jump
:init
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate))
;; eglot
;; (use-package eglot
;; :demand t
;; :hook (((c-ts-mode c++-ts-mode java-ts-mode rust-ts-mode python-ts-mode
;; latex-mode markdown-mode blueprint-ts-mode) . eglot-ensure)
;; (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 eldoc-echo-area-use-multiline-p nil
;; 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"))))
;; flycheck
(use-package flycheck
:hook (emacs-lisp-mode . flycheck-mode)
:bind (:map flycheck-mode-map
("C-c e" . my/flycheck-show-diagnostic-at-point))
:init
(setq flycheck-display-errors-function nil)
(defun my/flycheck-show-diagnostic-at-point ()
(interactive)
(if-let ((flycheck-mode)
(errors (flycheck-overlay-errors-at (point)))
(message (apply 'concat
(mapcar
(lambda (error)
(concat "" (flycheck-error-message error) "\n"))
errors))))
(if (display-graphic-p)
(progn
(posframe-show " *flycheck-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 " *flycheck-error-posframe*"))
(popup-tip message)))))
(use-package consult-flycheck
:bind (:map flycheck-mode-map
("C-c C-e" . consult-flycheck)))
;; lsp-mode
(use-package consult-lsp)
(use-package lsp-mode
:hook (((c-ts-mode c++-ts-mode java-ts-mode rust-ts-mode python-ts-mode
latex-mode markdown-mode blueprint-ts-mode) . lsp-mode)
(lsp-mode . my/-setup-lsp-mode-buffer))
:init
(setq lsp-completion-provider :none
lsp-headerline-breadcrumb-enable nil
lsp-inlay-hint-enable nil)
(defun my/-setup-lsp-mode-buffer ()
"Called by `lsp-mode-hook' to setup lsp-mode buffers."
(evil-define-key '(normal visual motion) 'local
"gR" #'lsp-rename
"gA" #'lsp-execute-code-action
"gs" #'consult-lsp-symbols)
(setq-local evil-lookup-func #'lsp-describe-thing-at-point)))
;; 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)))
;; blueprint
(use-package blueprint-ts-mode
:after eglot)
;; rust
(use-package rust-mode)
;; markdown
(use-package markdown-mode)
;; cmake
(use-package cmake-mode
:ensure nil
:hook (cmake-ts-mode . my/setup-cmake-ts-mode)
:init
(defun my/setup-cmake-ts-mode ()
"Setup `cmake-ts-mode' buffers."
(setq-local indent-line-function #'cmake-indent)))
;; 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"))
;; calc
(use-package calc
:ensure nil
:bind ("C-c m" . calc))
;; vterm
(use-package vterm
:hook (vterm-mode . with-editor-export-editor)
:init
(defvar my/project-vterm-hash-table (make-hash-table :test 'equal)
"Hash table that maps project root dirs to vterm buffers.")
(defun my/project-vterm (prompt)
"Switch to or create a vterm buffer in the current projects root."
(interactive (list t))
(if-let ((proj (project-current prompt))
(default-directory (project-root proj)))
(if-let ((vterm-buff (gethash default-directory
my/project-vterm-hash-table))
((buffer-live-p vterm-buff)))
(switch-to-buffer vterm-buff)
(puthash default-directory
(vterm (concat "*vterm for project " default-directory "*"))
my/project-vterm-hash-table))))
(defun my/project-vterm-or-default ()
"Open a vterm for the current project, otherwise, open a normal vterm."
(interactive)
(unless (my/project-vterm nil)
(if-let ((vterm-buff (gethash nil my/project-vterm-hash-table))
((buffer-live-p vterm-buff)))
(switch-to-buffer vterm-buff)
(puthash nil (vterm vterm-buffer-name) my/project-vterm-hash-table)))))
;; eat (mostly for eshell purposes)
(use-package eat)
;; 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\" $*")))
(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 (car-safe (car-safe (magit-list-worktrees))))
(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-status ()
"Get git status for `my/-eshell-prompt-function'"
(let ((branch (car (vc-git-branches)))
(state (my/-eshell-prompt-git-state-chars)))
(concat
(propertize (concat " 󰊢 " branch) 'face '(:foreground "medium purple"))
(unless (string-empty-p state)
(propertize (concat " [" state "]") 'face '(:foreground "red"))))))
(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)
(evil-define-key '(normal visual motion) dired-mode-map
"u" #'dired-unmark
"U" #'dired-unmark-all-marks)
(evil-define-key '(normal visual motion) wdired-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))
: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-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))
;; 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-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