From 08b5d9aa1c16800de86858e95767e7b1597e49b7 Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Sun, 26 Jul 2026 19:33:34 +0800 Subject: [PATCH] Add tp-member: plist-member-style property lookup Like tp-at but distinguishes a property present with value nil from an absent property, returning (PROPERTY VALUE) or nil. Covered by four new tests in tp-ops-tests.el; full suite 338/338 green. Co-Authored-By: Claude Fable 5 --- tp-ops-tests.el | 28 ++++++++++++++++++++++++++++ tp-ops.el | 17 +++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/tp-ops-tests.el b/tp-ops-tests.el index 5421350..7c10db5 100644 --- a/tp-ops-tests.el +++ b/tp-ops-tests.el @@ -250,5 +250,33 @@ (should (<= (nth 1 iv) 4))) (should (equal intervals '((2 4 (face bold)))))))) +;;; tp-member + +(ert-deftest tp-ops-test-member-present () + "tp-member returns (PROPERTY VALUE) when the property is present." + (let ((str (copy-sequence "hello"))) + (tp-set 0 5 '(face bold) str) + (should (equal (tp-member 0 'face str) '(face bold))))) + +(ert-deftest tp-ops-test-member-present-nil-value () + "tp-member distinguishes a nil value from an absent property." + (let ((str (copy-sequence "hello"))) + (tp-set 0 5 '(face nil) str) + (should (equal (tp-member 0 'face str) '(face nil))))) + +(ert-deftest tp-ops-test-member-absent () + "tp-member returns nil for an absent property." + (let ((str (copy-sequence "hello"))) + (tp-set 0 5 '(help-echo "tip") str) + (should (equal (tp-member 0 'face str) nil)))) + +(ert-deftest tp-ops-test-member-buffer () + "tp-member works on buffer positions." + (with-temp-buffer + (insert "hello") + (put-text-property 1 6 'my-prop nil) + (should (equal (tp-member 1 'my-prop) '(my-prop nil))) + (should (equal (tp-member 1 'other) nil)))) + (provide 'tp-ops-tests) ;;; tp-ops-tests.el ends here diff --git a/tp-ops.el b/tp-ops.el index d283e9b..bd0513c 100644 --- a/tp-ops.el +++ b/tp-ops.el @@ -538,6 +538,23 @@ Examples: prop-value)) (text-properties-at pos obj)))) +(defun tp-member (pos property &optional object) + "Return (PROPERTY VALUE) if PROPERTY is present at POS in OBJECT. +Return nil when the property is absent. Unlike `tp-at' (which +returns nil both for an absent property and for a property whose +value is nil), this distinguishes \"present with value nil\" from +\"not present\", like `plist-member' does for plists. + +OBJECT is a string or buffer; nil means the current buffer. + +Examples: + (tp-member 1 \\='face) ;; => (face bold) when face is bold + (tp-member 1 \\='face) ;; => (face nil) when face is present but nil + (tp-member 1 \\='face) ;; => nil when face is absent" + (let ((plist (text-properties-at pos object))) + (when-let ((cell (plist-member plist property))) + (list (car cell) (cadr cell))))) + (defun tp--remove-sub (start end property sub-property &optional object) "Remove SUB-PROPERTY from PROPERTY between START and END in OBJECT." (let* ((pos start))