1

How can I get a buffer to display text that is

  1. different from the text stored on disk,
  2. updates as the buffer is modified, and
  3. replaces one string with another string every time it occurs?

I'd like a buffer to display different characters from what is stored. However, unlike this answer, I might need to have multiple characters replace the given text. I know this should be possible, because org-mode manages to do it for links. But reading that codebase seems like overkill for solving this problem...

I tried to drag-and-drop the linked solution, changing the latter string to have multiple characters. Emacs overlays the characters of the string on top of one another instead of concatenating them when the following are set (Emacs draws the O and X on top of one another).

(defconst my-font-lock-seq-1
  '(("ab" "Y") ("abb" "OX")))
(defconst my-font-lock-keywords-1
  (mapcar (lambda (x)
            `(,(concat "\\(" (car x) "\\)")
              (0 (progn (compose-region (match-beginning 1) (match-end 1) ,(cadr x)) nil))))
          my-font-lock-seq-1))

I also looked over replacing display specs in the manual, but this causes other issues: (1) when I delete characters, the display is not updated if there is no longer a match (typing ab and then backspace, the buffer still has Y) and (2) if there are multiple matches, they are replaced by a single match (typing abab yields a single Y).

(defconst my-font-lock-seq-2
  '(("ab" "Y") ("abb" "OX")))
(defconst my-font-lock-keywords-2
  (mapcar (lambda (x)
            `(,(concat "\\(" (car x) "\\)")
              (0 (progn (put-text-property (match-beginning 1) (match-end 1) 'display ,(cadr x)) nil))))
          my-font-lock-seq-2))

To reproduce these issues, you can load the following with emacs -Q:

(defun attempt-1 (x)
  `(,(concat "\\(" (car x) "\\)")
    (0 (progn (compose-region (match-beginning 1) (match-end 1) ,(cadr x)) nil))))

(defun attempt-2 (x)
  `(,(concat "\\(" (car x) "\\)")
    (0 (progn (put-text-property (match-beginning 1) (match-end 1) 'display ,(cadr x)) nil))))

(defun my-mode ()
  "MY MODE!"
  (interactive)
  (kill-all-local-variables)
  (set (make-local-variable 'font-lock-defaults) '(my-font-lock-keywords))
  (setq major-mode 'my-mode)
  (setq mode-name "MyMode"))

(defconst my-font-lock-seq
  '(("ab" "Y") ("abb" "OX")))

(defconst my-font-lock-keywords
  (mapcar 'attempt-1 my-font-lock-seq))

Now to reproduce any behaviour described above, one need only M-x my-mode (possibly changing the last line to reproduce the second set of errors).

3
  • The question isn't clear. It should be standalone, not requiring referral to another question. The question seems to suppose the missing question, and asks about a partially implemented solution. What's the problem/question?
    – Drew
    Commented Jan 23 at 2:37
  • @Drew I updated the question with a direct question at the beginning. please let me know if it still needs clarification.
    – eppo
    Commented Jan 23 at 2:50
  • Thx.................
    – Drew
    Commented Jan 23 at 15:22

2 Answers 2

1

I tried to drag-and-drop the linked solution, changing the latter string to have multiple characters. Emacs overlays the characters of the string on top of one another

You need to read the compose-region documentation, paying particular attention to the cross-referenced reference-point-alist docs.

See https://emacs.stackexchange.com/a/34882/454 for a different example using such a "concatenated" sequence of composed characters.

(There are other ways to replace text; but if compose-region is involved then you need to deal with these character composition rules.)

1
  • Thanks, this was what I needed!
    – eppo
    Commented Jan 23 at 4:42
1

As phils noted in their answer, compose-region requires more information to offset the glyphs. The following version of my-font-lock-keywords resolves all the issues mentioned in the question.

(defconst my-font-lock-keywords
  `(("\\(ab\\)" (0 (progn (compose-region (match-beginning 1) (match-end 1) ?Y) nil)))
    ("\\(abb\\)" (0 (progn (compose-region (match-beginning 1) (match-end 1) '(?O (Br . Bl) ?X)) nil)))))

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.