Saturday, September 18, 2021

Snarfing String Within Delimiters With One Defun

Snarfing String Within Delimiters With One Defun

1 Executive Summary

I found that I frequently needed to snarf a string enclosed within delimiters, e.g., URLs in email messages <url>, bolded, italics and other styled text in org-mode etc. I first tried package ciel but found that it did not handle all the delimiters I wanted. However looking into it further revealed that emacs had all the tools needed to reduce the task to a single defun!

2 The Solution

Here is the solution I implemented at emacspeak-wizards-snarf-sexp. invoking this command with point on the opening delimiter snarfs the enclosed string into the kill-ring; an optional prefix arg clears it as well. The code below is the same as in the Emacspeak project, but with emacspeak-specific calls removed:

(defun snarf-sexp-contents (&optional delete)
  "Snarf the contents between delimiters at point.
Optional interactive prefix arg deletes it."
  (interactive "P")
  (let ((orig (point))
        (pair nil)
        (pairs ;;; The delimiter pairs:
         '((?< ?>)
           (?\[ ?\])
           (?\( ?\))
           (?{ ?})
           (?\" ?\")
           (?' ?')
           (?` ?')
           (?| ?|)
           (?* ?*)
           (?/ ?/)
           (?- ?-)
           (?_ ?_)
           (?~ ?~)))
        (char (char-after))
        (stab nil)) ;;; Syntax table we  use
    (unless (setq pair (assoc char pairs)) ;;; Not on a delimiter 
      (error "Point is not on a supported delimiter"))
    (setq stab (copy-syntax-table))
    (with-syntax-table stab
      (cond
       ((= (cl-first pair) (cl-second pair)) ;;;Like quotes
        (modify-syntax-entry (cl-first pair) "\"" ) 
        (modify-syntax-entry (cl-second pair) "\"" ))
       (t;;; Like parens 
        (modify-syntax-entry (cl-first pair) "(")
        (modify-syntax-entry (cl-second pair) ")")))
      (save-excursion;;; We have our sexp 
        (forward-sexp) ;;; Will error out if delims dont match
        (cond
         (delete ;;; Clear sexp contents 
          (kill-region (1+ orig) (1- (point))))
         (t ;;; Copy sexp contents
             (kill-ring-save (1+ orig) (1- (point)))))))))

2.1 Key Take-Aways

  • S-expressions are a key Emacs concept with extensive built-in support.
  • S-expressions are determined by matching delimiters.
  • Delimiters are defined by the syntax-table in effect.
  • Emacs-lisp primitives let us define and manipulate temporary syntax-tables.
  • Putting it all together, the underlying task of snarfing the contents within a pair of delimiters reduces to a few calls to the underlying primitives.