2

I have a function permanent-set-key that automates adding key-binding definitions, both local and global, to an init file. When the user requests to add a local key-binding, the function determines the current local keymap using this pretty robust approach from the answer to a question I asked earlier.

So now I know the symbol-name of the (current-local-map) and have the appropriate sexp that will define the desired key, for example:

(define-key python-shell-map [3 6] 'python-describe-symbol)

However, at the time of initialization, usually such maps are undefined, so eagerly evaluating the form above will cause error.

What is a robust programmatic approach to scheduling these forms to be eval-ed at the appropriate time?

What I have been doing so far has been to assume a minor mode exists for the current local map, and then guess the name of the minor mode's file in order to wrap the above sexp in an eval-after-load form, according to the convention foo-mode-mode-map. For example, this was generated automatically:

(eval-after-load 'dired '(define-key dired-mode-map [8388711] 'run_gdmap_dired))

and happens to work (since there does indeed exist a dired mode and file).

For the first example, this approach does not work: there does not exist a python-shell minor or major mode. The major mode comint-mode handles several "sub-modes" so adding to it customizations desired for only the "python" version does not seem appropriate.

How can I determine the name of the file which will define a symbol such as python-shell-map?

I suppose I could use after-load-functions and check for all new symbols that may have been defined, but maybe there is a more direct solution.

Community
  • 1
  • 1
ealfonso
  • 6,622
  • 5
  • 39
  • 67
  • If I understand you correctly, both the [other post](http://stackoverflow.com/questions/14489848/emacs-name-of-current-local-keymap) you cite and your suggestion that you need a *symbol* whose value is the `current-local-map` are mistaken/misguided. You do not. `define-key` takes a *keymap* as its argument, so you can just pass it `(current-local-map)` and not know or care what symbol(s) might be bound to that keymap value. – Drew Oct 07 '13 at 05:14
  • Drew, of course that will work when the current local map is the one I intend to modify, but as I noted in the other post, I am modifying various local maps at the time of initialization, ie, making local map customizations persistent. The value of `(current-local-map)` at initialization does not have to do with the various local maps that I have customized. – ealfonso Oct 07 '13 at 05:19
  • You might feel that you want a symbol, so you can do what you are trying to do here. Without knowing all that you are trying to do, I would probably instead just add the `define-key` to the appropriate mode hook (you presumably know the current `major-mode` when you generate the `define-key` code). It seems (again, without knowing all that you are doing) that you are reaching for indirection (symbols, file loading) that isn't really needed. I think all you need to know is the mode, and add `(define-key (current-local-map)...)` to the mode hook (yes, at startup time). – Drew Oct 07 '13 at 05:21
  • I am just automating the process of persistently defining keys into local maps. When I am in a local map and decide I want to override a keybinding, I don't want to do this operation manually. As in the example above, relying on the major mode does not always work, for example, with `comint-mode`. Also, I prefer not to rely on naming conventions and have my code work most of the time. I'd welcome any idea as to how to programatically find the appropriate mode hooks, but this solution seems the most robust, even if it does not make use of the explicitly intended customization mechanisms. – ealfonso Oct 07 '13 at 05:33
  • For example: which hook should I consider if I wanted to change a binding when I am in `python-shell`? The major mode in that case is `comint-mode`, but it does not make sense to add the keybindings there. There is a hook `inferior-python-mode-hook`, but 1) how do I programmatically find the name of this hook based only on the current context of active keymaps, and 2) this hook does not even seem to be the appropriate one for this task. – ealfonso Oct 07 '13 at 05:41
  • 1
    Variable `major-mode` tells you the current mode when you are in it, which is when you add the code to your init file (interactively), right? *Whatever* local map `comint-mode` (or whatever) uses will become current again when you are enter the mode in a new session --- as the value returned by `current-local-map`. So if you put `(define-key (current-local-map) KEY CMD)` on the mode hook (via your init file), then whenever that mode is entered the key will be bound in the correct map (the then-current local map), whatever the mode might be, and whatever symbols might be bound to the keymap. – Drew Oct 07 '13 at 05:44
  • There are two problems with that: 1) I don't always have an associated major mode that is particular to the current local map, as in the same example of above with `comint-mode`, which is the major mode when one enters `python-shell`. 2) I would have to rely on the naming conventions for mapping the name of a mode to the name of its hooks, and this makes me feel uneasy. This solution relies on things that are almost always going to be defined, eg, the `(current-local-map)`, and the file which defined that local map, both of which I can get programatically very easily. – ealfonso Oct 07 '13 at 05:54
  • Our comments crossed. I'm not familiar with the python code. If it is a minor mode then use the minor mode keymap. If it is a major mode then use `current-local-map`. If there is some other, strange relationship between the two then I'd have to know the details, to help; sorry. The point is that you should be able to add the key to a keymap that is appropriate, using just a mode hook. Which kind of mode will tell you which kind of mode hook. If the python stuff is not a mode, you can bind a conditional command that tests whatever for pythonness... – Drew Oct 07 '13 at 05:55
  • Try entering the `python-shell`, which should be in most emacs by default. Then try to figure out which hook you would want if you were to customize this "mode". Then imagine trying to this programatically, and maybe you will agree with me that this is a more robust solution. – ealfonso Oct 07 '13 at 06:00
  • This is degenerating into more of a chat, but I've noticed you're pretty active here with the emacs and elisp tags, so I imagine that you're pretty experienced with emacs/elisp. Is there a particular browser or extensions that you like to use? I'm on my way to installing stumpwm, which I can anticipate I will find useful, but it is the browser that remains the messiest thing in my normal workflow. I think someone asked a question once, which got closed. – ealfonso Oct 07 '13 at 06:11
  • I don't have python, sorry. But I can see in `python.el` that, e.g., `inferior-python-mode` is derived from `comint-mode`. Let's assume that's what you use. Even without python installed, if I enter `inferior-python-mode` it tells me `major-mode` is just that. And if you use `add-hook` with `inferior-python-mode-hook` to add the key it should work. Yes, a mode hook could have a weird name, but they generally do not because they use `define-minor-mode`, `define-derived-mode`, etc. Anyway, I think you understand me now, whether or not you agree. HTH. – Drew Oct 07 '13 at 06:21
  • No, sorry, I don't use Emacs as a browser. Can't help you with things like that. Ask a separate question, and someone else will help. – Drew Oct 07 '13 at 06:23
  • It was not more than curiosity about your particular choice since I have seen you're active in these tags. Actually, `inferior-python-mode` doesn't seem to be what `python-shell` brings up. I have added a hook to `inferior-python-mode` to add some of my commonly imported python modules, which works, but the `python-shell-map` seems to be independent of this. So I don't think it is yet clear the appropriate hook that I would want in that case. This is why waiting for the file which loads that particular map works well for this purpose. And my code has failed before by relying on naming – ealfonso Oct 07 '13 at 06:32
  • conventions, so I really do not like to rely on those. Well, thanks for the discussion. – ealfonso Oct 07 '13 at 06:33
  • Another consideration, which underlies what I wrote above but which now seems worth mentioning explicitly: The map for a given mode might not be defined just by loading the file -- not in its most complete form. A keymap can be defined by a mode when the mode is entered, in order, to be more up-to-date wrt command remappings etc. E.g., exiting and reentering a mode can update a map to pick up remappings for bindings that might have been added in the meantime, from other libraries or by the user. Having code depend on the file loading and not on the mode itself is *not* very robust, IMHO. HTH. – Drew Oct 07 '13 at 13:48

4 Answers4

2

I just found my own answer, thanks to an apropos that I hadn't noticed earlier:

(symbol-file SYMBOL &optional TYPE)



For more information check the manuals.

Return the name of the file that defined SYMBOL.
The value is normally an absolute file name.  It can also be nil,
if the definition is not associated with any file.  If SYMBOL
specifies an autoloaded function, the value can be a relative
file name without extension.

If TYPE is nil, then any kind of definition is acceptable.  If
TYPE is `defun', `defvar', or `defface', that specifies function
definition, variable definition, or face definition only.

[back]

So this works:

(symbol-file 'python-shell-map)--> "/usr/share/emacs/23.3/lisp/progmodes/python.elc"

Edit:

Just to make this more explicit:

(format "(eval-after-load \"%s\" '%s)" (symbol-file keymap-symbol) define-key-sexp)
ealfonso
  • 6,622
  • 5
  • 39
  • 67
1

An alternative path to get the behavior you're looking for might be to generate code of the form:

(add-hook '<MAJOR-MODE>-hook
          (lambda () (local-set-key <KEY> <BINDING>)))

this way you don't need to care about the keymap's name nor the file name in which that keymap variable is initialized.

Stefan
  • 27,908
  • 4
  • 53
  • 82
  • This looks more standard. I can't remember well but I think there is a reason I didn't want to rely on this. I think I wanted to be able to eval something after a `gud-mode` "sub-mode" for which a major or minor mode doesn't exist. I just checked and the `pdb` command is not a mode in itself, unlike e.g. `inferior-python-mode`, but is defined in `gud.el`. It does seem to manually run a `pdb-mode-hook`, but I don't know if it did at the time of the OP. – ealfonso Jun 21 '18 at 16:19
0

Corrected after comments: that works for me with shipped python.el of 24.3:

(eval-after-load 'python-mode (lambda () 
  (funcall (define-key inferior-python-mode-map MY-KEY COMMAND))))
Andreas Röhler
  • 4,804
  • 14
  • 18
  • I have tried variations of this, it does not work. `eval-after-load` expects either a file or a `provide`d symbol, and `inferior-python-mode-map` is neither, so while the sexp above does not err, it never actually evaluates the forms it is provided. – ealfonso Oct 07 '13 at 17:36
  • Right. Unless you are the maintainer of the library in question, in which case you can add as many `provide` expressions as you like, calling anything you like a "feature", this won't help. And it also suffers from the other problem I mentioned wrt depending on a file loading for the map state: it is pretty static. A map can be created dynamically and updated -- that's why we have `current-local-map` etc. The mode command, not necessarily the file top-level, is generally in charge of the map. – Drew Oct 07 '13 at 17:41
  • Drew. It would be nice if all libraries provided a stable, standard mechanism for customizing keybindings. From the point of view of my program (the function above), it is very hard to find the appropriate hooks to customize the current context of active keymaps. So your preferred approach relies on 1) Assuming that the current mode is particularly associated with the map, and 2) Assuming that the mode follows a standard naming convention in order to guess the appropriate hook. These assumptions for me have failed at times, but I have yet to find any anomalies by defining custom keys upon the – ealfonso Oct 07 '13 at 18:24
  • Okay, think have the solution. Problem goes away, if the mode-map in question is loaded, which is not the major-mode map. Will correct my answer. – Andreas Röhler Oct 07 '13 at 18:31
  • symbol definition. I suppose that it is possible as you mention, that a keymap is modified/updated after the file-load has defined the keymap symbol, but from the point of view of my program, the `eval-after-load` approach is less prone to failure. Also, if you insist in using hooks, how would you programmatically find the appropriate hooks for a given mode? What if a mode is derived and does not provide its own hooks? – ealfonso Oct 07 '13 at 18:31
  • Andreas, I'm sorry, you seem to have missed something basic here. The problem of the map not being loaded is kind of the whole point of the question. – ealfonso Oct 07 '13 at 18:33
  • Just wait my answer :) – Andreas Röhler Oct 07 '13 at 18:35
  • Before providing an answer, you should be sure to read the question fully as well as to check the other answers. Your answer basically illustrates the problem of my question, and my own answer is not more than a correction of the the same `eval-after-load` approach that you use incorrectly. – ealfonso Oct 07 '13 at 18:46
  • @ealfonso Seems you don't need `current-local-map` for setting stuff in advance – Andreas Röhler Oct 07 '13 at 19:13
  • About your updated answer: Actually, it is `python-shell-map`, and not `inferior-python-mode-map`, that is active when `python-shell` is started. Defining keys on `inferior-python-mode-map` seems to have no effect, even after a python shell is started. Also, `python-mode` is the mode for editing python files, not for interacting with the interpreter, so this is incorrect. I asked for a programmatic way to construct the sexp that would cause a `define-key` to be eval-ed at the appropriate time, but your solution does not even hand `eval-after-load` the correct first argument. – ealfonso Oct 07 '13 at 19:17
  • Evaluating all the major modes and running all interpreters for which I have a custom keybinding at initialization is obviously not an option. – ealfonso Oct 07 '13 at 19:19
  • @ealfonso Agreed. Cancelled that part. – Andreas Röhler Oct 07 '13 at 19:36
  • Ok. Now, open emacs, open a .py file, look at the value of `major-mode`. Then, open `python-shell`, look at the value of `major-mode`. Perhaps you should consider deleting your answer. – ealfonso Oct 07 '13 at 19:39
  • Also, define some keys to `inferior-python-mode-map`, then open `python-shell`, and tell me whether those keys are in effect. – ealfonso Oct 07 '13 at 19:42
  • @ealfonso Needed a "funcall" still – Andreas Röhler Oct 08 '13 at 06:15
