4

I'm looking to create a command which stores a kind of "history" for the current major-mode. I need the variable to be independent for each major mode, but consistent across all buffers in any particular major mode. For example, all buffers in python-mode should use the same version of the variable and all buffers in emacs-lisp-mode should use a single, different version of the variable.

Emacs has the ability to have buffer-local variables. Is there a way to make a variable that is mode-local?

2
  • 1
    For anyone who finds this question because they want all buffers of a given mode to use a common fixed value for a given variable. i.e. when its fine if all the buffers of that mode have their own independent variable, just so long as they all initialise it to the same value. The normal way to solve that problem is to set a buffer-local value in a mode hook function, so you should look up those concepts instead.
    – phils
    Sep 9, 2016 at 2:47
  • Yes, I should have said - another, similar question exists here which asks how to define mode-local variables that don't need to be modifiable: emacs.stackexchange.com/questions/14592/mode-local-variables
    – JCC
    Sep 9, 2016 at 13:35

3 Answers 3

2

A variable is either buffer-local or not. If it's buffer-local then the value isn't shared with other buffers. There's no built-in feature to directly have a variable that's shared between buffers. So you'll need to do something where accessing the value of the variable isn't just my-variable and setting isn't just (setq my-variable some-value).

One possibility that makes lookup time quasi-constant is to define a variable with a different name for each mode, and compute the name. For example, I call the value of foo in Python mode mode-local:foo:python-mode.

(defun make-variable-mode-local (var &optional default)
  "Make VAR eligible for mode-local use.
When VAR hasn't been set in a mode, it will have the value DEFAULT."
  (set var default))
(defun mode-local-variable (var &optional mode)
  "Return the mode-local variable symbol for VAR in the current major mode.
If MODE is specified, return the symbol for that major mode."
  (intern (concat "mode-local:" var ":" (symbol-name (or mode major-mode)))))
(defun mode-local-variable-value (var &optional mode)
  "Return the mode-local value of VAR in the current major mode.
If MODE is specified, return the value for that major mode."
  (let ((mlv (mode-local-variable var mode)))
    (symbol-value (if (boundp mlv) mlv var))))
(defun mode-local-variable-set (var new-value &optional mode)
  "Set the mode-local value of VAR to NEW-VALUE the current major mode.
If MODE is specified, return the value for that major mode."
  (let ((mlv (mode-local-variable var (or mode major-mode))))
    (set mlv new-value)))
(defun mode-local-variable-default-value (var)
  "Get the default value of the mode-local variable VAR."
  (symbol-value var))
(defun mode-local-variable-set-default (var new-value)
  "Set the default value of the mode-local variable VAR to VALUE."
  (set var new-value))

(mode-local-variable-set 'answer 42)
(message (mode-local-variable-get 'answer))

(Warning: untested code.)

Another possibility is to keep the data in a single global variable's value, arranged as an association list from mode symbols to values. This can be slow if a variable is set in a lot of major modes.

(defun make-variable-mode-local (var &optional default)
  "Make VAR eligible for mode-local use.
When VAR hasn't been set in a mode, it will have the value DEFAULT."
  (set var (cons default nil)))
(defun mode-local-variable-value (var &optional mode)
  "Return the mode-local value of VAR in the current major mode.
If MODE is specified, return the value for that major mode."
  (or mode (setq mode major-mode))
  (let ((cell (assq mode (cdr var))))
    (if cell
        (cdr cell)
      (car var))))
(defun mode-local-variable-set (var new-value &optional mode)
  "Set the mode-local value of VAR to NEW-VALUE the current major mode.
If MODE is specified, return the value for that major mode."
  (or mode (setq mode major-mode))
  (let ((cell (assq mode (cdr var))))
    (if cell
        (setcdr cell new-value)
      (setcdr var (cons (cons mode new-value) (cdr var))))))
(defun mode-local-variable-default-value (var)
  "Get the default value of the mode-local variable VAR."
  (car var))
(defun mode-local-variable-set-default (var new-value)
  "Set the default value of the mode-local variable VAR to VALUE."
  (setcar var new-value))
1
  • I like your first implementation, but both of these are great. Thanks. :)
    – JCC
    Sep 9, 2016 at 13:31
4

I think what you might do is to use properties of the variable symbol to store the history.

That is, each symbol can have a property list, which is a list of pairs. The key will be major mode names, and value the history as list you wanted.

But i don't know if that's efficient enough to work out.

3
  • Yeah, I thought of using something like an a-list with each mode mapped to its own history. It would work, but ideally I'd like to reference a variable directly and have emacs determine the version I'm referring to behind-the-scenes. That way it is trivial to swap between global and mode-local implementations.
    – JCC
    Sep 8, 2016 at 21:43
  • Jack CC: There is no such facility, so you must roll your own solution one way or another. If you're very intent on being able to swap implementations, then build that ability into the custom functions you will call, so that they return either the global value or your "mode local" value as appropriate.
    – phils
    Sep 9, 2016 at 2:49
  • Yes, I figured that might have been asking too much. I'll roll my own. Gilles' answer seems to have a good pseudo-implementation.
    – JCC
    Sep 9, 2016 at 13:32
1

You can keep that data in a hash-table (or alist) indexed with the value of major-mode. Of course, it gets more interesting if you also want to take into account the hierarchy of major modes.

1
  • This is a problem I hadn't thought of. Thanks for highlighting it. Seems to be a conceptual problem with mode-local variables in the first place.
    – JCC
    Sep 9, 2016 at 13:28

Your Answer

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

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