;;; 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:

(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 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 (display-graphic-p)
  ;; Too easy to accidentally suspend (freeze) Emacs GUI.
  (b/keymap-global-unset "C-z"))
(b/keymap-global-set "C-c e b" #'eval-buffer)
(b/keymap-global-set "C-c e r" #'eval-region)

(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.*\\|COMMIT_EDITMSG$\\)" . text-mode))
  (add-to-list 'auto-mode-alist '("\\.*rc$" . conf-mode))
  (add-to-list 'auto-mode-alist '("\\.bashrc$" . sh-mode)))
(b/keymap-global-set "C-c f ." #'find-file)

;; `ffap'
(b/keymap-global-set "C-c f p" #'find-file-at-point)

;; `find-func'
(b/keymap-global-set "C-c f l" #'find-library)

;; `frame'
(b/keymap-global-set "C-c F m" #'make-frame-command)
(b/keymap-global-set "C-c F d" #'delete-frame)

(with-eval-after-load 'window
  (setopt split-width-threshold 140))

;; `novice'
(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 1)))
(b/keymap-global-set "C-c e i" #'emacs-init-time)
(b/keymap-global-set "C-c e u" #'emacs-uptime)

(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))

;; `vc'
(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/")))

;; `ielm'
(b/keymap-global-set "C-c i" #'ielm)

(run-with-idle-timer 0.2 nil #'require 'recentf)
(with-eval-after-load 'recentf
  (setopt recentf-max-saved-items 2000)
  (recentf-mode 1)

  (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 1)
  (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)))

;; `help-fns'
(b/keymap-global-set "C-c h F" #'describe-face)

(with-eval-after-load 'doc-view
  (b/keymap-set doc-view-mode-map "M-RET" #'image-previous-line))

(with-eval-after-load 'man
  (setopt Man-width 80))

(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)))))

;; 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))

(with-eval-after-load 'simple
  (setopt
   ;; See `bandali-gnus' for my Gnus configuration.
   mail-user-agent 'gnus-user-agent
   read-mail-command #'gnus
   ;; 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))
(add-hook 'text-mode-hook #'auto-fill-mode)
(add-hook 'tex-mode-hook #'auto-fill-mode)

;; Save minibuffer history.
(run-with-idle-timer 0.2 nil #'require 'savehist)
(with-eval-after-load 'savehist
  (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"))
(add-hook 'text-mode-hook #'flyspell-mode)
(add-hook 'tex-mode-hook #'flyspell-mode)

;; `abbrev'
(add-hook 'text-mode-hook #'abbrev-mode)

(with-eval-after-load 'eww
  (setopt
   eww-download-directory
   (file-name-as-directory (getenv "XDG_DOWNLOAD_DIR"))))
(b/keymap-global-set "C-c e w" #'eww)

(run-with-idle-timer
 0.2 nil #'require 'display-fill-column-indicator nil 'noerror)
(with-eval-after-load 'display-fill-column-indicator
  (global-display-fill-column-indicator-mode 1))

(when (version<= "30" emacs-version)
  ;; (package-installed-p 'completion-preview)
  (with-eval-after-load 'completion-preview
    (let ((m completion-preview-active-mode-map))
      (b/keymap-set m "M-p" #'completion-preview-prev-candidate)
      (b/keymap-set m "M-n" #'completion-preview-next-candidate)
      (b/keymap-set m "M-i" #'completion-preview-insert)))
  (add-hook 'prog-mode-hook #'completion-preview-mode)
  (add-hook 'text-mode-hook #'completion-preview-mode)
  (add-hook 'comint-mode-hook #'completion-preview-mode))

;; `version'
(b/keymap-global-set "C-c e v" #'emacs-version)

(provide 'bandali-essentials)
;;; bandali-essentials.el ends here