emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/compat f74464f0e2: Add text-property-search functions f


From: ELPA Syncer
Subject: [elpa] externals/compat f74464f0e2: Add text-property-search functions from Emacs 27
Date: Tue, 30 Aug 2022 07:57:24 -0400 (EDT)

branch: externals/compat
commit f74464f0e2d898d7b008db2815b37a2e93fd534c
Author: Philip Kaludercic <philipk@posteo.net>
Commit: Philip Kaludercic <philipk@posteo.net>

    Add text-property-search functions from Emacs 27
---
 compat-27.el    | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 compat-tests.el |  78 +++++++++++++++++++
 compat.texi     |  99 ++++++++++++++++++++++++
 3 files changed, 411 insertions(+)

diff --git a/compat-27.el b/compat-27.el
index a96b1e9663..b52d7973c3 100644
--- a/compat-27.el
+++ b/compat-27.el
@@ -760,5 +760,239 @@ The return value is a string (or nil in case we can’t 
find it)."
         31
       30)))
 
+;;;; Defined in text-property-search.el
+
+(compat-defun make-prop-match (&rest attr)
+  "Constructor for objects of type ‘prop-match’."
+  :realname compat--make-prop-match-with-vector
+  :max-version "26.1"
+  (vector
+   'prop-match
+   (plist-get attr :beginning)
+   (plist-get attr :end)
+   (plist-get attr :value)))
+
+(compat-defun make-prop-match (&rest attr)
+  "Constructor for objects of type ‘prop-match’."
+  :realname compat--make-prop-match-with-record
+  :min-version "26.1"
+  (record
+   'prop-match
+   (plist-get attr :beginning)
+   (plist-get attr :end)
+   (plist-get attr :value)))
+
+(compat-defun prop-match-p (match)
+  "Return non-nil if MATCH is a `prop-match' object."
+  :realname compat--prop-match-p-with-vector
+  :max-version "26.1"
+  (and (vectorp match) (eq (aref match 0) 'prop-match)))
+
+(compat-defun prop-match-p (match)
+  "Return non-nil if MATCH is a `prop-match' object."
+  :realname compat--prop-match-p-with-record
+  :min-version "26.1"
+  (eq (type-of match) 'prop-match))
+
+(compat-defun prop-match-beginning (match)
+  "Retrieve the position where MATCH begins."
+  (aref match 1))
+
+(compat-defun prop-match-end (match)
+  "Retrieve the position where MATCH ends."
+  (aref match 2))
+
+(compat-defun prop-match-value (match)
+  "Retrieve the value that MATCH holds."
+  (aref match 3))
+
+(compat-defun text-property-search-forward
+    (property &optional value predicate not-current)
+  "Search for the next region of text where PREDICATE is true.
+PREDICATE is used to decide whether a value of PROPERTY should be
+considered as matching VALUE.
+
+If PREDICATE is a function, it will be called with two arguments:
+VALUE and the value of PROPERTY.  The function should return
+non-nil if these two values are to be considered a match.
+
+Two special values of PREDICATE can also be used:
+If PREDICATE is t, that means a value must `equal' VALUE to be
+considered a match.
+If PREDICATE is nil (which is the default value), a value will
+match if is not `equal' to VALUE.  Furthermore, a nil PREDICATE
+means that the match region is ended if the value changes.  For
+instance, this means that if you loop with
+
+  (while (setq prop (text-property-search-forward \\='face))
+    ...)
+
+you will get all distinct regions with non-nil `face' values in
+the buffer, and the `prop' object will have the details about the
+match.  See the manual for more details and examples about how
+VALUE and PREDICATE interact.
+
+If NOT-CURRENT is non-nil, the function will search for the first
+region that doesn't include point and has a value of PROPERTY
+that matches VALUE.
+
+If no matches can be found, return nil and don't move point.
+If found, move point to the end of the region and return a
+`prop-match' object describing the match.  To access the details
+of the match, use `prop-match-beginning' and `prop-match-end' for
+the buffer positions that limit the region, and
+`prop-match-value' for the value of PROPERTY in the region."
+  (let* ((match-p
+          (lambda (prop-value)
+            (funcall
+             (cond
+              ((eq predicate t)
+               #'equal)
+              ((eq predicate nil)
+               (lambda (val p-val)
+                 (not (equal val p-val))))
+              (predicate))
+             value prop-value)))
+         (find-end
+          (lambda (start)
+            (let (end)
+              (if (and value
+                       (null predicate))
+                  ;; This is the normal case: We're looking for areas where the
+                  ;; values aren't, so we aren't interested in sub-areas where 
the
+                  ;; property has different values, all non-matching value.
+                  (let ((ended nil))
+                    (while (not ended)
+                      (setq end (next-single-property-change (point) property))
+                      (if (not end)
+                          (progn
+                            (goto-char (point-max))
+                            (setq end (point)
+                                  ended t))
+                        (goto-char end)
+                        (unless (funcall match-p (get-text-property (point) 
property))
+                          (setq ended t)))))
+                ;; End this at the first place the property changes value.
+                (setq end (next-single-property-change (point) property nil 
(point-max)))
+                (goto-char end))
+              (make-prop-match
+               :beginning start
+               :end end
+               :value (get-text-property start property))))))
+    (cond
+     ;; No matches at the end of the buffer.
+     ((eobp)
+      nil)
+     ;; We're standing in the property we're looking for, so find the
+     ;; end.
+     ((and (funcall match-p (get-text-property (point) property))
+           (not not-current))
+      (funcall find-end (point)))
+     (t
+      (let ((origin (point))
+            (ended nil)
+            pos)
+        ;; Find the next candidate.
+        (while (not ended)
+          (setq pos (next-single-property-change (point) property))
+          (if (not pos)
+              (progn
+                (goto-char origin)
+                (setq ended t))
+            (goto-char pos)
+            (if (funcall match-p (get-text-property (point) property))
+                (setq ended (funcall find-end (point)))
+              ;; Skip past this section of non-matches.
+              (setq pos (next-single-property-change (point) property))
+              (unless pos
+                (goto-char origin)
+                (setq ended t)))))
+        (and (not (eq ended t))
+             ended))))))
+
+(compat-defun text-property-search-backward
+    (property &optional value predicate not-current)
+  "Search for the previous region of text whose PROPERTY matches VALUE.
+
+Like `text-property-search-forward', which see, but searches backward,
+and if a matching region is found, place point at the start of the region."
+  (let* ((match-p
+          (lambda (prop-value)
+            (funcall
+             (cond
+              ((eq predicate t)
+               #'equal)
+              ((eq predicate nil)
+               (lambda (val p-val)
+                 (not (equal val p-val))))
+              (predicate))
+             value prop-value)))
+         (find-end
+          (lambda (start)
+            (let (end)
+              (if (and value
+                       (null predicate))
+                  ;; This is the normal case: We're looking for areas where the
+                  ;; values aren't, so we aren't interested in sub-areas where 
the
+                  ;; property has different values, all non-matching value.
+                  (let ((ended nil))
+                    (while (not ended)
+                      (setq end (previous-single-property-change (point) 
property))
+                      (if (not end)
+                          (progn
+                            (goto-char (point-min))
+                            (setq end (point)
+                                  ended t))
+                        (goto-char (1- end))
+                        (unless (funcall match-p (get-text-property (point) 
property))
+                          (goto-char end)
+                          (setq ended t)))))
+                ;; End this at the first place the property changes value.
+                (setq end (previous-single-property-change
+                           (point) property nil (point-min)))
+                (goto-char end))
+              (make-prop-match
+               :beginning end
+               :end (1+ start)
+               :value (get-text-property end property))))))
+    (cond
+     ;; We're at the start of the buffer; no previous matches.
+     ((bobp)
+      nil)
+     ;; We're standing in the property we're looking for, so find the
+     ;; end.
+     ((funcall match-p (get-text-property (1- (point)) property))
+      (let ((origin (point))
+            (match (funcall find-end (1- (point)) property value predicate)))
+        ;; When we want to ignore the current element, then repeat the
+        ;; search if we haven't moved out of it yet.
+        (if (and not-current
+                 (equal (get-text-property (point) property)
+                        (get-text-property origin property)))
+            (text-property-search-backward property value predicate)
+          match)))
+     (t
+      (let ((origin (point))
+            (ended nil)
+            pos)
+        ;; Find the previous candidate.
+        (while (not ended)
+          (setq pos (previous-single-property-change (point) property))
+          (if (not pos)
+              (progn
+                (goto-char origin)
+                (setq ended t))
+            (goto-char (1- pos))
+            (if (funcall match-p (get-text-property (point) property))
+                (setq ended
+                      (funcall find-end (point)))
+              ;; Skip past this section of non-matches.
+              (setq pos (previous-single-property-change (point) property))
+              (unless pos
+                (goto-char origin)
+                (setq ended t)))))
+        (and (not (eq ended t))
+             ended))))))
+
 (compat--inhibit-prefixed (provide 'compat-27))
 ;;; compat-27.el ends here
diff --git a/compat-tests.el b/compat-tests.el
index a2c964b5da..85ca26d4fa 100644
--- a/compat-tests.el
+++ b/compat-tests.el
@@ -1862,5 +1862,83 @@ being compared against."
     (ought three three one.5 two one three)
     (ought three three one.5 three two one)))
 
+(ert-deftest text-property-search-forward ()
+  (when (fboundp 'text-property-search-forward)
+    (with-temp-buffer
+      (insert "one "
+              (propertize "two " 'prop 'val)
+              "three "
+              (propertize "four " 'prop 'wert)
+              "five ")
+      (goto-char (point-min))
+      (let ((match (text-property-search-forward 'prop)))
+        (should (eq (prop-match-beginning match) 5))
+        (should (eq (prop-match-end match) 9))
+        (should (eq (prop-match-value match) 'val)))
+      (let ((match (text-property-search-forward 'prop)))
+        (should (eq (prop-match-beginning match) 15))
+        (should (eq (prop-match-end match) 20))
+        (should (eq (prop-match-value match) 'wert)))
+      (should (null (text-property-search-forward 'prop)))
+      (goto-char (point-min))
+      (should (null (text-property-search-forward 'non-existant)))))
+  (with-temp-buffer
+    (insert "one "
+            (propertize "two " 'prop 'val)
+            "three "
+            (propertize "four " 'prop 'wert)
+            "five ")
+    (goto-char (point-min))
+    (let ((match (compat--text-property-search-forward 'prop)))
+      (should (eq (compat--prop-match-beginning match) 5))
+      (should (eq (compat--prop-match-end match) 9))
+      (should (eq (compat--prop-match-value match) 'val)))
+    (let ((match (compat--text-property-search-forward 'prop)))
+      (should (eq (compat--prop-match-beginning match) 15))
+      (should (eq (compat--prop-match-end match) 20))
+      (should (eq (compat--prop-match-value match) 'wert)))
+    (should (null (text-property-search-forward 'prop)))
+    (goto-char (point-min))
+    (should (null (text-property-search-forward 'non-existant)))))
+
+(ert-deftest text-property-search-backward ()
+  (when (fboundp 'text-property-search-backward)
+    (with-temp-buffer
+      (insert "one "
+              (propertize "two " 'prop 'val)
+              "three "
+              (propertize "four " 'prop 'wert)
+              "five ")
+      (goto-char (point-max))
+      (let ((match (text-property-search-backward 'prop)))
+        (should (eq (prop-match-beginning match) 15))
+        (should (eq (prop-match-end match) 20))
+        (should (eq (prop-match-value match) 'wert)))
+      (let ((match (text-property-search-backward 'prop)))
+        (should (eq (prop-match-beginning match) 5))
+        (should (eq (prop-match-end match) 9))
+        (should (eq (prop-match-value match) 'val)))
+      (should (null (text-property-search-backward 'prop)))
+      (goto-char (point-max))
+      (should (null (text-property-search-backward 'non-existant)))))
+  (with-temp-buffer
+    (insert "one "
+            (propertize "two " 'prop 'val)
+            "three "
+            (propertize "four " 'prop 'wert)
+            "five ")
+    (goto-char (point-max))
+    (let ((match (compat--text-property-search-backward 'prop)))
+      (should (eq (compat--prop-match-beginning match) 15))
+      (should (eq (compat--prop-match-end match) 20))
+      (should (eq (compat--prop-match-value match) 'wert)))
+    (let ((match (compat--text-property-search-backward 'prop)))
+      (should (eq (compat--prop-match-beginning match) 5))
+      (should (eq (compat--prop-match-end match) 9))
+      (should (eq (compat--prop-match-value match) 'val)))
+    (should (null (text-property-search-backward 'prop)))
+    (goto-char (point-max))
+    (should (null (text-property-search-backward 'non-existant)))))
+
 (provide 'compat-tests)
 ;;; compat-tests.el ends here
diff --git a/compat.texi b/compat.texi
index 59bb344c92..5ada74bc90 100644
--- a/compat.texi
+++ b/compat.texi
@@ -1612,6 +1612,105 @@ argument, which signals an error if the list of found 
files is empty.
 @code{error} can be a string with the error message.
 @end defun
 
+@defun text-property-search-forward prop &optional value predicate not-current
+Search for the next region that has text property @var{prop} set to
+@var{value} according to @var{predicate}.
+
+This function is modeled after @code{search-forward} and friends in
+that it moves point, but it returns a structure that describes the
+match instead of returning it in @code{match-beginning} and friends.
+
+If the text property can't be found, the function returns @code{nil}.
+If it's found, point is placed at the end of the region that has this
+text property match, and a @code{prop-match} structure is returned.
+
+@var{predicate} can either be @code{t} (which is a synonym for
+@code{equal}), @code{nil} (which means ``not equal''), or a predicate
+that will be called with two parameters: The first is @var{value}, and
+the second is the value of the text property we're inspecting.
+
+If @var{not-current}, if point is in a region where we have a match,
+then skip past that and find the next instance instead.
+
+The @code{prop-match} structure has the following accessors:
+@code{prop-match-beginning} (the start of the match),
+@code{prop-match-end} (the end of the match), and
+@code{prop-match-value} (the value of @var{property} at the start of
+the match).
+
+In the examples below, imagine that you're in a buffer that looks like
+this:
+
+@example
+This is a bold and here's bolditalic and this is the end.
+@end example
+
+That is, the ``bold'' words are the @code{bold} face, and the
+``italic'' word is in the @code{italic} face.
+
+With point at the start:
+
+@lisp
+(while (setq match (text-property-search-forward 'face 'bold t))
+  (push (buffer-substring (prop-match-beginning match)
+                          (prop-match-end match))
+        words))
+@end lisp
+
+This will pick out all the words that use the @code{bold} face.
+
+@lisp
+(while (setq match (text-property-search-forward 'face nil t))
+  (push (buffer-substring (prop-match-beginning match)
+                          (prop-match-end match))
+        words))
+@end lisp
+
+This will pick out all the bits that have no face properties, which
+will result in the list @samp{("This is a " "and here's " "and this is
+the end")} (only reversed, since we used @code{push}).
+
+@lisp
+(while (setq match (text-property-search-forward 'face nil nil))
+  (push (buffer-substring (prop-match-beginning match)
+                          (prop-match-end match))
+        words))
+@end lisp
+
+This will pick out all the regions where @code{face} is set to
+something, but this is split up into where the properties change, so
+the result here will be @samp{("bold" "bold" "italic")}.
+
+For a more realistic example where you might use this, consider that
+you have a buffer where certain sections represent URLs, and these are
+tagged with @code{shr-url}.
+
+@lisp
+(while (setq match (text-property-search-forward 'shr-url nil nil))
+  (push (prop-match-value match) urls))
+@end lisp
+
+This will give you a list of all those URLs.
+
+@xref{elisp,,,Property Search}.
+@end defun
+
+@defun text-property-search-backward prop &optional value predicate not-current
+This is just like @code{text-property-search-forward}, but searches
+backward instead.  Point is placed at the beginning of the matched
+region instead of the end, though.
+
+@xref{elisp,,,Property Search}.
+@end defun
+
+@defun text-property-search-backward prop &optional value predicate not-current
+This is just like @code{text-property-search-forward}, but searches
+backward instead.  Point is placed at the beginning of the matched
+region instead of the end, though.
+
+@xref{elisp,,,Property Search}.
+@end defun
+
 @subsection Missing Definitions
 Compat does not provide support for the following Lisp features
 implemented in 27.1:



reply via email to

[Prev in Thread] Current Thread [Next in Thread]