;;;; subt.el --- Subt -*- lexical-binding: t -*- ;; Copyright (C) 2026 Tyler Triplett ;; License: GNU GPL 3.0 or later <https://www.gnu.org/licenses/gpl-3.0.html> ;; This 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. ;;; Commentary: ;; Make Lisp parentheses subtle. ;; Adds a font lock for '(' and ')' for Lisp modes. ;;; Usage: ;; (subt-set-color-percent! '(+/-)50') to make parentheses (+/-)50% / 100% of ;; the background color. Pass 'relative to try to preserve color ratios better. ;; You could of course set the face 'subt-paren-face' directly. ;;; Code: (defface subt-paren-face '((t (:foreground "orange"))) "Subt face." :group 'subt) (defvar subt-modes '(scheme-mode clojure-mode emacs-lisp-mode lisp-interaction-mode) "Modes to apply subtlety to.") (defvar subt--key-word '(("(\\|)" . 'subt-paren-face)) "\=( and \=) font lock.") (defun subt--font-lock-add () "Font lock wrapper." (font-lock-add-keywords nil subt--key-word)) (defun subt--font-lock-remove () "Font lock wrapper." (font-lock-remove-keywords nil subt--key-word)) (defun subt--mode->hook (mode) "MODE to MODE-hook." (intern (concat (symbol-name mode) "-hook"))) (defun subt--hex-to-dec (h) "Convert hex string H to decimal." (string-to-number h 16)) (defun subt--dec-to-hex (d) "Convert decimal D to hex string." (format "%02X" d)) (defun subt--hex-to-rgb (s) "Convert hex color S to rgb list." (if (equal s "") '() (let ((f (substring s 0 2)) (r (substring s 2))) (cons (subt--hex-to-dec f) (subt--hex-to-rgb r))))) (defun subt--rgb-to-hex (r) "Convert rgb list R to hex string." (if (null r) "" (let ((f (car r))) (concat (subt--dec-to-hex f) (subt--rgb-to-hex (cdr r)))))) (defun subt--clamp (l h v) "Clamp V to L <= V <= H." (min h (max l v))) (defun subt--shift (c p) "Shift color C by amount P." (let ((amount (floor (* 255 (/ p 100.0))))) (subt--clamp 0 255 (+ c amount)))) (defun sub--shift-relative (c p) "Shift color C by amount P relative." (let ((amount (floor (* (if (>= p 0) (- 255 c) c) (/ p 100.0))))) (subt--clamp 0 255 (+ c amount)))) (defun sub--ensure-hex (c) "Convert C name (e.g. \"white\") to #RRGGBB. Also drop '#'." (thread-last (color-values c) (mapcar (lambda (c) (/ c 256))) (mapcar #'subt--dec-to-hex) (apply #'concat))) (defun subt-set-color-percent! (p &optional mode) "Set the paren color scheme by amount P and MODE." (let* ((shift-fn (cond ((eq mode 'relative) #'sub--shift-relative) (t #'subt--shift))) (paren-color (thread-last (face-attribute 'default :background nil 'default) (sub--ensure-hex) ; ensure a valid hex is passed (subt--hex-to-rgb) (mapcar (lambda (c) (funcall shift-fn c p))) (subt--rgb-to-hex) (concat "#")))) (set-face-attribute 'subt-paren-face nil :foreground paren-color))) (defvar subt--state nil "Track state to avoid redundant (subt--toggle!)'s.") (defgroup subt nil "Subt." :group 'convenience :prefix "subt-") ;;;###autoload (define-minor-mode subt-mode "Subt mode." :global t :group 'subt (unless (equal subt--state subt-mode) (subt--toggle!) (setq subt--state subt-mode))) (defun subt--toggle! () "Toggle subt-mode." (pcase-let ((`(,hf ,ff) (if subt-mode (list #'add-hook #'subt--font-lock-add) (list #'remove-hook #'subt--font-lock-remove)))) (mapc (lambda (hook) (funcall hf hook #'subt--font-lock-add)) (mapcar #'subt--mode->hook subt-modes)) (mapc (lambda (buf) (with-current-buffer buf (when (member major-mode subt-modes) (funcall ff) (font-lock-flush) (font-lock-ensure)))) (buffer-list)))) (provide 'subt) ;;; subt.el ends here