Reputation: 2318
I know there are a few different packages for selecting multiple regions in gnu emacs. What I'm looking for is a way to select multiple regions, operate on them, and then select the inverse of the previously marked regions in order to then operate on them.
As an example, suppose that I have the following data in a buffer:
Line A
Line B
Line C
Line D
Line E
Line F
What I want to do is the following:
Steps 1, 2, and 4 are trivial, and they simply depend on the choice of multiple-region-marking code I decide to use.
But what about step 3? Is there any multiple-region-marking package which allows me to switch to the inverse of what was previously marked?
Upvotes: 2
Views: 818
Reputation: 30699
Your question is general. My answer will likely fit some of it, but maybe not all.
Library zones.el
(see Zones) lets you define and manipulate multiple zones (in effect, multiple regions) of text, across multiple buffers. You can do these kinds of things with them:
isearch-prop.el
(see Isearch+).highlight.el
) or library facemenu+.el
(see Facemenu+).Library Isearch+ (including isearch-prop.el
) lets you search both zones as defined by zones.el
and zones defined by text or overlay properties (any properties), and it facilitates applying properties to zones of text.
thingatpt+.el
then you can easily define additional things.If you use Icicles then you can use Icicles search on a set of search contexts in buffers or files. You can define search contexts in various ways, including using a regexp. There too you can search the complement of a set of search contexts. And you can use incremental narrowing of matching search contexts, and when doing that you can also subtract sets of matches using complementing.
UPDATED after your remarks -
You apparently want to complement a list of non-basic zones (what I call "izones"), which are each a number as identifier followed by the two zone limits. (Basic zones have only the limits and not the identifier, and function zz-zones-complement
returns a list of basic zones.)
This is how I would define that complement function:
(defun zz-complement-izones (izones &optional beg end)
"Complement IZONES, which is a list like `zz-izones'.
Such a list is also returned, that is, zones that have identifiers."
(zz-izones-from-zones
(zz-zones-complement (zz-zone-union (zz-izone-limits izones nil t)))))
That just uses predefined function zz-zones-complement
after stripping the zone identifiers and coalescing the zones. Predefined function zz-izones-from-zones
gives the zones identifiers, making the result a list of izones.
And you want a function that maps a function over a list of zones, applying it to each zone in the list - what your function traverse-zones
does.
Here is a version (untested) of such a map function that expects zones of the form that you are using, that is, izones (zones that have a number identifier) and maps a 3-ary function over them. It is similar to your traverse-zones
(but see the comment):
(defun map-izones (function &optional izones)
"Map 3-ary FUNCTION over IZONES.
FUNCTION is applied to the first three elements of each zone.
IZONES is a list like `zz-izones', that is, zones with identifiers."
;; Do you really want this? It prohibits mapping over an empty list of zones.
;; (unless izones) (setq izones zz-izones)
(when (and (functionp function) (zz-izones-p izones))
(setq izones (zz-unite-zones izones))
(dolist (izone izones) (funcall function (car izone) (cadr izone) (caddr izone)))))
And here is an (untested) version that maps a binary function over a list of zones. The zones in the list can be either all basic zones or all izones. This seems more useful to me, as the identifiers are generally not very useful in such a context.
(defun map-zones (function &optional zones)
"Map binary FUNCTION over ZONES, applying it to the limits of each zone.
ZONES can be a list of basic zones or a list like `zz-izones', that
is, zones that have identifiers."
(when (functionp function)
(when (zz-izones-p zones)
(setq zones (zz-izone-limits zones nil 'ONLY-THIS-BUFFER)))
(setq zones (zz-zone-union zones))
(dolist (zone zones) (funcall function (car zone) (cadr zone)))))
Hope this helps.
Upvotes: 4
Reputation: 2318
Thank you, Drew.
It turns out that zones is closest to what I need, although icicles and isearch+ are also both useful.
As for zones, it turns out that its zz-zones-complement function doesn't seem to return correct information.
However, I wrote my own function which I can use in its place.
I now do this to run arbitrary lisp code on the complement of all the zones that were previously added via zones.el ...
(defun my-complement-zones (&optional zones)
(unless zones
(setq zones zz-izones))
(zz-unite-zones 'zones)
(let ((result ())
(end (copy-marker (point-min)))
(n 0)
(a nil)
(b nil))
(dolist (item (reverse zones))
(setq n (1+ n))
(setq a (cadr item))
(setq b (caddr item))
(setq result (append (list (list n end a)) result))
(setq end b))
(when (< (marker-position end) (point-max))
(setq result (append (list (list (1+ n) end (copy-marker (point-max)))) result)))
result))
;; Each element has three values: an index followed by the start
;; and end markers for each region. To traverse this structure,
;; do the following ...
(dolist (region (my-complement-zones))
(let ((idx (car region))
(start (cadr region))
(end (caddr region)))
;; At this point, "start" is a marker pointing to the
;; beginning of the given zone, and "end" is a marker pointing
;; its endpoint. I can use these for inputs to any region-aware
;; elisp commands, or for any functions that I might want to
;; write which operate on each given region.
;;
;; ... etc. ...
))
... and here's a function I wrote which will traverse a zones list and apply a lambda to each zone. It works equivalently, whether we're using the original zz-izones or its complement as generated with my function:
;; Helper function
(defun funcallable (func)
(and func
(or (functionp func)
(and (symbolp func)
(fboundp func)))))
;; Traverse a list of zones such as zz-izones, and apply a lambda
;; to each zone in the list. Works equivalently with the output of
;; `my-complement-zones'.
(defun traverse-zones (func &optional zones)
(when (funcallable func)
(unless zones
(setq zones zz-izones))
(zz-unite-zones 'zones) ;; not sure if this is really necessary
(dolist (zone zones)
(let ((i (car zone))
(s (cadr zone))
(e (caddr zone)))
(funcall func i s e)))))
To illustrate the difference in structure between zz-izones and the output of zz-zones-complement, here's an example of a zz-izones structure I created in a buffer called "foo":
((4 #<marker at 1202 in foo> #<marker at 1266 in foo>) (3 #<marker at 689 in foo> #<marker at 1132 in foo>) (2 #<marker at 506 in foo> #<marker at 530 in foo>) (1 #<marker at 3 in foo> #<marker at 446 in foo>))
Here's what (zz-zones-complement zz-izones) looks like for this same zz-izones list ...
((1 4) (#<marker at 1202 in foo> 3) (#<marker at 689 in foo> 2) (#<marker at 506 in foo> 1) (#<marker at 3 in foo> 1266)
Note that each entry in zz-izones consists of an index and two markers. However, in its complement, each entry is either two integers or a marker and an integer. These structures are not isomorphic.
ADDITIONAL INFO
For (zz-zone-union (zz-izone-limits zz-izones nil t))
...
((#<marker at 3 in foo> #<marker at 446 in foo>) (#<marker at 506 in foo> #<marker at 530 in foo>) (#<marker at 689 in foo> #<marker at 1132 in foo>) (#<marker at 1202 in foo> #<marker at 1266 in foo>)
For (zz-zones-complement (zz-zone-union (zz-izone-limits zz-izones nil t)))
((1 #<marker at 3 in foo>) (#<marker at 446 in foo> #<marker at 506 in foo>) (#<marker at 530 in foo> #<marker at 689 in foo>) (#<marker at 1132 in foo> #<marker at 1202 in foo>) (#<marker at 1266 in foo> 1266))
I guess I could use this complement if I convert the "1" in the first entry to (copy-marker (point-min))
and the "1266" in the final item to (copy-marker (point-max))
... unless I'm dealing with a specific case where it doesn't matter whether I'm dealing with markers or points.
Markers are ideal, because then I can change the buffer after generating the complement, and I won't have to worry about the numeric point value within the complement structure no longer pointing to where it originally pointed.
Upvotes: 0
Reputation: 2318
I just discovered a preliminary answer. I say "preliminary", because I haven't done any in-depth testing yet. However, the "zones.el" package officially offers the functionality I'm looking for.
It creates a list of selected regions, which it calls "zones". It offers a function called zz-zones-complement
which will return the complement of the "coalesced" list of current zones, which can be gotten as follows: (zz-zones-complement (zz-zone-union zz-izones))
.
I'm confident that this will give me all of the functionality that I'm looking for. However, if I hit any issues, I'll come back and post again.
Upvotes: 0