I've been trying to add the ability to unfold with mouse by clicking on ellipsis in my json-mode, but after considerable time spent experimenting and looking through the Elisp manual, I can't figure out how to bind a function to clicking on the overlay I add when I fold.

Currently, my folding function looks like this:

(defun json-mode-hide-region (beg end)
  "Hides region from BEG to END with an overlay."
  (let ((overlay (make-overlay beg end))
        (map (make-sparse-keymap)))
    (define-key map [follow-link] (kbd "mouse-2"))
    (define-key map (kbd "mouse-2")
      (lambda (&rest args)
        (delete-overlay overlay)))
    ;; FIXME: show hidden content in isearch before it's finished
    (overlay-put overlay 'invisible t)
    (overlay-put overlay 'isearch-open-invisible #'delete-overlay)
    (overlay-put overlay 'display json-mode-fold-ellipsis)
    (overlay-put overlay 'mouse-face 'highlight)
    (overlay-put overlay 'help-echo "mouse-2: unfold")
    (overlay-put overlay 'keymap map)
    (overlay-put overlay 'evaporate t)))

It's worth noting that I use lexical-binding to have proper lexical scoping and closures in my code, the entirety of which is in this repo. I've tried moving out the function that handles mouse-2 to a separate function, and even throwing errors in it, but it seems to never get called. It seems that documentation about making clickable buttons and links is mostly related to text properties, and overlays which don't make the original text invisible.

Does anyone know how to turn the display text into a link that calls a function when clicked (which deletes the overlay in my case)?

  • @Drew how is my question related to isearch? The mere fact that the code sample I put up has isearch-related code isn't relevant to the actual question at all.
    – user12563
    Feb 17, 2019 at 11:48

2 Answers 2


On the one hand you set the invisible property to t on the other hand you set the display property to json-mode-fold-ellipsis. That combination does not make much sense. Use only one of those.

If you use the display property you should propertize the shown text with the keymap property -- not the text to be replaced.

You can test the following code in the *scratch* buffer. Copy and paste it into the scratch buffer place point behind it and evaluate it with C-x C-e.

It inserts the text hidden text at point and puts an overlay with the string ... as display property on it. The displayed string ... itself is propertized with the keymap property that defines the key <mouse-2> for removing the overlay (as in your code).

 '(let* ((map (make-sparse-keymap))
         (pos (point))
         (ol (progn
               (insert "hidden text")
               (make-overlay pos (point)))))
    (define-key map (kbd "<mouse-2>")
      (lambda ()
        (delete-overlay ol)))
    (overlay-put ol 'display (propertize "..." 'keymap map)))

Your key-binding code is wrong, to start with.

This will get you started: use (kbd "<mouse-2>"), not (kbd "mouse-2").

Put (debug) in your lambda to continue debugging.

(Do that to start with, and you'll see that <mouse-2> is not bound to your command with your key binding - the debugger is not invoked. A call to message in your command would show the same thing.)

Always test your kbd sexp to see what it gives. It needs to give the key description - what C-h k shows.

  • Thanks for the suggestion. I think I've tried both ways to define the key, but since I normally don't deal with the mouse, I wasn't sure which one is correct.
    – user12563
    Feb 16, 2019 at 23:07
  • You can quickly see the difference by doing what I suggested. (debug) and (message "Got here") are your friends, to see whether some code path is followed.
    – Drew
    Feb 17, 2019 at 2:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.