Is it possible to apply a function to every region between two “marker” lines?

I have a .tex file that looks something like this:

\$ cat foo.tex

\begin{document}

\begin{question}
lots of
multiline text

with several
paragraphs

of information
\end{question}

other text

\begin{question}
more text

here that i want to

manipulate

easily
\end{question}

\end{document}


I want to apply my-function to every region occuring between \begin{question} and \end{question}. Is this easily accomplished in emacs?

• Can we assume that my-function accepts arguments for the beginning and end of the region? – phils Jun 13 '19 at 21:16

Yes. See library Zones, in particular, function zz-do-zones or zz-map-zones.

From the Commentary in zones.el, section Define Your Own Commands:

Pretty much anything you can do with the Emacs region you can do with a set of zones (i.e., with a non-contiguous "region"). But existing Emacs commands that act on the region do not know about non-contiguous regions. What you will need to do is define new commands that take these into account.

You can define your own commands that iterate over a list of izones in a given buffer, or over such lists in a set of buffers. Utility functions zz-izone-limits, zz-izone-limits-in-bufs, and zz-read-bufs, zz-do-zones, zz-do-izones, zz-map-zones, and zz-map-izones can help with this.

As examples of such commands, if you use library highlight.el then you can use C-x n h (command hlt-highlight-regions) to highlight the izones recorded for the current buffer. You can use C-x n H (command hlt-highlight-regions-in-buffers) to do the same across a set of buffers that you specify (or across all visible buffers). If option hlt-auto-faces-flag is non-nil then each zone gets a different face. Otherwise, all of the zones are highlighted with the same face. Complementary (unbound) commands hlt-unhighlight-regions and hlt-unhighlight-regions-in-buffers unhighlight.

Defining your own command can be simple or somewhat complex, depending on how the region is used in the code for the corresponding region-action Emacs command. The definition of hlt-highlight-regions just calls existing function hlt-highlight-region once for each recorded zone:

(defun hlt-highlight-regions (&optional regions face msgp mousep buffers)
"Apply hlt-highlight-region' to regions in zz-izones'."
(interactive (list (zz-izone-limits zz-izones)
nil
t
current-prefix-arg))
(dolist (start+end  regions)
(hlt-highlight-region (nth 0 start+end) (nth 1 start+end)
face msgp mousep buffers)))


That's it - just iterate over zz-izones with a function that takes a zone as an argument. What zones.el offers in this regard is a way to easily define a set of buffer zones.

The doc also tells you how to easily create zones from bits of text such as what you describe. See, for example, function (command) zz-add-zone:

zz-add-zone is an interactive Lisp function in zones.el.

It is bound to C-x n a.

(zz-add-zone START END &optional VARIABLE NOT-BUF-LOCAL-P SET-VAR-P MSG)

Add an izone for the region to the izones of VARIABLE.

But do not add a zone if it would cover the entire buffer. Return the new value of VARIABLE.

This is a destructive operation: The list structure of the variable value can be modified.

VARIABLE defaults to the value ofzz-izones-var.

With a prefix arg you are prompted for a different variable to use, in place of the current value of zz-izones-var. The particular prefix arg determines whether the variable, if unbound, is made buffer-local, and whether zz-izones-var is set to the variable symbol:

 prefix arg          buffer-local   set ‘zz-izones-var’
----------          ------------   -------------------
Plain ‘C-u’         yes            yes
> 0 (e.g. ‘C-1’)    yes            no
= 0 (e.g. ‘C-0’)    no             yes
< 0 (e.g. ‘C--’)    no             no


Non-interactively:

• START and END are as for ‘narrow-to-region’.
• VARIABLE is the optional izones variable to use.
• Non-nil NOT-BUF-LOCAL-P means do not make VARIABLE buffer-local.
• Non-nil SET-VAR-P means set zz-izones-var to VARIABLE.
• Non-nil MSG means echo the zone limits, preceded by string MSG.

See also zz-add-region-as-izone which also adds the region as an izone, but which has a simpler signature and so can be used as a hook function. (In particular, it is used on deactivate-mark-hook by zz-auto-add-region-as-izone-mode, to automatically add the region as an izone each time it is deactivated.)

The easiest way I can think of is to use query-replace-regexp. That works if my-function takes the region as string and transforms it into the new string such as demonstrated with upcase in the following example.

BTW, I do not know how long the texts in the question environment may be.

Go to the beginning of the buffer and type M-C-% for query-replace-regexp and input the following FROM and TO strings:

\\begin{question}$$\(.\|^Q^J$$*?\)\\end{question}


where ^Q^J means pressing C-qC-j to insert a line break in the FROM string. Note the use of the non-greedy repetition operator *? to match until the next \end{question} (* would match until the last \end{question}).

\\begin{question}\,(upcase \1)\\end{question}
`