14

I write (and rewrite) a lot of mathematical formulas in LaTeX with Emacs. I frequently run into situations where I want to change a pair of matching parentheses, to improve readability. My Emacs is kind enough to show me the matching delimiter, but how do I change it programmatically?

For example, change the outer delimiters in one go:

( (\sqrt{a} + b)^{-1} + c^{-1} )

to

[ (\sqrt{a} + b)^{-1} + c^{-1} ]
6
  • 2
    Note it would also be interesting if such code could change e.g. \bigl(...\bigr) to \Bigl(...\Bigr) etc. Dec 20, 2017 at 10:47
  • 1
    Here I gave an example of a simplified LaTeX grammar using PEG: emacs.stackexchange.com/questions/36541/… this would be one way to approach this problem.
    – wvxvw
    Dec 20, 2017 at 11:55
  • @wvxvw I looked at your approach when I wrote this question, and indeed it looks interesting! A also hope there is something, perhaps simpler, out there. Emacs is already aware of the matching delimiter since it is highlighted. Maybe this could be leveraged?
    – Mankka
    Dec 20, 2017 at 13:35
  • 2
    Related Stack Overflow thread: How can you modify two matching delimiters at once with Emacs?
    – Dan
    Dec 20, 2017 at 14:42
  • Emacs knows to highlight the matching delimiter because either the mode implements forward-sexp-function (which, I'm guessing TeX mode does), or it will use scan-sexps to find the possible match. In the later case, the match will not always be correct. So, if all you need is to match matching delimiters, you can check the syntax of the character under point. If it's $, then it has to have a match, and you could use forwad-sexp to get to its match.
    – wvxvw
    Dec 20, 2017 at 15:09

5 Answers 5

3

Use smartparens package. It includes a function called sp-rewrap-sexp, which is exactly what you need. The project homepage (https://github.com/Fuco1/smartparens) has some gifs clearly showing the functionality.

11

For those using evil you can use evil-surround which gives you the c s motion (change, surround).

For your example then just do c s ( [ (motion, from type of paren, to type of paren)

1
  • Just what I needed!!! Thanks!
    – Hilman
    Dec 28, 2019 at 9:12
6

I use the code below and bind yf/replace-or-delete-pair to M-D.

Example usage : with point on (, I hit M-D [ and the () pair becomes a [] pair. If you hit M-D RET instead, the pair will be removed.

This code uses the syntax table, which means that for some pairs you'll have to specify the closing paren yourself. e.g. in html-mode, () can be replaced by <> by hitting M-D <. However, in many modes <> isn't a recognized pair, and M-D < will say "Don't know how to close <". You can then you just type >.

(defun yf/replace-or-delete-pair (open)
  "Replace pair at point by OPEN and its corresponding closing character.
The closing character is lookup in the syntax table or asked to
the user if not found."
  (interactive
   (list
    (read-char
     (format "Replacing pair %c%c by (or hit RET to delete pair):"
             (char-after)
             (save-excursion
               (forward-sexp 1)
               (char-before))))))
  (if (memq open '(?\n ?\r))
      (delete-pair)
    (let ((close (cdr (aref (syntax-table) open))))
      (when (not close)
        (setq close
              (read-char
               (format "Don't know how to close character %s (#%d) ; please provide a closing character: "
                       (single-key-description open 'no-angles)
                       open))))
      (yf/replace-pair open close))))

(defun yf/replace-pair (open close)
  "Replace pair at point by respective chars OPEN and CLOSE.
If CLOSE is nil, lookup the syntax table. If that fails, signal
an error."
  (let ((close (or close
                   (cdr-safe (aref (syntax-table) open))
                   (error "No matching closing char for character %s (#%d)"
                          (single-key-description open t)
                          open)))
        (parens-require-spaces))
    (insert-pair 1 open close))
  (delete-pair)
  (backward-char 1))
2

ar-parentized2bracketed-atpt would do the task.

It comes along with ar-braced2parentized-atpt and basically all respective combinations.

Get it from thingatpt-transform-delimited.el of

URL: https://github.com/andreas-roehler/thing-at-point-utils

An abstracted class of commands transforms all delimited forms, for example:

ar-delimited2bracketed-atpt

These commandes are delivered in same repo by

thingatpt-transform-generic-delimited.el

0

Matching parentheses are visualised with show-paren-mode. The logical approach is to base the function to change parens to the same underlying logic and function. When matching parens are highlighted, you can call the function toggle-parens defined below:

(defun toggle-parens ()
  "Toggle parens () <> [] at cursor.

Turn on `show-paren-mode' to see matching pairs of parentheses
and other characters in buffers. This function then uses the same
function `show-paren-data-function' to find and replace them with
the other pair of brackets.

This function can be easily modified and expanded to replace
other brackets. Currently, mismatch information is ignored and
mismatched parens are changed based on the left one."
  (interactive)
  (let* ((parens (funcall show-paren-data-function))
         (start (if (< (nth 0 parens) (nth 2 parens))
                    (nth 0 parens) (nth 2 parens)))
         (end (if (< (nth 0 parens) (nth 2 parens))
                  (nth 2 parens) (nth 0 parens)))
         (startchar (buffer-substring-no-properties start (1+ start)))
         (mismatch (nth 4 parens)))
    (when parens
      (pcase startchar
        ("(" (toggle-parens--replace "[]" start end))
        ("[" (toggle-parens--replace "()" start end))))))

(defun toggle-parens--replace (pair start end)
  "Replace parens with a new PAIR at START and END in current buffer.

A helper function for `toggle-parens'."
  (goto-char start)
  (delete-char 1)
  (insert (substring pair 0 1))
  (goto-char end)
  (delete-char 1)
  (insert (substring pair 1 2)))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.