I need to change the "OBJECT" string foreground color (let's say "red") in all the occurrences of the string "\command{OBJECT}" in my buffer and I need to restore the original foreground color at the end of the job.

I figured out to do something like (it's a simplified MWE):

(defun propertize-command-arg ()
    (goto-char (point-min))
    (while (search-forward-regexp "\\\\command{\\([a-z]+\\)}" nil t)
        (let ((b (match-beginning 1))
              (e (match-end 1)))
          (put-text-property b e SOMETHING))))))

SOMETHING is what I've not be able to understand/find. I tried also add-text-properties and add-face-text-property.

Please keep in mind I need to do it temporarily. I'll restore the original appearance of the text at the end of my job (script).

  • What does "job (script)" mean? Do you mean an interactive command by that? Why do you need fontification for that? Do you run a recursive-edit session? Or maybe, do you fontify for printing or htmlize-buffer? It is often better to mention your primary task instead of some problem on the way of its solution.
    – Tobias
    Jan 20 '19 at 2:53
  • @Tobias, I'm exploring some emac's features to speed up and make more confortable my LaTeX typesetting tasks. In this particular case I simply want to highlight some text (not easily detectable by regexp) during some query-replace routines. I'm also exploring the fontification as a method to discriminate region in my replacing routines (see emacs.stackexchange.com/q/47302/15606). Jan 20 '19 at 16:18

You should not try to fontify the buffer yourself.

I describe here a method that works in buffers with font-lock activated.

Add the keywords with font-lock-add-keywords as long as you need them and remove them afterwards with font-lock-remove-keywords. After such actions you should invalidate the buffer fontification with font-lock-flush.

This method has two main advantages:

  1. It fontifies the buffer just in time. Only the visible part of the buffer is fontified. The fontification is extented as needed when the visible part of the buffer changes.
  2. The keyword is also highlighted in parts of the buffer that you insert/modify after the keyword has been added.

There follows example code that shows how keywords can be added to font-lock.

(defface propertize-command-arg-face
  '((t (:foreground "Red")))
  "Face for highlighting command args with `propertize-command-arg'.
I am a bit lazy here and do not differentiate the faces terminal-wise.
See (info \"(elisp) Defining Faces\") for details."
  :group 'latex)

(defvar propertize-command-arg-keywords nil
  "Keywords highlighted by `propertize-command-arg'.")

(setq propertize-command-arg-keywords
      '(("\\\\command{\\([a-z]+\\)}" 1 'propertize-command-arg-face)))

(defun propertize-command-arg ()
  "Highlight command arguments in current buffer."

(defun unpropertize-command-arg ()
  "Unhighlight command arguments in current buffer."

You indicated in the comments that you want to use the command with latex-mode and that your MATCHER should also match more general arguments of LaTeX macros. The Elisp Manual describes that you can also use functions as matchers. That function can use scan-sexps for parsing the macro argument. The following Elisp code defines a new function propertize-command-scanner as matcher and re-defines the keywords list for font-lock-add-keywords to use propertize-command-scanner. Also note the comments given in the source code.

(defun propertize-command-scanner (bound)
  "Search for LaTeX macro \\command{...}.
Start at point and do not search beyond BOUND.
Set match data such that the embraced macro
argument is the first group."
      (let (found)
    (while (and (search-forward "\\command" bound t)
            ;; avoid matches in strings and comments
            (null (setq found
                 (nth 8
                      (syntax-ppss (point))))))))
    (let* ((b (match-beginning 0))
       (comment-forward most-positive-fixnum) ;; also skips whitespace
       (b1 (point))
       (parse-sexp-ignore-comments t)
       (e1 (scan-sexps b1 1)))
      (set-match-data (list b e1 b1 e1 (current-buffer)))
      (goto-char e1))))

(setq propertize-command-arg-keywords
      '((propertize-command-scanner 1 'propertize-command-arg-face t)))
;; OVERRIDE must be non-nil since fontification of $...$ is syntax based
;; and syntax based fontification comes first.

I only outline a strategy for the case that font-lock is not active: Define your own special face as it is done above for the font-lock case. You can register a fontification function via jit-lock-register. That function gets the boundaries of the region to be fontified and should set the face property of the text-stretches that match your keyword.

When you want to get rid of the highlighting for your keyword search for the stretches of text that have your special face as face property and remove their face property.

  • Very helpful and interesting. Your method has however the disadvantage that needs regexp to intercept the region to be highlighted/propertized. What if I have \command{Some text $\frac{\mathcal{Z}\times\frac{1}{2}}{\sqrt{\bar{t}}}$ \emph{other text}}? Jan 20 '19 at 15:55
  • (Actually I can write a regexp that matchs multiple nested { } groups, but I have experienced some stack overflow issues.) Jan 20 '19 at 15:59
  • @GabrieleNicolardi I added a possible modification for handling the case of nested groups in LaTeX macro arguments.
    – Tobias
    Jan 20 '19 at 23:22

Replace SOMETHING by 'font-lock-face '(:foreground "red") or 'face '(:foreground "red").

You want font-lock-face when font-locking is active, otherwise property face is correct.

Please look at EmacsWiki Text Properties for more explanation.

  • It works but I don't have yet any idea to undo the fontification (I mean restore the previous fontification). Any suggestion? Jan 18 '19 at 21:58
  • Ok, I tried (remove-list-of-text-properties (point-min) (point-max) '(font-lock-face)). It removes the fontification keeping untouched the sintax highlighting of the buffer. Could be it the right way to do that? Jan 18 '19 at 22:15

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.