Support mixed face list format like (bold :foreground "green")

Added tp--parse-face-list helper function to parse mixed face lists
that contain both symbols and inline plist keys/values:
- (bold :foreground "green") -> symbols: (bold), plist: (:foreground "green")
- (bold (:foreground "green")) -> symbols: (bold), plist: (:foreground "green")
- (:foreground "green") -> symbols: nil, plist: (:foreground "green")

Updated tp--merge-face-values to use this helper when merging face lists.

Added test case for mixed format to tp-test-tp-add-face-override-subprops.

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-05 18:39:08 +00:00
parent a140fd8b45
commit d3679eccad
2 changed files with 56 additions and 8 deletions

View File

@ -3180,6 +3180,21 @@ the inserted text should be that string, not the source text."
'tp-text (propertize "vim" 'face '(:foreground "red"))))) 'tp-text (propertize "vim" 'face '(:foreground "red")))))
(should (equal result "vim")) (should (equal result "vim"))
;; Face should be (bold (:foreground "green")) - new overrides old ;; Face should be (bold (:foreground "green")) - new overrides old
(let ((face-val (tp-at 0 'face result)))
(should (member 'bold (if (listp face-val) face-val (list face-val))))
;; Should have green, not red
(should (cl-some (lambda (f)
(and (listp f)
(keywordp (car-safe f))
(equal (plist-get f :foreground) "green")))
(if (and (listp face-val) (not (keywordp (car-safe face-val))))
face-val
(list face-val))))))
;; Mixed format case: new (bold :foreground "green") with embedded (:foreground "red")
(let ((result (tp-add "emacs" 'face '(bold :foreground "green")
'tp-text (propertize "vim" 'face '(:foreground "red")))))
(should (equal result "vim"))
;; Face should be (bold (:foreground "green")) - parsed correctly and new overrides old
(let ((face-val (tp-at 0 'face result))) (let ((face-val (tp-at 0 'face result)))
(should (member 'bold (if (listp face-val) face-val (list face-val)))) (should (member 'bold (if (listp face-val) face-val (list face-val))))
;; Should have green, not red ;; Should have green, not red

49
tp.el
View File

@ -258,6 +258,42 @@ Scans the entire string, not just position 0."
(and (stringp str) (and (stringp str)
(not (null (object-intervals str))))) (not (null (object-intervals str)))))
(defun tp--parse-face-list (face-list)
"Parse a mixed face list into symbols and a plist.
FACE-LIST can be a mix of:
- Face symbols (like bold, italic)
- Face plists (like (:foreground \"red\"))
- Inline plist keys and values (like bold :foreground \"green\")
Returns (SYMBOLS . PLIST) where SYMBOLS is a list of face symbols
and PLIST is the merged plist of all face attributes."
(let ((symbols nil)
(plist nil)
(i 0)
(len (length face-list)))
(while (< i len)
(let ((elem (nth i face-list)))
(cond
;; Nested plist like (:foreground "red")
((and (listp elem) (keywordp (car-safe elem)))
(setq plist (if plist (tp--deep-merge-plist plist elem) elem))
(setq i (1+ i)))
;; Inline keyword - consume key and value
((keywordp elem)
(let ((key elem)
(val (nth (1+ i) face-list)))
(setq plist (if plist
(plist-put plist key val)
(list key val)))
(setq i (+ i 2))))
;; Face symbol
((symbolp elem)
(push elem symbols)
(setq i (1+ i)))
;; Something else - skip
(t (setq i (1+ i))))))
(cons (nreverse symbols) plist)))
(defun tp--merge-string-props-into-plist (str props) (defun tp--merge-string-props-into-plist (str props)
"Merge text properties from string STR into PROPS plist. "Merge text properties from string STR into PROPS plist.
Properties from PROPS take precedence over those in STR. Properties from PROPS take precedence over those in STR.
@ -334,17 +370,14 @@ Returns the merged face value."
(append face2 (list face1)))) (append face2 (list face1))))
;; face1 is a plist - need to merge any plist in face2 with face1 ;; face1 is a plist - need to merge any plist in face2 with face1
((and (listp face1) (keywordp (car-safe face1))) ((and (listp face1) (keywordp (car-safe face1)))
;; Extract plist and symbols from face2 ;; Use tp--parse-face-list to handle mixed formats like (bold :foreground "green")
(let ((symbols nil) (let* ((parsed (tp--parse-face-list face2))
(plist nil)) (symbols (car parsed))
(dolist (f face2) (plist (cdr parsed)))
(if (and (listp f) (keywordp (car-safe f)))
(setq plist (if plist (tp--deep-merge-plist plist f) f))
(push f symbols)))
;; Merge face2's plist with face1, then prepend symbols ;; Merge face2's plist with face1, then prepend symbols
(let ((merged-plist (if plist (tp--deep-merge-plist face1 plist) face1))) (let ((merged-plist (if plist (tp--deep-merge-plist face1 plist) face1)))
(if symbols (if symbols
(append (nreverse symbols) (list merged-plist)) (append symbols (list merged-plist))
merged-plist)))) merged-plist))))
((listp face1) ((listp face1)
(append face2 (append face2