3

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?

Brian Fitzpatrick
  • 2,325
  • 1
  • 20
  • 42
  • Can we assume that my-function accepts arguments for the beginning and end of the region? – phils Jun 13 '19 at 21:16

2 Answers2

1

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}
Tobias
  • 33,167
  • 1
  • 37
  • 77
1

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.)

Drew
  • 77,472
  • 10
  • 114
  • 243