1

I have a function that uses smartparens to replace specified pairs of parentheses.

(defun replace-paren-at-point (paren)
  (interactive "sReplace with: ")
  (if (looking-at "[「『《〈(〔【]")
    (progn
      (set-mark-command nil)
      (forward-sexp)
      (sp-wrap-with-pair paren)
      (sp-unwrap-sexp))
    (if (save-excursion
      (backward-char)
      (looking-at "[」』》〉)〕】]"))
    (progn
      (set-mark-command nil)
      (backward-sexp)
      (sp-wrap-with-pair paren)
      (sp-unwrap-sexp)))
    (message "No parenthesis at point.")))

One issue with the function above is that the user would be prompted to enter the replace string before he/she is reminded that there is "No parenthesis at point." (in cases which the cursor is at the wrong position.)

How do I change this so that an error message appears before the user is prompted for the input?

(defun replace-paren-at-point (paren)
  (interactive)
  (if (looking-at "[「『《〈(〔【]")
      (progn
        (read-string (format "Replace %s with: " (string (char-after))))
        (set-mark-command nil)
        (forward-sexp)
        (sp-wrap-with-pair paren)
        (sp-unwrap-sexp))
    (if (save-excursion
      (backward-char)
      (looking-at "[」』》〉)〕】]"))
    (progn
      (read-string (format "Replace %s with: " (string (char-before))))
      (set-mark-command nil)
      (backward-sexp)
      (sp-wrap-with-pair paren)
      (sp-unwrap-sexp)))
    (message "No parenthesis at point.")))

I try to use read-string for the input prompt but it doesn't work this way.

Seems like read-string has to be defined without the paren argument, but I need the paren variable set to store the value for the replace string. So I am kind of stuck.


Settings:

In case some of you might like to test the code above, here are the settings that should be evaluated in init.el before running the tests.

(package-initialize)
(smartparens-global-mode t)

(sp-pair "「" "」")
(sp-pair "『" "』")
(sp-pair "【" "】")
(sp-pair "《" "》")
(sp-pair "〈" "〉")
(sp-pair "(" ")")
(sp-pair "〔" "〕")

Sati
  • 775
  • 6
  • 23
  • Can you also include some instruction on how to make this work? I don't think sp works with these special pairs out of the box. – jagrg Mar 13 '20 at 13:20

2 Answers2

1

Put a test for paren at point inside the interactive spec. If no paren at point, raise an error.

(defun replace-paren-at-point (paren)
  (interactive
   (if (not (or (looking-at "[「『《〈(〔【]")
                (save-excursion (backward-char) (looking-at "[」』》〉)〕】]"))))
       (error "No parenthesis at point")
     (list (read-string "Replace paren with: "))))
  (cond ((looking-at "[「『《〈(〔【]")
         (set-mark-command nil)
         (forward-sexp)
         (sp-wrap-with-pair paren)
         (sp-unwrap-sexp))
        ((save-excursion (backward-char) (looking-at "[」』》〉)〕】]"))
         (set-mark-command nil)
         (backward-sexp)
         (sp-wrap-with-pair paren)
         (sp-unwrap-sexp))))

(I didn't test the parts of your code. I just rearranged them. Assuming that your tests etc. do what you want, they should do that in this code too.)

Drew
  • 77,472
  • 10
  • 114
  • 243
  • It works, thanks! – Sati Mar 13 '20 at 04:29
  • Why does read-sting need to be nested within a (list (read-string "Replace paren with: "))? – Sati Mar 13 '20 at 04:29
  • 2
    C-h f interactive, and check the Emacs manual about interactive (i interactive in the manual). When passed a sexp to evaluate, instead of a special "interactive" string, the sexp must return a list of all arguments you want to pass to the function. – Drew Mar 13 '20 at 15:12
1

You can do the same using sp functions. No need to hardcode the matching pairs.

(defun replace-paren-at-point ()
  (interactive)
  (cond ((sp--looking-at (sp--get-opening-regexp))
         (sp-down-sexp)
         (call-interactively 'sp-rewrap-sexp))
        ((sp--looking-back (sp--get-closing-regexp))
         (sp-backward-sexp)
         (sp-down-sexp)
         (call-interactively 'sp-rewrap-sexp))
        (t
         (user-error "No matching pair at point"))))
jagrg
  • 3,914
  • 5
  • 19
  • That's super cool! – Sati Mar 14 '20 at 01:37
  • When I try to rewrap with using your code, I get the following error. user-error: Impossible pair prefix selected: \30024Error during redisplay: (eval (ergoemacs-status--eval)) signaled (error "Lisp nesting exceeds ‘max-lisp-eval-depth’") – Sati Mar 14 '20 at 01:43
  • Works for me, although with emacs -Q I have to either reload the major-mode or use sp-local-pair. For example: (sp-local-pair 'lisp-interaction-mode "〔" "〕"). – jagrg Mar 14 '20 at 11:18