18

electric-pair-mode is a built-in mode for automatically inserting matching pairs of delimiters (parentheses, square brackets, etc.) based on the current major mode.

I know that I can define additional pairs (which will be global) like this:

(push '(?\' . ?\') electric-pair-pairs)      ; Automatically pair single-quotes
(push '(?\' . ?\') electric-pair-text-pairs) ; ... in comments

My question is, how can I define mode-specific pairs (e.g., //, == for org-mode)?

itsjeyd
  • 14,586
  • 3
  • 58
  • 87

4 Answers4

12

An easy way to do this is by making electric-pair-pairs and electric-pair-text-pairs buffer-local and customizing them in hooks for relevant modes.

Working example for org-mode:

(defvar org-electric-pairs '((?/ . ?/) (?= . ?=)) "Electric pairs for org-mode.")

(defun org-add-electric-pairs ()
  (setq-local electric-pair-pairs (append electric-pair-pairs org-electric-pairs))
  (setq-local electric-pair-text-pairs electric-pair-pairs))

(add-hook 'org-mode-hook 'org-add-electric-pairs)

Note that this approach is generalizable to other situations in which you want to modify the value of a global variable for specific modes.


Additional info: setq-local

From the Creating and Deleting Buffer-Local Bindings section of the Elisp manual:

Macro: setq-local variable value

This macro creates a buffer-local binding in the current buffer for VARIABLE, and gives it the buffer-local value VALUE. It is equivalent to calling make-local-variable followed by setq. VARIABLE should be an unquoted symbol.

itsjeyd
  • 14,586
  • 3
  • 58
  • 87
  • How would this work for Ruby's `do/end` pair, for example? I try adding `(?do . ?end)` but I get a read error... maybe it only supports single characters? – gosukiwi Sep 27 '20 at 05:28
7

The correct way: fill a bug report through the proper channel of your project, e.g. org-submit-bug-report or report-emacs-bug and argue why the syntax class of your favourite character should be changed.

Alternatively, you could modify the proper syntax-table, (info "(elisp) Syntax Tables"), in your init.el.

Let's try Org:

(with-eval-after-load 'org
  (modify-syntax-entry ?/ "(/" org-mode-syntax-table)
  (modify-syntax-entry ?= "(=" org-mode-syntax-table)
  (add-hook 'org-mode-hook 'electric-pair-mode))

Alternatively, you can use the fallback variables. Here's a defun that should work, but that you might want to make prettier:

(defun rasmus/electric-pairs-with-local-pairs (pairs)
  "Start electric pair with buffer-local PAIRS.

  PAIRS is a list of strings and/or cons of strings."
  (require 'elec-pair)
  (let ((ec-lists '(electric-pair-pairs electric-pair-text-pairs)))
    (mapc 'make-local-variable ec-lists)        
    (mapc (lambda (L)
            (mapc (lambda (elm) (add-to-list L elm))
                  (mapcar (lambda (x)
                            (if (consp x)
                                (cons (string-to-char (car x))
                                      (string-to-char (cdr x)))
                              (cons (string-to-char x)
                                    (string-to-char x))))
                          pairs)))
          ec-lists))
  (electric-pair-mode t))

(with-eval-after-load 'org
  (add-hook 'org-mode-hook
            (lambda ()
              (rasmus/electric-pairs-with-local-pairs
               '("/" "=" ("`" . "'"))))))
rasmus
  • 2,682
  • 13
  • 20
  • Thanks for posting an answer that shows how to modify the syntax table. Out of the two suggestions in your answer, this is the approach I would prefer. W/r/t using the fallback variables, I came up with [another solution](http://emacs.stackexchange.com/a/2559/504) that's a bit shorter than the `defun` in your answer. – itsjeyd Oct 24 '14 at 16:04
2

Building on itsjeyd's answer:

(defmacro spw/add-mode-pairs (hook pairs)
  `(add-hook ,hook
             (lambda ()
               (setq-local electric-pair-pairs (append electric-pair-pairs ,pairs))
               (setq-local electric-pair-text-pairs electric-pair-pairs))))

(spw/add-mode-pairs 'emacs-lisp-mode-hook '((?` . ?')))
Sean Whitton
  • 121
  • 1
2

This answer doesn't answer your question on how to configure electric-pair-mode. But it might lead you to the results you want.

The wrap-region package available on Melpa might be the answer to your problem. Here is its brief description from its github:

Wrap Region is a minor mode for Emacs that wraps a region with punctuations. For "tagged" markup modes, such as HTML and XML, it wraps with tags.

Here is how I have set it to work in my selected modes. The snippet also covers the points you raised in your question; about org-mode font property markers.

(require 'wrap-region)

;; Enable wrap-region in the following major modes
(dolist (hook '(emacs-lisp-mode-hook
                org-mode-hook))
  (add-hook hook 'wrap-region-mode))

(wrap-region-add-wrapper "`" "'") ; select region, hit ` then region -> `region'

(wrap-region-add-wrapper "=" "=" nil 'org-mode) ; select region, hit = then region -> =region= in org-mode
(wrap-region-add-wrapper "*" "*" nil 'org-mode) ; select region, hit * then region -> *region* in org-mode
(wrap-region-add-wrapper "/" "/" nil 'org-mode) ; select region, hit / then region -> /region/ in org-mode
(wrap-region-add-wrapper "_" "_" nil 'org-mode) ; select region, hit _ then region -> _region_ in org-mode
(wrap-region-add-wrapper "+" "+" nil 'org-mode))) ; select region, hit + then region -> +region+ in org-mode

I'd like to add that this package works really well with the expand-region package (also available on Melpa).

With these 2 packages, when I am in org-mode, doing: MY-EXPAND-REGION-BINDING * on a word will make it bold.

Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179
  • Thanks for your answer. I was aware of the `wrap-region` package; it's quite useful. I am currently trying to reduce the number of third-party packages I depend on, so I won't be using this solution, but it definitely deserves a mention here! :) – itsjeyd Oct 24 '14 at 15:52