;;; 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 calc-mode calc-trail-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-" . 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 lsp-signature-doc-lines 1) (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 (:map calc-mode-map ("M-" . calc-roll-up) ("M-TAB" . calc-roll-up)) :hook ((calc-mode calc-trail-mode) . my/setup-calc-calc-trail-mode) :init (defun my/setup-calc-calc-trail-mode () (setq-local doom-modeline-percent-position '() truncate-partial-width-windows nil) (visual-line-mode -1) (display-line-numbers-mode -1) (line-number-mode -1) (column-number-mode -1) (toggle-truncate-lines 1)) :config (evil-define-key '(normal visual motion) calc-edit-mode-map (kbd "RET") 'calc-edit-return (kbd "") 'calc-edit-return) (defun my/-calc-float-mode-string () (cl-destructuring-bind (mode prec) calc-float-format (concat (upcase-initials (symbol-name mode)) (unless (zerop prec) (concat ": " (number-to-string prec)))))) (doom-modeline-def-segment calc "Display calculator icons and info. 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")))))))) ;; 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