From 7b718d276e1d9f01287baea7187baef618e6b947 Mon Sep 17 00:00:00 2001
From: Amin Bandali <bandali@kelar.org>
Date: Sat, 8 Feb 2025 10:38:38 -0500
Subject: Add bandali-essentials and bandali-utils, use use-package

---
 .emacs.d/init.el                    | 407 +-----------------------------------
 .emacs.d/lisp/bandali-essentials.el | 402 +++++++++++++++++++++++++++++++++++
 .emacs.d/lisp/bandali-utils.el      |  94 +++++++++
 3 files changed, 505 insertions(+), 398 deletions(-)
 create mode 100644 .emacs.d/lisp/bandali-essentials.el
 create mode 100644 .emacs.d/lisp/bandali-utils.el

diff --git a/.emacs.d/init.el b/.emacs.d/init.el
index f9518c2..7ab00be 100644
--- a/.emacs.d/init.el
+++ b/.emacs.d/init.el
@@ -113,7 +113,7 @@ plain variables.  This means that `setopt' will execute any
   (load custom-file 'noerror))
 
 ;; Start Emacs server
-;; (https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html)
+;; https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html
 (run-with-idle-timer 0.5 nil #'require 'server)
 (with-eval-after-load 'server
   (declare-function server-edit "server")
@@ -121,362 +121,13 @@ plain variables.  This means that `setopt' will execute any
   (declare-function server-running-p "server")
   (or (server-running-p) (server-mode)))
 
