preview.el library of auctex shows how something like that can be done with overlays.
Font-lock based solution
Another state-of-the-art package is
It does not support setting and filtering text properties out of the box.
Paintover is a modified reduced copy of hi-lock.el.
It serves as concept study for solving this question via
- The package only provides
paintover-regexp which corresponds to
- Only the font-lock-mode support has been adapted. Overlays are not supported properly.
paintover-regexp accepts in comparison to
highlight-regexp two additional arguments:
- an expression for a plist
PROPERTIES with format
(PROP1 VAL1 PROP2 VAL2 ...).
That is the plist provided for the
FACENAME elements in
font-lock-keywords entries as
'(face FACE PROP1 VAL1 PROP2 VAL2 ...).
- Note the quote. The list is not evaluated.
- nil is an acceptable
- a filter function
FILTER, it is called as
(FILTER '(face FACE PROP1 VAL1 PROP2 VAL2 ...))
The filter can modify the list argument and return the modified list that is used for the
The prefix has changed from
- You have to add the properties that you use in the FACENAME
list yourself to
GNU Emacs 26.2 (build 2, x86_64-pc-linux-gnu, GTK+ Version 3.22.30) of 2019-04-12
Solution to your test example:
- Put paintover.el somewhere in your
load-path and run
- Find the file
test.tex with contents below.
- Paste the Elisp code below to your
*scratch* buffer and evaluate that buffer with M-x
Second time: [[!This is the END!]] try again!
Let's try another time [[!Another Time!]].
Another case [[!SoMe String WiTH CaPITALS and lower case!]]
Paste to your
(defun invert-case (str)
"Return new string with case-inverted characters from STR."
((string-match "[[:lower:]]" (string char))
((string-match "[[:upper:]]" (string char))
;; Test: (invert-case "Hello You own mE $11")
(defun paintover-flush-once ()
(remove-hook 'post-command-hook #'paintover-flush-once t)
(let ((re "\\[\\[\\!\\(.*?\\)\\!\\]\\]"))
(when (assoc-string re paintover-interactive-patterns)
(cl-pushnew 'display font-lock-extra-managed-props)
(cl-pushnew 'cursor-sensor-function font-lock-extra-managed-props)
((lambda (&rest _args) ;; sensor function
(add-hook 'post-command-hook #'paintover-flush-once nil t))))
(lambda (expr) ;; filter
(and (null (paintover-in paintover-point (match-beginning 0) (match-end 0)))
(list 'display (format "[[!%s!]]" (invert-case (match-string 1))))))))))