0

Another solution that I ended up using eventually was to use the after-load-functions hook to define a function eval-after-sym-loaded, which evaluates a 0-ary function as soon as the given symbol becomes bound after a load-file call--possibly immediately:

(defun eval-after-sym-loaded (sym fun)
  "funcall the 0-argument function `fun' only after `sym' has been bound after a `load-file'
if `sym' is already bound, call `fun' immediately"
  (if (boundp sym)
      (funcall fun)
    (push (cons sym fun) eval-after-sym-loaded-alist)))

(defvar eval-after-sym-loaded-alist nil
  "alist used internally by eval-after-sym-loaded")

(defun eval-after-sym-loaded-hook (file-loaded)
  (setf eval-after-sym-loaded-alist
    (loop for (sym . fun) in eval-after-sym-loaded-alist
          if (boundp sym) do
              (funcall fun)
          else collect (cons sym fun))))

(add-hook 'after-load-functions 'eval-after-sym-loaded-hook)

;; example usage:

(eval-after-sym-loaded 'html-mode-map
                       (lambda () (message "the symbol 'html-mode-map has been loaded!")))

(eval-after-sym-loaded 'js-mode-map
                       (lambda () (message "the symbol 'js-mode-map has been loaded!")))
ealfonso
  • 6,622
  • 5
  • 39
  • 67