emacselispinverseregions

Emacs: select multiple regions and switch to inverse


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:

  1. Mark lines A, C, and E as multiple regions
  2. Apply some elisp to the text within these marked regions
  3. Tell emacs to switch the marked and unmarked regions. Now, lines B, D, and F should be the ones that are marked as multiple regions
  4. Apply some other elisp to the text within these other regions

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?


Solution

  • Your question is general. My answer will likely fit some of it, but maybe not all.

    1. 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:

      • Sort them.
      • Unite (coalesce) adjacent or overlapping zones (which includes sorting them).
      • Complement them if they have been united.
      • Intersect them.
      • Narrow the buffer to zones in the list - see MultipleNarrowings.
      • Select zones in the list as the active region. Cycle among regions.
      • Search them (they are automatically united first). For this you need library isearch-prop.el (see Isearch+).
      • Highlight and unhighlight them. (For this you need the Highlight library (highlight.el) or library facemenu+.el (see Facemenu+).
      • Add the active region to a list of zones.
      • Add the region to a list of zones, and then unite (coalesce) the zones.
      • Delete a zone from a list of zones.
      • Clone a zones variable to another one, so the clone has the same zones.
      • Clone a zones variable and then unite the zones of the clone.
      • Make a list-of-zones variable persistent, in a bookmark. Use the bookmark to restore it in a subsequent Emacs session. For this you need library Bookmark+.
    2. 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.

      • You can also search the complement of a set of zones, which I think is what your question is really about. The areas not being searched can optionally be dimmed during search.
      • You can search various "THINGS" as zones. Things can be anything you can define syntactically. They include the usual Emacs "things", and if you use library thingatpt+.el then you can easily define additional things.
    3. 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.