-
-;;; Defaults
-
-;;;; C source code
-
-(setq-default
- ;; Case-sensitive search (and `dabbrev-expand').
- ;; case-fold-search nil
- indent-tabs-mode nil  ; always use space for indentation
- ;; tab-width 4
- indicate-buffer-boundaries 'left)
-
-(setq
- ;; line-spacing 3
- completion-ignore-case t
- read-buffer-completion-ignore-case t
- enable-recursive-minibuffers t
- resize-mini-windows t
- message-log-max 20000
- mode-line-compact t
- ;; mouse-autoselect-window t
- scroll-conservatively 15
- scroll-preserve-screen-position 1
- ;; I don't feel like randomly jumping out of my chair.
- ring-bell-function 'ignore)
-
-;;;; elisp source code
-
-(with-eval-after-load 'minibuffer
-  (setopt read-file-name-completion-ignore-case t))
-
-(with-eval-after-load 'files
-  (setopt
-   make-backup-files nil
-   ;; Insert newline at the end of files.
-   ;; require-final-newline t
-   ;; Open read-only file buffers in view-mode, to get `q' for quit.
-   view-read-only t)
-  (add-to-list 'auto-mode-alist '("\\README.*" . text-mode))
-  (add-to-list 'auto-mode-alist '("\\.*rc$" . conf-mode))
-  (add-to-list 'auto-mode-alist '("\\.bashrc$" . sh-mode))
-  (add-to-list 'auto-mode-alist '("\\COMMIT_EDITMSG$" . text-mode)))
-
-(setq disabled-command-function nil)
-
-(run-with-idle-timer 0.1 nil #'require 'autorevert)
-(with-eval-after-load 'autorevert
-  (setopt
-   ;; auto-revert-verbose nil
-   global-auto-revert-non-file-buffers nil)
-  (global-auto-revert-mode 1))
-
-(run-with-idle-timer 0.1 nil #'require 'time)
-(with-eval-after-load 'time
-  (setopt
-   display-time-default-load-average nil
-   display-time-format " %a %Y-%m-%d %-l:%M%P"
-   display-time-mail-icon
-   '(image :type xpm :file "gnus/gnus-pointer.xpm" :ascent center)
-   display-time-use-mail-icon t
-   zoneinfo-style-world-list
-   `(,@zoneinfo-style-world-list
-     ("Etc/UTC" "UTC")
-     ("Asia/Tehran" "Tehran")
-     ("Australia/Melbourne" "Melbourne")))
-  (unless (display-graphic-p)
-    (display-time-mode)))
-
-(defvar b/battery-format "%p%b %t")
-(run-with-idle-timer 0.1 nil #'require 'battery)
-(with-eval-after-load 'battery
-  (setopt battery-mode-line-format (format " [%s]" b/battery-format))
-  ;; (display-battery-mode -1)
-  )
-
-(run-with-idle-timer 0.5 nil #'require 'winner)
-(with-eval-after-load 'winner
-  (winner-mode 1)
-  (when (featurep 'exwm)
-    ;; prevent a bad interaction between EXWM and winner-mode, where
-    ;; sometimes closing a window (like closing a terminal after
-    ;; entering a GPG password via pinentry-gnome3's floating window)
-    ;; results in a dead frame somewhere and effectively freezes EXWM.
-    (advice-add
-     'winner-insert-if-new
-     :around
-     (lambda (orig-fun &rest args)
-       ;; only add the frame if it's live
-       (when (frame-live-p (car args))
-         (apply orig-fun args))))))
-
-(run-with-idle-timer 0.5 nil #'require 'windmove)
-(with-eval-after-load 'windmove
-  (setopt windmove-wrap-around t)
-  (b/keymap-global-set "M-H" #'windmove-left)
-  (b/keymap-global-set "M-L" #'windmove-right)
-  (b/keymap-global-set "M-K" #'windmove-up)
-  (b/keymap-global-set "M-J" #'windmove-down))
-
-(with-eval-after-load 'isearch
-  (setopt
-   isearch-allow-scroll t
-   isearch-lazy-count t
-   ;; Match non-ASCII variants during search
-   search-default-mode #'char-fold-to-regexp))
-
-(b/keymap-global-set "C-x v C-=" #'vc-ediff)
-
-(with-eval-after-load 'vc-git
-  (setopt
-   ;; vc-git-show-stash 0
-   vc-git-print-log-follow t))
-
-(with-eval-after-load 'ediff
-  (setopt
-   ediff-window-setup-function #'ediff-setup-windows-plain
-   ediff-split-window-function #'split-window-horizontally))
-
-;; (with-eval-after-load 'face-remap
-;;   (setopt
-;;    ;; Gentler font resizing.
-;;    text-scale-mode-step 1.05))
-
-(run-with-idle-timer 0.4 nil #'require 'mwheel)
-(with-eval-after-load 'mwheel
-  (setopt
-   mouse-wheel-scroll-amount '(1 ((shift) . 1)) ; one line at a time
-   mouse-wheel-progressive-speed nil    ; don't accelerate scrolling
-   mouse-wheel-follow-mouse t))         ; scroll window under mouse
-
-(run-with-idle-timer 0.4 nil #'require 'pixel-scroll)
-(with-eval-after-load 'pixel-scroll
-  (pixel-scroll-mode 1))
-
-(with-eval-after-load 'epg-config
-  (setopt
-   epg-gpg-program (executable-find "gpg")
-   ;; Ask for GPG passphrase in minibuffer.
-   ;; Will fail if gpg >= 2.1 is not available.
-   epg-pinentry-mode 'loopback))
-
-;; (with-eval-after-load 'auth-source
-;;   (setopt
-;;    auth-sources '("~/.authinfo.gpg")
-;;    authinfo-hidden
-;;    (regexp-opt '("password" "client-secret" "token"))))
-
-(with-eval-after-load 'info
-  (setq
-   Info-directory-list
-   `(,@Info-directory-list
-     ,(expand-file-name
-       (convert-standard-filename "info/") source-directory)
-     "/usr/share/info/")))
-
-(when (display-graphic-p)
-  (set-fontset-font t 'arabic "Sahel WOL")
-  (let ((emoji-font "Apple Color Emoji"))
-    (when (member emoji-font (font-family-list))
-      (set-fontset-font
-       t 'emoji `(,emoji-font . "iso10646-1") nil 'prepend)))
-  (with-eval-after-load 'faces
-    (let ((grey "#e7e7e7"))
-      ;; (set-face-attribute 'default nil
-      ;;                     :font "Source Code Pro"
-      ;;                     :height 113   ; 130 ; 105
-      ;;                     :weight 'medium)
-      ;; (set-face-attribute 'fixed-pitch nil
-      ;;                     :font "Source Code Pro"
-      ;;                     :weight 'medium)
-      ;; (set-face-attribute 'default nil
-      ;;                     :font "Inconsolata Medium-12:hinting=true:autohint=true")
-      ;; (set-face-attribute 'fixed-pitch nil
-      ;;                     :font "Inconsolata Medium-12:hinting=true:autohint=true")
-      (set-face-attribute 'default nil
-                          :font "Source Code Pro Medium-10.5")
-      (set-face-attribute 'fixed-pitch nil
-                          :font "Source Code Pro Medium-10.5")
-      (set-face-attribute 'mode-line nil
-                          :box '(:line-width 2 :style released-button)
-                          :background grey
-                          :inherit 'fixed-pitch))))
-
-(when (and (version< emacs-version "28") mode-line-compact)
-  ;; Manually make some `mode-line' spaces smaller.
-  ;; Emacs 28 and above do a terrific job at this out of the box
-  ;; when `mode-line-compact' is set to t (see above)."
-  (setq-default
-   mode-line-format
-   (mapcar
-    (lambda (x)
-      (if (and (stringp x)
-               (or (string= x "   ")
-                   (string= x "  ")))
-          " "
-        x))
-    mode-line-format)
-   mode-line-buffer-identification
-   (propertized-buffer-identification "%10b")))
-
-
-;;; Useful utilities
-
-(defun b/insert-asterism ()
-  "Insert a centred asterism."
-  (interactive)
-  (let ((asterism "* * *"))
-    (insert
-     (concat
-      "\n"
-      (make-string
-       (floor (/ (- fill-column (length asterism)) 2))
-       ?\s)
-      asterism
-      "\n"))))
-
-(defun b/join-line-top ()
-  "Like `join-line', but join next line to the current line."
-  (interactive)
-  (join-line 1))
-
-(defun b/*scratch* ()
-  "Switch to `*scratch*' buffer, creating it if it does not exist."
-  (interactive)
-  (let ((fun (if (functionp #'get-scratch-buffer-create)
-                 #'get-scratch-buffer-create ; (version<= "29" emacs-version)
-               #'startup--get-buffer-create-scratch))) ; (version< emacs-version "29")
-    (switch-to-buffer (funcall fun))))
-
-(defun b/duplicate-line-or-region (&optional n)
-  "Duplicate the current line, or region (if active).
-Make N (default: 1) copies of the current line or region."
-  (interactive "*p")
-  (let ((u-r-p (use-region-p))          ; if region is active
-        (n1 (or n 1)))
-    (save-excursion
-      (let ((text
-             (if u-r-p
-                 (buffer-substring (region-beginning) (region-end))
-               (prog1 (thing-at-point 'line)
-                 (end-of-line)
-                 (if (eobp)
-                     (newline)
-                   (forward-line 1))))))
-        (dotimes (_ (abs n1))
-          (insert text))))))
-
-(defun b/invert-default-face (arg)
-  "Invert the `default' and `mode-line' faces for the current frame.
-Swap the background and foreground for the two `default' and
-`mode-line' faces, effectively acting like a simple light/dark
-theme toggle.  If prefix argument ARG is given, invert the faces
-for all frames."
-  (interactive "P")
-  (let ((frame (unless arg
-                 (selected-frame))))
-    (invert-face 'default frame)
-    (invert-face 'mode-line frame)
-    (when (fboundp #'exwm-systemtray--refresh-background-color)
-      (exwm-systemtray--refresh-background-color 'remap))))
-
-(defun b/unfill-paragraph-or-region (&optional beg end)
-  "Unfill paragraph, or region (if active)."
-  (interactive "r")
-  (let ((fill-column most-positive-fixnum))
-    (if (use-region-p)
-        (fill-region beg end)
-      (fill-paragraph))))
-
-
-;;; General key bindings
-
-(let ((kfs
-       '(("C-c i"   . ielm)
-         ("C-c d"   . b/duplicate-line-or-region)
-         ("C-c j"   . b/join-line-top)
-         ("C-S-j"   . b/join-line-top)
-         ("C-c s c" . b/*scratch*)
-         ("C-c v"   . b/invert-default-face)
-         ("C-c q"   . b/unfill-paragraph-or-region)
-         ;; evaling and macro-expanding
-         ("C-c e b" . eval-buffer)
-         ("C-c e e" . eval-last-sexp)
-         ("C-c e m" . pp-macroexpand-last-sexp)
-         ("C-c e r" . eval-region)
-         ;; emacs things
-         ("C-c e i" . emacs-init-time)
-         ("C-c e u" . emacs-uptime)
-         ("C-c e v" . emacs-version)
-         ;; finding
-         ("C-c f ." . find-file)
-         ("C-c f l" . find-library)
-         ("C-c f p" . find-file-at-point)
-         ;; frames
-         ("C-c F m" . make-frame-command)
-         ("C-c F d" . delete-frame)
-         ;; help/describe
-         ("C-c h F" . describe-face))))
-  (dolist (kf kfs)
-    (let ((key (car kf))
-          (fun (cdr kf)))
-      (b/keymap-global-set key fun))))
-
-(when (display-graphic-p)
-  ;; Too easy to accidentally suspend (freeze) Emacs GUI.
-  (b/keymap-global-unset "C-z"))
-
 
 ;;; Essential packages
 
 (add-to-list 'load-path (b/emacs.d "lisp"))
 
+(require 'bandali-essentials)
 ;; (require 'bandali-exwm)
-
-;; recently opened files
-(run-with-idle-timer 0.2 nil #'require 'recentf)
-(with-eval-after-load 'recentf
-  (setopt recentf-max-saved-items 2000)
-  (recentf-mode)
-
-  (defun b/recentf-open ()
-  "Use `completing-read' to \\[find-file] a recent file."
-  (interactive)
-  (find-file
-   (completing-read "Find recent file: " recentf-list)))
-  (b/keymap-global-set "C-c f r" #'b/recentf-open))
-
-(with-eval-after-load 'help
-  (temp-buffer-resize-mode)
-  (setopt help-window-select t))
-
-(with-eval-after-load 'help-mode
-  (let ((m help-mode-map))
-    (b/keymap-set m "p" #'backward-button)
-    (b/keymap-set m "n" #'forward-button)
-    (b/keymap-set m "b" #'help-go-back)
-    (b/keymap-set m "f" #'help-go-forward)))
-
-(with-eval-after-load 'doc-view
-  (b/keymap-set doc-view-mode-map "M-RET" #'image-previous-line))
-
-(with-eval-after-load 'shr
-  (setopt shr-max-width 80))
-
-(with-eval-after-load 'mule-cmds
-  (setopt default-input-method "farsi-isiri-9147"))
-
-(with-eval-after-load 'tramp
-  (tramp-set-completion-function
-   "ssh"
-   (append (tramp-get-completion-function "ssh")
-           (mapcar (lambda (file) `(tramp-parse-sconfig ,file))
-                   (directory-files
-                    "~/.ssh/config.d/"
-                    'full directory-files-no-dot-files-regexp)))))
-
 (require 'bandali-eshell)
 (require 'bandali-ibuffer)
 (require 'bandali-dired)
@@ -492,52 +143,15 @@ for all frames."
 (require 'bandali-erc)
 
 
-;;; Editing
-
-;; Display Lisp objects at point in the echo area.
-(with-eval-after-load 'eldoc
-  (setopt eldoc-minor-mode-string " eldoc")
-  (global-eldoc-mode 1))
-
-;; highlight matching parens
-(run-with-idle-timer 0.2 nil #'require 'paren)
-(with-eval-after-load 'paren
-  (show-paren-mode 1))
+;;; Programming modes
 
-(with-eval-after-load 'simple
-  (setopt
-   ;; Save what I copy into clipboard from other applications into
-   ;; Emacs' kill-ring, which would allow me to still be able to
-   ;; easily access it in case I kill (cut or copy) something else
-   ;; inside Emacs before yanking (pasting) what I'd originally
-   ;; intended to.
-   save-interprogram-paste-before-kill t)
-  (column-number-mode 1)
-  (line-number-mode 1))
-
-(run-with-idle-timer 0.2 nil #'require 'savehist)
-(with-eval-after-load 'savehist
-  ;; Save minibuffer history.
-  (savehist-mode 1)
-  (add-to-list 'savehist-additional-variables 'kill-ring))
-
-;; Automatically save place in files.
-(run-with-idle-timer 0.2 nil #'require 'saveplace nil 'noerror)
-(with-eval-after-load 'saveplace
-  (save-place-mode 1))
-
-(with-eval-after-load 'flyspell
-  (setopt flyspell-mode-line-string " fly"))
-
-(with-eval-after-load 'text-mode
-  (add-hook 'text-mode-hook #'flyspell-mode)
-  (b/keymap-set text-mode-map "M-RET" #'b/insert-asterism))
-
-(with-eval-after-load 'abbrev
-  (add-hook 'text-mode-hook #'abbrev-mode))
+(use-package elisp-mode
+  :bind
+  ("C-c e e" . eval-last-sexp))
 
-
-;;; Programming modes
+(use-package pp
+  :bind
+  ("C-c e m" . pp-macroexpand-last-sexp))
 
 (with-eval-after-load 'lisp-mode
   (add-hook
@@ -594,9 +208,6 @@ for all frames."
 
 
 ;;; Emacs enhancements & auxiliary packages
-(with-eval-after-load 'man
-  (setopt Man-width 80))
-
 ;; `debbugs'
 (b/keymap-global-set "C-c D d" #'debbugs-gnu)
 (b/keymap-global-set "C-c D b" #'debbugs-gnu-bugs)
diff --git a/.emacs.d/lisp/bandali-essentials.el b/.emacs.d/lisp/bandali-essentials.el
new file mode 100644
index 0000000..a0ea5f1
--- /dev/null
+++ b/.emacs.d/lisp/bandali-essentials.el
@@ -0,0 +1,402 @@
+;;; bandali-essentials.el --- bandali's essentials -*- lexical-binding: t; -*-
+
+;; Copyright (c) 2018-2025 Amin Bandali <bandali@gnu.org>
+
+;; Author: Amin Bandali <bandali@gnu.org>
+;; Keywords: files, internal
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The core essentials of my GNU Emacs setup.
+
+;;; Code:
+
+(use-package emacs
+  :demand t
+  :bind
+  ("C-c e b" . eval-buffer)
+  ("C-c e r" . eval-region)
+  :config
+  (when (display-graphic-p)
+    ;; Too easy to accidentally suspend (freeze) Emacs GUI.
+    (b/keymap-global-unset "C-z"))
+
+  (setq-default
+   ;; Case-sensitive search (and `dabbrev-expand').
+   ;; case-fold-search nil
+   indent-tabs-mode nil  ; always use space for indentation
+   ;; tab-width 4
+   indicate-buffer-boundaries 'left)
+
+  (setq
+   ;; line-spacing 3
+   completion-ignore-case t
+   read-buffer-completion-ignore-case t
+   enable-recursive-minibuffers t
+   resize-mini-windows t
+   message-log-max 20000
+   mode-line-compact t
+   ;; mouse-autoselect-window t
+   scroll-conservatively 15
+   scroll-preserve-screen-position 1
+   ;; I don't feel like randomly jumping out of my chair.
+   ring-bell-function 'ignore)
+
+  ;; Mode-line compacting for older Emacsen.
+  (when (and (version< emacs-version "28") mode-line-compact)
+    ;; Manually make some `mode-line' spaces smaller.
+    ;; Emacs 28 and above do a terrific job at this out of the box
+    ;; when `mode-line-compact' is set to t (see above)."
+    (setq-default
+     mode-line-format
+     (mapcar
+      (lambda (x)
+        (if (and (stringp x)
+                 (or (string= x "   ")
+                     (string= x "  ")))
+            " "
+          x))
+      mode-line-format)
+     mode-line-buffer-identification
+     (propertized-buffer-identification "%10b")))
+
+  ;; Fonts and types.
+  (when (display-graphic-p)
+    (set-fontset-font t 'arabic "Sahel WOL")
+    (let ((emoji-font "Apple Color Emoji"))
+      (when (member emoji-font (font-family-list))
+        (set-fontset-font
+         t 'emoji `(,emoji-font . "iso10646-1") nil 'prepend)))
+    (with-eval-after-load 'faces
+      (let ((grey "#e7e7e7"))
+        ;; (set-face-attribute 'default nil
+        ;;                     :font "Source Code Pro"
+        ;;                     :height 113   ; 130 ; 105
+        ;;                     :weight 'medium)
+        ;; (set-face-attribute 'fixed-pitch nil
+        ;;                     :font "Source Code Pro"
+        ;;                     :weight 'medium)
+        ;; (set-face-attribute 'default nil
+        ;;                     :font "Inconsolata Medium-12:hinting=true:autohint=true")
+        ;; (set-face-attribute 'fixed-pitch nil
+        ;;                     :font "Inconsolata Medium-12:hinting=true:autohint=true")
+        (set-face-attribute 'default nil
+                            :font "Source Code Pro Medium-10.5")
+        (set-face-attribute 'fixed-pitch nil
+                            :font "Source Code Pro Medium-10.5")
+        (set-face-attribute 'mode-line nil
+                            :box '(:line-width 2 :style released-button)
+                            :background grey
+                            :inherit 'fixed-pitch)))))
+
+(use-package minibuffer
+  :custom
+  (read-file-name-completion-ignore-case t))
+
+(use-package files
+  :bind
+  ("C-c f ." . find-file)
+  :custom
+  (make-backup-files nil)
+  ;; Insert newline at the end of files.
+  ;; (require-final-newline t)
+  ;; Open read-only file buffers in view-mode, to get `q' for quit.
+  (view-read-only t))
+
+(use-package ffap
+  :bind
+  ("C-c f p" . find-file-at-point))
+
+(use-package find-func
+  :bind
+  ("C-c f l" . find-library))
+
+(use-package frame
+  :bind
+  ("C-c F m" . make-frame-command)
+  ("C-c F d" . delete-frame))
+
+(use-package text-mode
+  :mode "\\(README.*\\|COMMIT_EDITMSG$\\)")
+
+(use-package conf-mode
+  :mode "\\.*rc$")
+
+(use-package sh-script
+  :mode ("\\.bashrc$" . sh-mode))
+
+(use-package novice
+  :config
+  (setq disabled-command-function nil))
+
+(use-package autorevert
+  :defer 0.1
+  :custom
+  ;; (auto-revert-verbose nil)
+  (global-auto-revert-non-file-buffers nil)
+  :config
+  (global-auto-revert-mode 1))
+
+(use-package time
+  :defer 0.1
+  :bind
+  ("C-c e i" . emacs-init-time)
+  ("C-c e u" . emacs-uptime)
+  :custom
+  (display-time-default-load-average nil)
+  (display-time-format " %a %Y-%m-%d %-l:%M%P")
+  (display-time-mail-icon
+   '(image :type xpm :file "gnus/gnus-pointer.xpm" :ascent center))
+  (display-time-use-mail-icon t)
+  :config
+  (setopt
+   zoneinfo-style-world-list
+   `(,@zoneinfo-style-world-list
+     ("Etc/UTC" "UTC")
+     ("Asia/Tehran" "Tehran")
+     ("Australia/Melbourne" "Melbourne")))
+  (unless (display-graphic-p)
+    (display-time-mode 1)))
+
+(defvar b/battery-format "%p%b %t")
+(use-package battery
+  ;; :if (not (display-graphic-p))
+  :custom
+  (battery-mode-line-format (format " [%s]" b/battery-format)))
+
+(use-package winner
+  :defer 0.5
+  :config
+  (winner-mode 1)
+  (when (featurep 'exwm)
+    ;; Prevent a bad interaction between EXWM and winner-mode, where
+    ;; sometimes closing a window (like closing a terminal after
+    ;; entering a GPG password via pinentry-gnome3's floating window)
+    ;; results in a dead frame somewhere and effectively freezes EXWM.
+    (advice-add
+     'winner-insert-if-new
+     :around
+     (lambda (orig-fun &rest args)
+       ;; only add the frame if it's live
+       (when (frame-live-p (car args))
+         (apply orig-fun args))))))
+
+(use-package windmove
+  :defer 0.5
+  :custom
+  (windmove-wrap-around t)
+  :bind
+  ("M-H" . windmove-left)
+  ("M-L" . windmove-right)
+  ("M-K" . windmove-up)
+  ("M-J" . windmove-down))
+
+(use-package isearch
+  :custom
+  (isearch-allow-scroll t)
+  (isearch-lazy-count t)
+  ;; Match non-ASCII variants during search
+  (search-default-mode #'char-fold-to-regexp))
+
+(use-package vc
+  :bind
+  ("C-x v C-=" . vc-ediff))
+
+(use-package vc-git
+  :custom
+  ;; (vc-git-show-stash 0)
+  (vc-git-print-log-follow t))
+
+(use-package ediff
+  :custom
+  (ediff-window-setup-function #'ediff-setup-windows-plain)
+  (ediff-split-window-function #'split-window-horizontally))
+
+(use-package face-remap
+  :disabled
+  :custom
+  ;; Gentler font scaling.
+  (text-scale-mode-step 1.05))
+
+(use-package mwheel
+  :defer 0.4
+  :custom
+  (mouse-wheel-scroll-amount '(1 ((shift) . 1))) ; one line at a time
+  (mouse-wheel-progressive-speed nil)   ; don't accelerate scrolling
+  (mouse-wheel-follow-mouse t))         ; scroll window under mouse
+
+(use-package pixel-scroll
+  :defer 0.4
+  :config
+  (pixel-scroll-mode 1))
+
+(use-package epg-config
+  :custom
+  (epg-gpg-program (executable-find "gpg"))
+  ;; Ask for GPG passphrase in minibuffer.
+  ;; Will fail if gpg >= 2.1 is not available.
+  (epg-pinentry-mode 'loopback))
+
+(use-package auth-source
+  :disabled
+  :custom
+   (auth-sources '("~/.authinfo.gpg"))
+   (authinfo-hidden
+    (regexp-opt '("password" "client-secret" "token"))))
+
+(use-package info
+  :config
+  (setq
+   Info-directory-list
+   `(,@Info-directory-list
+     ,(expand-file-name
+       (convert-standard-filename "info/") source-directory)
+     "/usr/share/info/")))
+
+(use-package ielm
+  :bind
+  ("C-c i" . ielm))
+
+(use-package recentf
+  ;; recently opened files
+  :defer 0.2
+  :init
+  (defun b/recentf-open ()
+    "Use `completing-read' to \\[find-file] a recent file."
+    (interactive)
+    (find-file
+     (completing-read "Find recent file: " recentf-list)))
+  :bind
+  ("C-c f r" . b/recentf-open)
+  :custom
+  (recentf-max-saved-items 2000)
+  :config
+  (recentf-mode 1))
+
+(use-package help
+  :custom
+  (help-window-select t)
+  :config
+  (temp-buffer-resize-mode 1))
+
+(use-package help-mode
+  :bind
+  (:map
+   help-mode-map
+   ("p" . backward-button)
+   ("n" . forward-button)
+   ("b" . help-go-back)
+   ("f" . help-go-forward)))
+
+(use-package help-fns
+  :bind
+  ("C-c h F" . describe-face))
+
+(use-package doc-view
+  :bind
+  (:map
+   doc-view-mode-map
+   ("M-RET" . image-previous-line)))
+
+(use-package man
+  :custom
+  (Man-width 80))
+
+(use-package shr
+  :custom
+  (shr-max-width 80))
+
+(use-package mule-cmds
+  :preface (provide 'mule-cmds)
+  :custom
+  (default-input-method "farsi-isiri-9147"))
+
+(use-package tramp
+  :config
+  (tramp-set-completion-function
+   "ssh"
+   (append (tramp-get-completion-function "ssh")
+           (mapcar (lambda (file) `(tramp-parse-sconfig ,file))
+                   (directory-files
+                    "~/.ssh/config.d/"
+                    'full directory-files-no-dot-files-regexp)))))
+
+(use-package eldoc
+  ;; Display Lisp objects at point in the echo area.
+  :custom
+  (eldoc-minor-mode-string " eldoc")
+  :config
+  (global-eldoc-mode 1))
+
+(use-package paren
+  ;; Highlight matching parens.
+  :defer 0.2
+  :config
+  (show-paren-mode 1))
+
+(use-package simple
+  :custom
+  ;; Save what I copy into clipboard from other applications into
+  ;; Emacs' kill-ring, which would allow me to still be able to
+  ;; easily access it in case I kill (cut or copy) something else
+  ;; inside Emacs before yanking (pasting) what I'd originally
+  ;; intended to.
+  (save-interprogram-paste-before-kill t)
+  :config
+  (column-number-mode 1)
+  (line-number-mode 1))
+
+(use-package savehist
+  ;; Save minibuffer history.
+  :defer 0.2
+  :config
+  (savehist-mode 1)
+  (add-to-list 'savehist-additional-variables 'kill-ring))
+
+(use-package saveplace
+  ;; Automatically save place in files.
+  :defer 0.2
+  :config
+  (save-place-mode 1))
+
+(use-package flyspell
+  :custom
+  (flyspell-mode-line-string " fly")
+  :hook
+  (text-mode . flyspell-mode))
+
+(use-package abbrev
+  :hook
+  (text-mode . abbrev-mode))
+
+(use-package version
+  :bind
+  ("C-c e v" . emacs-version))
+
+(use-package bandali-utils
+  :bind
+  ("C-c d"   . b/duplicate-line-or-region)
+  ("C-c j"   . b/join-line-top)
+  ("C-S-j"   . b/join-line-top)
+  ("C-c s c" . b/*scratch*)
+  ("C-c v"   . b/invert-default-face)
+  ("C-c q"   . b/unfill-paragraph-or-region)
+  (:map
+   text-mode-map
+   ("M-RET" . b/insert-asterism)))
+
+(provide 'bandali-essentials)
+;;; bandali-essentials.el ends here
diff --git a/.emacs.d/lisp/bandali-utils.el b/.emacs.d/lisp/bandali-utils.el
new file mode 100644
index 0000000..9edc91e
--- /dev/null
+++ b/.emacs.d/lisp/bandali-utils.el
@@ -0,0 +1,94 @@
+;;; bandali-utils.el --- useful utilities -*- lexical-binding: t; -*-
+
+;; Copyright (c) 2018-2025 Amin Bandali <bandali@gnu.org>
+
+;; Author: Amin Bandali <bandali@gnu.org>
+;; Keywords: convenience
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Some useful utilities.
+
+;;; Code:
+
+(defun b/insert-asterism ()
+  "Insert a centred asterism."
+  (interactive)
+  (let ((asterism "* * *"))
+    (insert
+     (concat
+      "\n"
+      (make-string
+       (floor (/ (- fill-column (length asterism)) 2))
+       ?\s)
+      asterism
+      "\n"))))
+
+(defun b/join-line-top ()
+  "Like `join-line', but join next line to the current line."
+  (interactive)
+  (join-line 1))
+
+(defun b/*scratch* ()
+  "Switch to `*scratch*' buffer, creating it if it does not exist."
+  (interactive)
+  (let ((fun (if (functionp #'get-scratch-buffer-create)
+                 #'get-scratch-buffer-create ; (version<= "29" emacs-version)
+               #'startup--get-buffer-create-scratch))) ; (version< emacs-version "29")
+    (switch-to-buffer (funcall fun))))
+
+(defun b/duplicate-line-or-region (&optional n)
+  "Duplicate the current line, or region (if active).
+Make N (default: 1) copies of the current line or region."
+  (interactive "*p")
+  (let ((u-r-p (use-region-p))          ; if region is active
+        (n1 (or n 1)))
+    (save-excursion
+      (let ((text
+             (if u-r-p
+                 (buffer-substring (region-beginning) (region-end))
+               (prog1 (thing-at-point 'line)
+                 (end-of-line)
+                 (if (eobp)
+                     (newline)
+                   (forward-line 1))))))
+        (dotimes (_ (abs n1))
+          (insert text))))))
+
+(defun b/invert-default-face (arg)
+  "Invert the `default' and `mode-line' faces for the current frame.
+Swap the background and foreground for the two `default' and
+`mode-line' faces, effectively acting like a simple light/dark
+theme toggle.  If prefix argument ARG is given, invert the faces
+for all frames."
+  (interactive "P")
+  (let ((frame (unless arg
+                 (selected-frame))))
+    (invert-face 'default frame)
+    (invert-face 'mode-line frame)
+    (when (fboundp #'exwm-systemtray--refresh-background-color)
+      (exwm-systemtray--refresh-background-color 'remap))))
+
+(defun b/unfill-paragraph-or-region (&optional beg end)
+  "Unfill paragraph, or region (if active)."
+  (interactive "r")
+  (let ((fill-column most-positive-fixnum))
+    (if (use-region-p)
+        (fill-region beg end)
+      (fill-paragraph))))
+
+(provide 'bandali-utils)
+;;; bandali-utils.el ends here
-- 
cgit v1.2.3-83-g751a