Some time ago I wrote about making Emacs display things without actually modifying any buffer. I used text properties then, and I mentioned that a similar thing could be done using overlays, too.
Well, it turns out that there exists a very interesting function in Emacs (and it indeed uses overlays internally), which could be of help in a situation like that: momentary-string-display
. It does more or less what you’d expect from a function called like that: given a string (and a position in the buffer), it displays it there until the next keypress (or mouse click – in general, any input event). One thing I don’t particularly like about momentary-string-display
is that you have to choose a character which will “terminate” it, e.g. SPC
or RET
, and pressing that character will make the temporary text disappear – but the character itself won’t be inserted in the buffer. (Any other self-inserting character will normally insert itself.) And giving nil
as the character won’t help, since then momentary-string-display
will use the default SPC
. Well, you can’t have everything.
Or can you. Here’s the source code of momentary-string-display
.
(defun momentary-string-display (string pos &optional exit-char message) "Momentarily display STRING in the buffer at POS. Display remains until next event is input. If POS is a marker, only its position is used; its buffer is ignored. Optional third arg EXIT-CHAR can be a character, event or event description list. EXIT-CHAR defaults to SPC. If the input is EXIT-CHAR it is swallowed; otherwise it is then available as input (as a command if nothing else). Display MESSAGE (optional fourth arg) in the echo area. If MESSAGE is nil, instructions to type EXIT-CHAR are displayed there." (or exit-char (setq exit-char ?\s)) (let ((ol (make-overlay pos pos)) (str (copy-sequence string))) (unwind-protect (progn (save-excursion (overlay-put ol 'after-string str) (goto-char pos) ;; To avoid trouble with out-of-bounds position (setq pos (point)) ;; If the string end is off screen, recenter now. (if (<= (window-end nil t) pos) (recenter (/ (window-height) 2)))) (message (or message "Type %s to continue editing.") (single-key-description exit-char)) (let ((event (read-key))) ;; `exit-char' can be an event, or an event description list. (or (eq event exit-char) (eq event (event-convert-list exit-char)) (setq unread-command-events (append (this-single-command-raw-keys) unread-command-events))))) (delete-overlay ol))))
And here’s a slightly modified version, which makes any self-inserting character terminate the momentary display and insert the character:
(defun momentary-string-display-no-key (string pos &optional message) "Momentarily display STRING in the buffer at POS. Display remains until next event is input. If POS is a marker, only its position is used; its buffer is ignored. Display MESSAGE (optional third arg) in the echo area." (let ((ol (make-overlay pos pos)) (str (copy-sequence string))) (unwind-protect (progn (save-excursion (overlay-put ol 'after-string str) (goto-char pos) ;; To avoid trouble with out-of-bounds position (setq pos (point)) ;; If the string end is off screen, recenter now. (if (<= (window-end nil t) pos) (recenter (/ (window-height) 2)))) (message "%s" (or message "Press any key to continue editing.")) (let ((event (read-key))) (setq unread-command-events (append (this-single-command-raw-keys) unread-command-events)))) (delete-overlay ol))))
As you can see, it’s not rocket science. Probably the only mysterious thing is the use of read-key
and this-single-command-raw-keys
. I have to admit that I did not examine that in detail, but it seems that read-key
somehow mutates the global state – i.e., remembers the input event somewhere – and this-single-command-raw-keys
retrieves it. Not the cleanest way to do it, but given the fact that Emacs is a text editor toolkit after all, not that bad either. Anyway, another thing I learned.