Question

Once in a while I manually set the font-family and size different from the default, and I use buffer-face-mode to do it. (To be exact I use the mouse & pick one from the dialog box.) Once I set it, I'd like it to stay set for that buffer, even if I change modes, so I tried a customization. The idea was to add a change-major-mode-hook (which runs just before buffer-locals get killed) that would save the buffer face, if it is set, in a function to be called later- that much seems to work. But then that function seems to be called too soon, and when the mode change is over, buffer-face-mode is not active.

Here's the customization I cam up with so far

(defun my-preserve-bufface-cmmh ()
  "Keep the state of buffer-face-mode between major-mode changes"
  (if (and (local-variable-p 'buffer-face-mode) buffer-face-mode)
      (delay-mode-hooks
    (message "face is %s" buffer-face-mode-face) ; Just to show me it has the right face
    (let ((my-inner-face buffer-face-mode-face))
      (run-mode-hooks
       (message "inner %s" my-inner-face) ; it still has the right face here
       (setq buffer-face-mode-face my-inner-face)
       (buffer-face-mode))))))

(add-hook 'change-major-mode-hook
      'my-preserve-bufface-cmmh)

The messages both run and show a custom face, as they should, when I'm changing major-mode in a buffer with the minor-mode buffer-face-mode set. I had thought the combination of delay-mode-hooks ... run-mode-hooks would make setq buffer-face-mode-face ... (buffer-face-mode) run after the new mode was set up, but apparently not.

Is this customization "close"/salvageable for my wants? Is there a cleaner way?

Was it helpful?

Solution

The first thing is that delayed-mode-hooks is itself a local variable. That means, if you set it by (delay-mode-hooks (run-mode-hooks ...)) in change-major-mode-hook this will have no effect since it is killed instantaneously.

The second thing is that the stuff within your run-mode-hooks is evaluated within my-preserve-bufface-cmmh. It should be defined as a back-quoted hook function `(lambda () ...) where you splice in the values you want to keep. The alternative would be to use lexical binding (which google).

The 2nd thing is demonstrated in the following example (to be evaluated step-by-step):

(defun test (str)
  (let ((mytest (concat "hello " str)))
    (add-hook 'my-own-hook `(lambda () (message "mytest:%S" ,mytest)))))


(test "you")

(run-hooks 'my-own-hook)

(test "world")

(run-hooks 'my-own-hook)

(put  :myface 'test)

If you want to keep the font buffer-local you have to use a local variable that survives kill-all-local-variables such as buffer-file-name. You can hook a property there:

Edit: Even if the symbol has a buffer local value its properties are not buffer local. Thus, the previous approach did not work. Better: Create your own permanent buffer-local variable:

(defvar-local my-preserve-bufface nil
  "Keep the state of buffer-face-mode between major-mode changes")

(put 'my-preserve-bufface 'permanent-local t)

(defun my-preserve-bufface-put ()
  "Keep the state of buffer-face-mode between major-mode changes"
  (and (local-variable-p 'buffer-face-mode)
       buffer-face-mode
       (setq my-preserve-bufface buffer-face-mode-face)))

(defun my-preserve-bufface-get ()
  "Keep the state of buffer-face-mode between major-mode changes"
    (and my-preserve-bufface
         (setq buffer-face-mode-face my-preserve-bufface)
         (buffer-face-mode)))

(add-hook 'change-major-mode-hook 'my-preserve-bufface-put)
(add-hook 'after-change-major-mode-hook 'my-preserve-bufface-get)

OTHER TIPS

Thanks for the educational comments and especially the answer/example from @user2708138, which I am going to accept because it does answer the question.

Yet I am also going to answer my own question, since I did come up with working code that is a more general solution. I went down this path after finding I also wanted my font-size changes maintained and that they were from text-scale-mode, one more minor-mode to keep. This code reads a list of minor-modes to preserve, without my having to figure out which variables they use. (It isn't too hard for a human to figure them out, but I wanted to try having emacs do it).

Alas there is no function I know of to retrieve the variables used by a minor-mode, but the modes I'm interested in use the convention minor-mode-var-name, so this code just filters buffer-local variables for that pattern.

; Save & restore minor modes I wish to be "permanent" if set
(setq my-preserve-minor-modes '(buffer-face-mode text-scale-mode))

(defun my-preserve-minor-modes-cmmh ()
  "Keep the state of desired-permanent minor modes between major-mode changes. Assumes that associated buffer-local minor-mode variables to save begin with `minor-mode-'"
  (setq my-restore-minor-modes-acmmh nil)
  (dolist (mm my-preserve-minor-modes)
    (when (and (local-variable-p mm) (symbol-value mm))
      (push mm my-restore-minor-modes-acmmh)))
  (when my-restore-minor-modes-acmmh
    (add-hook 'after-change-major-mode-hook 'my-restore-minor-modes-acmmh)
    ; Predicate-list showing if symbol starts with a preserved mode
    (let ((mm-p-l `(lambda (locvar-nm)
             (or ,@(mapcar (lambda (mm)
                     `(and (< ,(length (symbol-name mm))
                          (length locvar-nm))
                       (string-prefix-p ,(symbol-name mm)
                                locvar-nm)))
                   my-restore-minor-modes-acmmh)))))
      ; For each found minor mode, create fn to restore its buf-local variables
      (dolist (locvar (buffer-local-variables))
    (if (and (listp locvar) (funcall mm-p-l (symbol-name (car locvar))))
          (push `(lambda()(setq ,(car locvar) ',(cdr locvar)))
            my-restore-minor-modes-acmmh))))))


(defun my-restore-minor-modes-acmmh ()
  "After major-mode change, restore minor-mode state, and remove self from hook. It restores state by calling the function stored in the variable my-restore-minor-modes-acmmh."
  (remove-hook 'after-change-major-mode-hook 'my-restore-minor-modes-acmmh)
  (dolist (restore-f my-restore-minor-modes-acmmh) (funcall restore-f)))

(add-hook 'change-major-mode-hook 'my-preserve-minor-modes-cmmh)

I did know about the permanent-local property but I wasn't sure about any unwanted side-effects... probably unwarranted paranoia on my part!

My answer could be improved if there is ever a way to get a list of variables for a minor mode, or by having the user specify an alist of variables per minor mode- in either case we wouldn't have to loop over buffer-local-variables anymore, and maybe simply making those all permanent-local would be all we need, simplifying the code quite a bit. Anyway, figuring all this out (with your help & looking at the fine manual) was quite educational.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top