2

I want to highlight every identifier (a sequence of characters matching a certain regexp or, even better, having the default face) appearing once within a block of code (a piece of arbitrary text between two empty lines) for each block in the current buffer.

Is there an existing solution to this problem?

I don't know any elisp, but I could write a program taking the contents of the buffer and annotating every unique identifier with something that emacs knows how to pick up and turn into highlighting (the annotations should not appear in the text once rendered by emacs). Is there some kind of format for explicit highlighting annotations that emacs understands? Would that be a sensible way of approaching the problem?

2
  • So in the block foo bar foo baz you want only bar and baz to be highlighted?
    – phils
    Commented Nov 16, 2021 at 3:59
  • @phils exactly. Commented Nov 16, 2021 at 4:38

1 Answer 1

3
+100

I guess you want a two-pass system then, whereby you firstly collect/count identifiers, and secondly discard those which appeared more than once, and then highlight what's left.

Here's one approach...

(defun my-highlight-unique-symbols ()
  "In each block, highlight all symbols which occur exactly once."
  (interactive)
  (font-lock-ensure)
  (save-excursion
    (goto-char (point-min))
    (let ((regexp "\\_<.+?\\_>")
          (case-fold-search nil)
          (blockstart (point))
          (blockdelimiter "\n\n\n"))
      ;; Establish the next block.
      (while (and (not (eobp))
                  (or (search-forward blockdelimiter nil t)
                      (goto-char (point-max))))
        (let ((blockend (point))
              (identifiers (obarray-make)))
          (goto-char blockstart)
          (while (re-search-forward regexp blockend t)
            ;; Have we seen this identifier before?
            (let* ((sym (intern (match-string 0) identifiers))
                   (seen (get sym 'region)))
              (if seen
                  ;; Mark it as a duplicate.
                  (unless (eq seen 'duplicate)
                    (put sym 'region 'duplicate))
                ;; This is the first time we've seen this identifier.
                ;; Store the region boundaries for the match.
                (put sym 'region (cons (match-beginning 0)
                                       (match-end 0))))))
          ;; Highlight the unique instances.
          (with-silent-modifications
            (obarray-map
             (lambda (sym)
               (let ((region (get sym 'region)))
                 (unless (eq region 'duplicate)
                   (put-text-property (car region) (cdr region)
                                      'face 'highlight))))
             identifiers))
          ;; Prepare for the next block.
          (setq blockstart blockend)
          (goto-char blockstart))))))

Which you can run with M-x my-highlight-unique-symbols

This version is not a permanent effect -- you can run it to highlight things, but subsequent edits to the buffer will likely cause font-lock to update the faces for that text, and clobber this highlighting.

If you don't edit the buffer, then I'm fairly sure that font-lock won't subsequently/stealthily fontify/clobber any of the highlight faces, as I've called font-lock-ensure up front; so calling this command should be good enough for read-only purposes.

5
  • Amazing, thanks a ton. I'll create a bounty on the question tomorrow and award it to you. ... Oh wait, that's not Stack Overflow, I don't have much rep to award you with :facepalm: Commented Nov 16, 2021 at 16:13
  • I appreciate the thought :) It's all good; happy to help.
    – phils
    Commented Nov 16, 2021 at 16:30
  • I've removed one newline from "\n\n\n", added a couple of (if (not (get-text-property (match-beginning 0) 'face)) to ignore words that are already highlighted and it works like a charm. Do you maybe have a patreon/buymeacoffee/github sponsorship account? Commented Nov 16, 2021 at 18:08
  • 1
    I don't; but if you wanted to donate something to any Free Software author who does, or to the FSF (they have a shop) or to a charity, that would be really great.
    – phils
    Commented Nov 16, 2021 at 18:56
  • Thanks for the bounty! Looking again at the code, I wondered why I was doing a second search (and building a new regexp) when the regions I wanted to highlight were already available to me the first time around; so I've updated the code with a more efficient version.
    – phils
    Commented Nov 20, 2021 at 2:03

Your Answer

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

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