5

Consider a pattern/regular expression I will call reg which matches multiple times in an emacs buffer. I want emacs to recognize all matches for reg and replace (temporarily, like folding) each matching text match with the result of a custom elisp function applied to that particular match, which I call f(match). Furthermore the replaced text should be (customizable) highlighted (other color etc.) and if you move your cursor into an occurrence of f(match), it should display the original match as long as the cursor is inside match (if you move it outside, it just shows f(match) again).

To have a simple example in mind, let reg be say \[\[\!.*?\!\]\] and the elisp function f could just translate all lower case letters in the matched text to upper case and vice versa. So for example [[!HelloWorld1!]] should be folded to [[!hELLOwORLD1!]]

In my particular case I want it to replace in a more complex way results of mathematical python calculations with the results in a latex file using pythontex.

Is there any package which could do this? If there are multiple packages, what would be the "state of the art" for doing something like this?

  • The built-in occur function or loccur package should get you part of the way there. Don't know of any out-of-the box solutions but you could build on occur with a custom function to replace match with f(match) to all matches except if point is in match. – Stefan Avey Jul 30 at 13:46
  • The article Higlight text ... with other texts describes something similar. One would have to adapt my-highlight-regexp-with-string to your purpose. (That should be relatively easy.) – Tobias Jul 30 at 15:34
4
+50

Overlay-based solution

The 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 hi-lock.el. 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 font-lock.

Reduction:

  1. The package only provides paintover-regexp which corresponds to highlight-regexp.
  2. Only the font-lock-mode support has been adapted. Overlays are not supported properly.

Modification:

paintover-regexp accepts in comparison to highlight-regexp two additional arguments:

  1. 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 PROPERTIES argument.
  2. 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 FACENAME element.

The prefix has changed from hi-lock to paintover.

Notes:

  1. You have to add the properties that you use in the FACENAME list yourself to font-lock-extra-managed-props.

Test:

emacs-version: 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:

  • start emacs -Q
  • Put paintover.el somewhere in your load-path and run (require 'paintover.el)
  • Find the file test.tex with contents below.
  • Paste the Elisp code below to your *scratch* buffer and evaluate that buffer with M-x eval-buffer.

Contents of test.tex:

[[!HelloWorld1!]]

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 *scratch* buffer:

(defun invert-case (str)
  "Return new string with case-inverted characters from STR."
  (cl-map 'string
          (lambda (char)
            (let (case-fold-search)
              (cond
               ((string-match "[[:lower:]]" (string char))
                (upcase char))
               ((string-match "[[:upper:]]" (string char))
                (downcase char))
               (t char))))
          str))
;; Test: (invert-case "Hello You own mE $11")

(defun paintover-flush-once ()
  (message "paintover-flush-once")
  (remove-hook 'post-command-hook #'paintover-flush-once t)
  (font-lock-flush)
  (font-lock-ensure))

(with-current-buffer "test.tex"
  (let ((re "\\[\\[\\!\\(.*?\\)\\!\\]\\]"))
    (when (assoc-string re paintover-interactive-patterns)
      (paintover-unface-buffer re))
    (cl-pushnew 'display font-lock-extra-managed-props)
    (cl-pushnew 'cursor-sensor-function font-lock-extra-managed-props)
    (cursor-sensor-mode)
    (paintover-regexp
     re
     'paintover-yellow
     '(cursor-sensor-functions
       ((lambda (&rest _args) ;; sensor function
          (add-hook 'post-command-hook #'paintover-flush-once nil t))))
     (lambda (expr) ;; filter
       (append expr
               (and (null (paintover-in paintover-point (match-beginning 0) (match-end 0)))
                    (list 'display (format "[[!%s!]]" (invert-case (match-string 1))))))))))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.