diff options
| -rw-r--r-- | init.el | 1 | ||||
| -rw-r--r-- | modules/dirvish-config.el | 20 | ||||
| -rw-r--r-- | modules/font-config.el | 6 | ||||
| -rw-r--r-- | modules/keyboard-compat.el | 45 | ||||
| -rw-r--r-- | modules/nerd-icons-config.el | 94 | ||||
| -rw-r--r-- | modules/selection-framework.el | 7 | ||||
| -rw-r--r-- | modules/system-utils.el | 14 | ||||
| -rw-r--r-- | tests/test-keyboard-compat--icon-blank-in-terminal.el | 54 | ||||
| -rw-r--r-- | tests/test-nerd-icons-config--apply-tint.el | 63 | ||||
| -rw-r--r-- | tests/test-nerd-icons-config--color-dir.el | 57 |
10 files changed, 316 insertions, 45 deletions
@@ -59,6 +59,7 @@ (cj/load-theme-from-file) (require 'ui-navigation) ;; the movement and navigation of windows (require 'font-config) ;; font and emoji configuration +(require 'nerd-icons-config) ;; nerd-icons + completion/ibuffer integration + tint (require 'selection-framework) ;; menu config (require 'modeline-config) ;; modeline (status-bar) config (require 'mousetrap-mode) ;; prevent accidental mouse/trackpad modifications diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el index 0ea056bd..5f30ba10 100644 --- a/modules/dirvish-config.el +++ b/modules/dirvish-config.el @@ -259,7 +259,7 @@ Uses feh on X11, swww on Wayland." ("pdx" "~/projects/documents/" "project documents") ("pdl" "~/projects/danneel/" "project danneel") ("pcl" "~/projects/clipper/" "project clipper") - ("pcr" "~/projects/career/" "project career") + ("pwk" "~/projects/work/" "project work") ("pl" "~/projects/elibrary/" "project elibrary") ("pf" "~/projects/finances/" "project finances") ("pjr" "~/projects/jr-estate/" "project jr-estate") @@ -363,6 +363,24 @@ Uses feh on X11, swww on Wayland." ("v" . dirvish-vc-menu) ("y" . dirvish-yank-menu))) +;;; ----------------------------- Dired Text Greying ---------------------------- + +;; The right-column file-size attribute uses `shadow' (#969385). Match the +;; visible text faces to it so the column reads as one tone, with icon color +;; supplying the only accent. `default' is remapped buffer-locally inside +;; dired/dirvish so plain files match too — no global side effects. + +(with-eval-after-load 'dired + (set-face-attribute 'dired-directory nil :foreground 'unspecified :inherit 'shadow) + (set-face-attribute 'dired-symlink nil :foreground 'unspecified :inherit 'shadow) + (set-face-attribute 'dired-header nil :foreground 'unspecified :inherit 'shadow)) + +(defun cj/--dired-text-greyout () + "Buffer-local: render `default' in `shadow' so plain files read grey." + (face-remap-add-relative 'default 'shadow)) + +(add-hook 'dired-mode-hook #'cj/--dired-text-greyout) + ;;; ---------------------------- Dired Hide Dotfiles ---------------------------- (use-package dired-hide-dotfiles diff --git a/modules/font-config.el b/modules/font-config.el index 3310580c..f7c3af5f 100644 --- a/modules/font-config.el +++ b/modules/font-config.el @@ -209,12 +209,6 @@ If FRAME is nil, uses the selected frame." :config (all-the-icons-nerd-fonts-prefer)) -;; -------------------------------- Nerd Icons --------------------------------- -;; Modern icon fonts for UI elements - -(use-package nerd-icons - :defer .5) - ;; ----------------------------- Emoji Fonts Per OS ---------------------------- ;; Set emoji fonts in priority order (first found wins) diff --git a/modules/keyboard-compat.el b/modules/keyboard-compat.el index 735839eb..b914978f 100644 --- a/modules/keyboard-compat.el +++ b/modules/keyboard-compat.el @@ -111,26 +111,31 @@ This runs after init to override any package settings." ;; Run after init completes to override any package settings (add-hook 'emacs-startup-hook #'cj/keyboard-compat-terminal-setup) -;; Icon disabling only in terminal mode (prevents unicode artifacts) -(when (env-terminal-p) - ;; Disable nerd-icons display (shows as \uXXXX artifacts) - (with-eval-after-load 'nerd-icons - (defun nerd-icons-icon-for-file (&rest _) "") - (defun nerd-icons-icon-for-dir (&rest _) "") - (defun nerd-icons-icon-for-mode (&rest _) "") - (defun nerd-icons-icon-for-buffer (&rest _) "")) - - ;; Disable dashboard icons - (with-eval-after-load 'dashboard - (setq dashboard-display-icons-p nil) - (setq dashboard-set-file-icons nil) - (setq dashboard-set-heading-icons nil)) - - ;; Disable all-the-icons - (with-eval-after-load 'all-the-icons - (defun all-the-icons-icon-for-file (&rest _) "") - (defun all-the-icons-icon-for-dir (&rest _) "") - (defun all-the-icons-icon-for-mode (&rest _) ""))) +;; Icon-rendering functions return blank on terminal frames so unicode +;; artifacts don't show up. The check runs per call against the selected +;; frame, so the same daemon serves real icons to GUI clients and blanks to +;; terminal clients. Earlier this lived in a top-level (when (env-terminal-p) +;; ...) block that redefined the icon functions at module-load time, which +;; broke under daemon startup: no frame exists yet, display-graphic-p returns +;; nil, env-terminal-p returns t, and the stubs install permanently. GUI +;; clients connecting later saw empty icons everywhere. + +(defun cj/--icon-blank-in-terminal (orig &rest args) + "Return empty string on a terminal frame, otherwise call ORIG with ARGS." + (if (display-graphic-p) (apply orig args) "")) + +(with-eval-after-load 'nerd-icons + (dolist (fn '(nerd-icons-icon-for-file + nerd-icons-icon-for-dir + nerd-icons-icon-for-mode + nerd-icons-icon-for-buffer)) + (advice-add fn :around #'cj/--icon-blank-in-terminal))) + +(with-eval-after-load 'all-the-icons + (dolist (fn '(all-the-icons-icon-for-file + all-the-icons-icon-for-dir + all-the-icons-icon-for-mode)) + (advice-add fn :around #'cj/--icon-blank-in-terminal))) ;; ============================================================================= ;; GUI-specific fixes diff --git a/modules/nerd-icons-config.el b/modules/nerd-icons-config.el new file mode 100644 index 00000000..4a8ce194 --- /dev/null +++ b/modules/nerd-icons-config.el @@ -0,0 +1,94 @@ +;;; nerd-icons-config.el --- Nerd-icons setup, integrations, and tinting -*- lexical-binding: t; -*- +;; author: Craig Jennings <c@cjennings.net> + +;;; Commentary: +;; +;; Single home for nerd-icons: +;; - the package itself +;; - completion integration (`nerd-icons-completion') +;; - ibuffer integration (`nerd-icons-ibuffer') +;; - bulk color tinting of every `nerd-icons-*' color face +;; - dir-icon color advice (so directory glyphs carry a color face like +;; file glyphs do, instead of falling through to the buffer default +;; face) +;; +;; Per-feature USE of nerd-icons stays in the feature module that consumes +;; it: `dashboard-icon-type', `dirvish-attributes', and the keyboard-compat +;; terminal-frame icon-blanking advice are not centralized here. + +;;; Code: + +;; ----------------------------- Customization --------------------------------- + +(defcustom cj/nerd-icons-tint-color "darkgoldenrod" + "Single foreground color applied to every `nerd-icons-*' color face. +Set via Customize or by `setq' before this module loads, then call +`cj/nerd-icons-apply-tint' to re-apply on demand." + :type 'string + :group 'cj) + +(defconst cj/--nerd-icons-color-faces + '(nerd-icons-red nerd-icons-lred nerd-icons-dred nerd-icons-red-alt + nerd-icons-green nerd-icons-lgreen nerd-icons-dgreen + nerd-icons-yellow nerd-icons-lyellow nerd-icons-dyellow + nerd-icons-orange nerd-icons-lorange nerd-icons-dorange + nerd-icons-blue nerd-icons-blue-alt nerd-icons-lblue nerd-icons-dblue + nerd-icons-cyan nerd-icons-cyan-alt nerd-icons-lcyan nerd-icons-dcyan + nerd-icons-purple nerd-icons-purple-alt nerd-icons-lpurple nerd-icons-dpurple + nerd-icons-pink nerd-icons-lpink nerd-icons-dpink + nerd-icons-maroon nerd-icons-lmaroon nerd-icons-dmaroon + nerd-icons-silver nerd-icons-lsilver nerd-icons-dsilver) + "Every color face nerd-icons attaches to glyphs via `:inherit'.") + +;; ------------------------------- Helpers ------------------------------------- + +(defun cj/nerd-icons-apply-tint (&optional color) + "Set every face in `cj/--nerd-icons-color-faces' to foreground COLOR. +COLOR defaults to `cj/nerd-icons-tint-color'. Faces that are not yet +defined (nerd-icons not loaded) are silently skipped." + (interactive) + (let ((c (or color cj/nerd-icons-tint-color))) + (dolist (f cj/--nerd-icons-color-faces) + (when (facep f) + (set-face-foreground f c))))) + +(defun cj/--nerd-icons-color-dir (icon) + "Layer `nerd-icons-yellow' onto ICON's face stack and return ICON. +ICON is the propertized string returned by `nerd-icons-icon-for-dir'. +Without this, directory icons render in the buffer default face — so a +buffer-local face-remap of `default' catches them unintentionally. + +Idempotent: nerd-icons memoizes its return strings, and this advice modifies +in place, so a naive `add-face-text-property' would stack the face symbol on +every call. The `memq' check skips when the face is already present." + (when (and (stringp icon) (> (length icon) 0)) + (let ((faces (ensure-list (get-text-property 0 'face icon)))) + (unless (memq 'nerd-icons-yellow faces) + (add-face-text-property 0 (length icon) 'nerd-icons-yellow nil icon)))) + icon) + +;; ------------------------------- Packages ------------------------------------ + +(use-package nerd-icons + :demand t + :config + (advice-add 'nerd-icons-icon-for-dir :filter-return #'cj/--nerd-icons-color-dir) + (cj/nerd-icons-apply-tint)) + +(use-package nerd-icons-completion + :demand t + :after (nerd-icons marginalia) + :hook (marginalia-mode . nerd-icons-completion-marginalia-setup) + :config + (nerd-icons-completion-mode)) + +(use-package nerd-icons-ibuffer + :after nerd-icons + :hook (ibuffer-mode . nerd-icons-ibuffer-mode) + :custom + (nerd-icons-ibuffer-icon t) + (nerd-icons-ibuffer-color-icon t) + (nerd-icons-ibuffer-human-readable-size t)) + +(provide 'nerd-icons-config) +;;; nerd-icons-config.el ends here diff --git a/modules/selection-framework.el b/modules/selection-framework.el index 1129ac05..2a714866 100644 --- a/modules/selection-framework.el +++ b/modules/selection-framework.el @@ -45,13 +45,6 @@ :init (marginalia-mode)) -(use-package nerd-icons-completion - :demand t - :hook (marginalia-mode nerd-icons-completion-marginalia-setup) - :after marginalia - :init - (nerd-icons-completion-mode)) - ;; ---------------------------------- Consult ---------------------------------- ;; Practical commands based on completing-read diff --git a/modules/system-utils.el b/modules/system-utils.el index 86c2ae16..9a81c402 100644 --- a/modules/system-utils.el +++ b/modules/system-utils.el @@ -176,20 +176,12 @@ Logs output and exit code to buffer *external-open.log*." (savehist-mode)) ) -;;; ------------------------ List Buffers With Nerd Icons ----------------------- +;;; ----------------------------- List Buffers Remap ---------------------------- -;; Remap list-buffers to ibuffer (built-in). Keybinding is separate from -;; nerd-icons-ibuffer package, which only adds icons to ibuffer. +;; Remap list-buffers to ibuffer (built-in). Icons come from nerd-icons-ibuffer +;; in `nerd-icons-config'. (keymap-global-set "<remap> <list-buffers>" #'ibuffer) -(use-package nerd-icons-ibuffer - :after nerd-icons - :hook (ibuffer-mode . nerd-icons-ibuffer-mode) - :config - (setq nerd-icons-ibuffer-icon t - nerd-icons-ibuffer-color-icon t - nerd-icons-ibuffer-human-readable-size t)) - ;;; -------------------------- Scratch Buffer Happiness ------------------------- (defvar scratch-emacs-version-and-system diff --git a/tests/test-keyboard-compat--icon-blank-in-terminal.el b/tests/test-keyboard-compat--icon-blank-in-terminal.el new file mode 100644 index 00000000..db2bb6b7 --- /dev/null +++ b/tests/test-keyboard-compat--icon-blank-in-terminal.el @@ -0,0 +1,54 @@ +;;; test-keyboard-compat--icon-blank-in-terminal.el --- Tests for cj/--icon-blank-in-terminal -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the per-call icon-rendering gate that replaced the load-time +;; defun-redefinition. The helper must dispatch on the current frame's +;; `display-graphic-p' so the same daemon can serve real icons to GUI frames +;; and blanks to terminal frames. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'keyboard-compat) + +(defmacro test-keyboard-compat--with-graphic-p (graphic-p &rest body) + "Run BODY with `display-graphic-p' stubbed to GRAPHIC-P." + (declare (indent 1) (debug t)) + `(cl-letf (((symbol-function 'display-graphic-p) + (lambda (&optional _) ,graphic-p))) + ,@body)) + +(ert-deftest test-keyboard-compat--icon-blank-in-terminal-passes-through-on-gui () + "Normal: returns ORIG's value when display-graphic-p is non-nil." + (test-keyboard-compat--with-graphic-p t + (let ((orig (lambda (&rest _) "ICON"))) + (should (equal (cj/--icon-blank-in-terminal orig "foo.txt") "ICON"))))) + +(ert-deftest test-keyboard-compat--icon-blank-in-terminal-blank-on-terminal () + "Boundary: returns empty string when display-graphic-p is nil." + (test-keyboard-compat--with-graphic-p nil + (let ((orig (lambda (&rest _) "ICON"))) + (should (equal (cj/--icon-blank-in-terminal orig "foo.txt") ""))))) + +(ert-deftest test-keyboard-compat--icon-blank-in-terminal-forwards-args () + "Normal: forwards all ARGS to ORIG when graphical." + (test-keyboard-compat--with-graphic-p t + (let ((received nil)) + (cj/--icon-blank-in-terminal + (lambda (&rest args) (setq received args) "ok") + 'a 'b 'c) + (should (equal received '(a b c)))))) + +(ert-deftest test-keyboard-compat--icon-blank-in-terminal-does-not-call-orig-on-terminal () + "Error: ORIG must not be invoked when display-graphic-p is nil." + (test-keyboard-compat--with-graphic-p nil + (let ((called nil)) + (cj/--icon-blank-in-terminal + (lambda (&rest _) (setq called t) "ICON")) + (should-not called)))) + +(provide 'test-keyboard-compat--icon-blank-in-terminal) +;;; test-keyboard-compat--icon-blank-in-terminal.el ends here diff --git a/tests/test-nerd-icons-config--apply-tint.el b/tests/test-nerd-icons-config--apply-tint.el new file mode 100644 index 00000000..ef723352 --- /dev/null +++ b/tests/test-nerd-icons-config--apply-tint.el @@ -0,0 +1,63 @@ +;;; test-nerd-icons-config--apply-tint.el --- Tests for cj/nerd-icons-apply-tint -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the bulk-tint helper. Mocks `set-face-foreground' and `facep' +;; at the framework boundary so the tests don't depend on nerd-icons being +;; loaded — only on the symbol list and the dispatch logic. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'nerd-icons-config) + +(defmacro test-nerd-icons-config--capture-set-face-foreground (calls-var &rest body) + "Run BODY with `set-face-foreground' and `facep' stubbed. +Each (face color) pair gets pushed onto CALLS-VAR. `facep' returns t +for every symbol so all faces in the list count as defined." + (declare (indent 1) (debug t)) + `(cl-letf (((symbol-function 'set-face-foreground) + (lambda (face color &rest _) (push (cons face color) ,calls-var))) + ((symbol-function 'facep) + (lambda (_) t))) + ,@body)) + +(ert-deftest test-nerd-icons-config--apply-tint-covers-every-face () + "Normal: apply-tint calls set-face-foreground once per face in the list." + (let ((calls nil)) + (test-nerd-icons-config--capture-set-face-foreground calls + (cj/nerd-icons-apply-tint "test-color")) + (should (= (length calls) (length cj/--nerd-icons-color-faces))) + (dolist (face cj/--nerd-icons-color-faces) + (should (assq face calls))))) + +(ert-deftest test-nerd-icons-config--apply-tint-passes-color-arg () + "Normal: apply-tint forwards COLOR to every set-face-foreground call." + (let ((calls nil)) + (test-nerd-icons-config--capture-set-face-foreground calls + (cj/nerd-icons-apply-tint "rebeccapurple")) + (dolist (call calls) + (should (equal (cdr call) "rebeccapurple"))))) + +(ert-deftest test-nerd-icons-config--apply-tint-defaults-to-customvar () + "Normal: with no COLOR arg, uses `cj/nerd-icons-tint-color'." + (let ((calls nil)) + (test-nerd-icons-config--capture-set-face-foreground calls + (let ((cj/nerd-icons-tint-color "default-test-color")) + (cj/nerd-icons-apply-tint))) + (should (cl-every (lambda (call) (equal (cdr call) "default-test-color")) calls)))) + +(ert-deftest test-nerd-icons-config--apply-tint-skips-undefined-faces () + "Boundary: faces that fail `facep' are silently skipped, not errored." + (let ((calls nil)) + (cl-letf (((symbol-function 'set-face-foreground) + (lambda (face color &rest _) (push (cons face color) calls))) + ((symbol-function 'facep) + (lambda (_) nil))) + (cj/nerd-icons-apply-tint "any")) + (should (null calls)))) + +(provide 'test-nerd-icons-config--apply-tint) +;;; test-nerd-icons-config--apply-tint.el ends here diff --git a/tests/test-nerd-icons-config--color-dir.el b/tests/test-nerd-icons-config--color-dir.el new file mode 100644 index 00000000..808c0dc3 --- /dev/null +++ b/tests/test-nerd-icons-config--color-dir.el @@ -0,0 +1,57 @@ +;;; test-nerd-icons-config--color-dir.el --- Tests for cj/--nerd-icons-color-dir -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the :filter-return advice that attaches a color face to the +;; output of `nerd-icons-icon-for-dir'. Without this, directory icons render +;; in the buffer default face — so a face-remap of `default' (buffer-local +;; or global) catches the icons unintentionally. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'nerd-icons-config) + +(ert-deftest test-nerd-icons-config--color-dir-attaches-face () + "Normal: adds nerd-icons-yellow to the face stack of a propertized icon." + (let ((icon (propertize "X" 'face '(:family "Symbols Nerd Font Mono" :height 1.0)))) + (let ((result (cj/--nerd-icons-color-dir icon))) + (should (memq 'nerd-icons-yellow + (ensure-list (get-text-property 0 'face result))))))) + +(ert-deftest test-nerd-icons-config--color-dir-preserves-family () + "Normal: retains the original :family / :height attributes." + (let ((icon (propertize "X" 'face '(:family "Symbols Nerd Font Mono" :height 1.0)))) + (let* ((result (cj/--nerd-icons-color-dir icon)) + (face (get-text-property 0 'face result)) + (specs (ensure-list face)) + (plist (seq-find (lambda (x) (and (listp x) (plist-member x :family))) specs))) + (should (equal (plist-get plist :family) "Symbols Nerd Font Mono")) + (should (equal (plist-get plist :height) 1.0))))) + +(ert-deftest test-nerd-icons-config--color-dir-empty-string () + "Boundary: an empty icon string returns unchanged (no range to propertize)." + (let ((icon "")) + (should (equal (cj/--nerd-icons-color-dir icon) "")))) + +(ert-deftest test-nerd-icons-config--color-dir-non-string-passthrough () + "Error: a nil input returns nil rather than erroring." + (should-not (cj/--nerd-icons-color-dir nil))) + +(ert-deftest test-nerd-icons-config--color-dir-idempotent () + "Boundary: calling twice on the same icon adds the face only once. +nerd-icons memoizes its return strings — without this guard, repeated +renders would stack `nerd-icons-yellow' over and over on the cached string." + (let ((icon (propertize "X" 'face '(:family "Symbols Nerd Font Mono" :height 1.0)))) + (cj/--nerd-icons-color-dir icon) + (cj/--nerd-icons-color-dir icon) + (cj/--nerd-icons-color-dir icon) + (let* ((face (get-text-property 0 'face icon)) + (specs (ensure-list face)) + (yellows (cl-count 'nerd-icons-yellow specs))) + (should (= yellows 1))))) + +(provide 'test-nerd-icons-config--color-dir) +;;; test-nerd-icons-config--color-dir.el ends here |
