Skip to content

Instantly share code, notes, and snippets.

@dandrake
Last active May 31, 2024 11:13
Show Gist options
  • Save dandrake/864f642850acaa3534cf5029868d12eb to your computer and use it in GitHub Desktop.
Save dandrake/864f642850acaa3534cf5029868d12eb to your computer and use it in GitHub Desktop.
;; Inspired by this blog post on "let's surround":
;; https://arialdomartini.github.io/emacs-surround-2 and
;; https://www.emacswiki.org/emacs/SurroundRegion.
;;
;; Notable bits:
;;
;; - Specifying =open= as a default value in the closing delimiter completing
;; read -- that allows the user to hit return to re-use the opening
;; delimiter for the closing one.
;; - This moves point accordingly, so that you can make repeated calls to
;; the function (using `C-x M-:` or similar) and it will correctly nest
;; the surrounded region.
;; - I don't know if it's just a vertico thing, but =completing-read= is
;; pretty aggressive about, well, completing based on the history. If you
;; type in something that has a completion candidate, and you want to use
;; what you typed and not the candidate, you need to use =M-RET=.
(defvar surround-region-opening-delim-hist nil "Completing read history for surround-region's opening delimiters.")
(defvar surround-region-closing-delim-hist nil "Completing read history for surround-region's closing delimiters.")
(defun surround-region-with-delimiters (opening-delimiter closing-delimiter beginning-position ending-position)
"Insert OPENING-DELIMITER at BEGINNING-POSITION and CLOSING-DELIMITER at ENDING-POSITION."
(with-current-buffer (current-buffer)
(goto-char ending-position)
(insert closing-delimiter)
(goto-char beginning-position)
(insert opening-delimiter)
(if (> (point) (mark))
;; selecting text forwards: put point at end of newly-inserted closing delim
(goto-char (+ ending-position (length opening-delimiter) (length closing-delimiter)))
(progn
;; else, selecting backwards: put point at start of opening delim,
;; mark at end of newly-inserted closing delim
(set-mark (+ ending-position (length opening-delimiter) (length closing-delimiter)))
(goto-char beginning-position)))))
(defun surround-region ()
"Calls `surround-region-with-delimiters' when the buffer is not read-only.
If the closing delimiter is the empty string, re-uses the opening delimiter.
Note that with vertico completion, use `M-<return>' (`vertico-exit-input')
to use the currently-entered string as-is, not the currently highlighted
completion candidate."
(interactive "*")
(let* ((open (completing-read "Opening delimiter: "
surround-region-opening-delim-hist nil nil nil
'surround-region-opening-delim-hist))
(close (completing-read "Closing delimiter (blank to use opening delimiter): "
surround-region-closing-delim-hist nil nil nil
'surround-region-closing-delim-hist open)))
(surround-region-with-delimiters open close (region-beginning) (region-end))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment