I have this example function:

(defun enumerate-foo ()
    (let ((counter 0))
      (goto-char (point-min))
      (while (search-forward-regexp "\\<foo\\>" nil t)
        (setq counter (1+ counter))
        (let* ((b (copy-marker (match-beginning 0)))
               (e (copy-marker (match-end 0)))
               (foo-counter-overlay (make-overlay b e))
                (propertize (format "[%d]" counter)
                            'face '(:background "DarkGoldenrod4")))
               (overlay-string (concat "foo " overlay-counter)))
          (overlay-put foo-counter-overlay 'display overlay-string)
          (overlay-put foo-counter-overlay 'evaporate t))))))

If I use it in a buffer that conteins:





I get:

enter image description here

Now if I run (query-replace "foo" "bar" nil (point-min) (point-max)) the buffer "looks" unmodified:

enter image description here

My question is: How can I use the modification-hooks overlay property to delete the overlay "if any character within the overlay is changed"?

I know I can use text-properties instead but in my specific case I'd prefer to use overlays because I find easier to create toggle specific functions.

1 Answer 1


EDIT In response to the comments

It appears that modification-hooks do not get triggered when text is inserted in front of the overlay. For that there exists insert-in-front-hooks. The code block in the original answer has been fixed accordingly (the function now additionally gets added to the insert-in-front-hooks).


The Overlay Properties section of the elisp manual explains how modification-hooks work. To be sure that some character has changed, it is easiest to compare the before-string with the after-string (this does not include for deletion the case of replacing some character(s) with an/some identical character(s)).

Here follows an example of how to create an overlay that gets deleted when a character within it gets changed.

(let* ((o (make-overlay (point)
                        (progn (insert "foo") (point))))
       (overlay-counter (propertize "[1]" 'face '(:background "DarkGoldenrod4")))
       (overlay-string (concat "foo " overlay-counter)))
  (overlay-put o 'display overlay-string)
  (dolist (h `(modification-hooks insert-in-front-hooks))
    (overlay-put o h
                 (list (let (modification-hooks-before-string)
                         (lambda (overlay after beg end &optional length)
                           (if after
                               (unless (string= (buffer-substring beg end)
                                 (delete-overlay overlay))
                             (setq modification-hooks-before-string (buffer-substring beg end)))))))))

To understand what the code does, I refer you to that section, just mentioned, in the elisp manual.

  • I you go at the beginning of the overlay and add a char (or a newline/space) you don't see any change in the buffer. The text is added behind the overlay and the overlay is not removed.
    – Gabriele
    Dec 4, 2023 at 8:39
  • Okay, I did not notice that. However, to me, that looks like a bug. Or maybe I/we do not correctly understand what the description in the modification hooks section means. It says: the list of functions is called when 'text is inserted strictly within the overlay'. To me, this text seems to be inserted strictly within the overlay, but it could be that the manual means something else. I guess you should report this as a bug and await the answer. Dec 4, 2023 at 9:49
  • B.t.w., the problem is that the modification-hooks functions don't get called when you insert some character at the beginning. Dec 4, 2023 at 9:53
  • I think this is being handled by insert-in-front-hooks. I made some tests and it works.
    – Gabriele
    Dec 4, 2023 at 10:06
  • 1
    The most interesting thing, for me, is indeed the 'closure environment'. This is a thing I wasn't aware! I really need to look into this further!
    – Gabriele
    Dec 4, 2023 at 13:37

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.