reddit / emacs

Define a keymap that is only active for my overlay

MonsieurPi · 2022-08-24 23:10
If I write this elisp code outside of a mode: (defun my/remove () "NTS." (interactive) (remove-overlays)) (defvar test-map (let ((map (make-sparse-keymap))) (keymap-set map "TAB" 'my/remove) map)) (defun test-overlay () "NTS." (interactive) (let ((new-overlay (make-overlay (mark) (point)))) (overlay-put new-overlay 'keymap test-map) (overlay-put new-overlay 'display '("...")) )) Whenever I select a region and execute `test-overlay`, if my cursor is in the region and I hit `TAB` the overlay disappears, if I'm outside, the normal `TAB` happens. However, if I put these functions in a mode: (defun my/remove () "NTS." (interactive) (remove-overlays)) (defvar test-map (let ((map (make-sparse-keymap))) (keymap-set map "TAB" 'my/remove) map)) ;;;###autoload (defun test-overlay () "NTS." (interactive) (let ((new-overlay (make-overlay (mark) (point)))) (overlay-put new-overlay 'keymap test-map) (overlay-put new-overlay 'display '("...")) )) ;;;###autoload (define-minor-mode test-minor-mode "Test mode." :init-value nil :lighter "Test" ) The keymap is never loaded. And if I put `:keymap test-map` in `define-minor-mode`, `TAB` will execute `my/remove` even thought it's not on my overlay (which looks normal). What am I missing that would make it work? Thanks :-)
[deleted] · 2022-08-24 23:29
[deleted]
ieure · 2022-08-25 02:07
> The keymap is never loaded. That's because the keymap `define-minor-mode` creates for you is bound to `test-minor-mode-map`, which doesn't have your binding. > And if I put :keymap test-map in define-minor-mode, TAB will execute my/remove even thought it's not on my overlay (which looks normal). That's correct behavior, because you told it to make that keymap is active whenever the minor mode is active. > What am I missing that would make it work? You need to bind `test-overlay` to a key sequence in the keymap for your minor mode, not `my/remove`. You may also want to change `my/remove`, as your call will remove all overlays in the buffer, not just the one with the keymap which had the binding it was invoked from. I'm not sure how to do this, off the top of my head, but probably you can get the properties of the character at point, including which overlay is being applied to it, and use its bounds as arguments to `remove-overlay`.
MonsieurPi · 2022-08-25 08:30
Thanks for your answer but it's not really answering my question. I have a behaviour that works when defining functions out of a minor mode: - apply an overlay with `f1` that has a keybinding `k` attached to it - when the cursor is in this overlay, hit `k` and the overlay will be removed (I wrote `remove-overlays` to make it simple, in my real case I use a different function ;) ) The issue is that if I copy this exact same code in a minor mode, the behaviour changes because it looks like the keymap associated to the overlay is not "exported".
ieure · 2022-08-25 16:12
Respectfully, I did answer your question, though I see that I need to explain it differently. > I have a behaviour that works when defining functions out of a minor mode: > > apply an overlay with f1 that has a keybinding k attached to it > when the cursor is in this overlay, hit k and the overlay will be removed (I wrote remove-overlays to make it simple, in my real case I use a different function ;) ) There's nothing in the code you shared that would do either of these things. Do you have a new version? What you have will: - Apply an overlay if you call `test-overlay`, ex. with `M-x test-overlay RET`. - Remove overlays when you press `TAB`. > The issue is that if I copy this exact same code in a minor mode, the behaviour changes because it looks like the keymap associated to the overlay is not "exported". Without seeing the actual code you're trying to run, it's hard to offer suggestions. The way you need to think about this is: - When the minor mode is enabled, it adds some kind of new functionality. That functionality is enabled as long as the minor mode is, and can include adding new bindings. - `test-map` is only intended to be enabled when point is in an overlay. Therefore it cannot be part of the minor mode map, since that's always active or always inactive, not active based on point's position in the buffer. Therefore, you need *two* keymaps: - One in `test-minor-mode-map`, which binds `test-overlay`, to create the overlays. - One that `test-overlay` makes active when point is inside the overlay, which removes them. Hope this helps.
MonsieurPi · 2022-08-25 16:34
I pasted two versions: \- The first version are functions defined in my init file. If I hit tab outside of the overlay, nothing happens, if my cursor is in the overlay, it will delete all overlays. \- The second version, the one ending with a minor mode definition, is the exact same code but in a test-minor.el file. And when I execute test-overlay from this mode, when I hit tab nothing happens even when I'm in the overlay. I don't need to bind test-overlay to my keymap since I don't want to have a keybind for it but the one that test-overlay makes active is not active, like it's hidden behind the minor mode and not exported.
ieure · 2022-08-25 17:04
> I pasted two versions: Neither version has any code that would operate how you describe in your previous reply. Nothing binds `f1` or `k`. The difference between what you describe and what the code says makes me think that you modified the code after sharing it, and we're now discussing different code entirely.
MonsieurPi · 2022-08-25 17:17
f1 and k are just short names because I was describing a behaviour I didn't edit my code and it does what I described Now, if you want me to capture a video of my code working and the minor mode not working I can do it but if you copy paste the first code in your init file and do what I described, it will work, if you copy paste my second code in a file and load it in your init file then `M-x test-minor-mode` it won't.
arthurno1 · 2022-08-25 20:20
> What am I missing that would make it work? A connection between mode map declaration and the mode declaration. Try to rename "test-map" to "test-minor-mode-map", or use the [:keymap keyword](https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Minor-Modes.html) to associate your map with your mode.
MonsieurPi · 2022-08-25 20:24
If I do so, `tab` will be enabled even if I'm not in my overlay because it will be in the keymap and not bound to the overlay only.
arthurno1 · 2022-08-26 00:53
Make your map buffer local and set it as local map when overlay is active, and restore the old map back when you set overlay inactive.
ieure · 2022-08-26 03:53
> if you copy paste the first code in your init file and do what I described, it will work, if you copy paste my second code in a file and load it in your init file then M-x test-minor-mode it won't. I'd be happy to reproduce. Please state the precise steps you're taking which you'd like me to replicate. As you've defined `test-minor-mode`, it does nothing except add a "Test" lighter to the modeline. What, exactly, do you want the minor mode to do? If it's: > apply an overlay with `f1`... What is `f1`? A function? A key binding? Please explain what you actually are trying to accomplish, not *how* you're trying to accomplish it.
MonsieurPi · 2022-08-26 08:43
f1 was just a shortname to avoid rewriting test-overlay in my answer, I was describing a behaviour You can reproduce already with what I pasted. Paste the first code in your init file and evaluate it, if you use test-overlay then hit tab on the created overlay it will remove overlays Paste the second code in a test-minor.el file, load it, if you use test-overlay then hit tab on the created overlay, nothing will happen
MonsieurPi · 2022-08-26 09:54
Thanks a lot, the only part I missed was making it buffer local. I don't really understand why it changes its behaviour but it works. Here's what I came up with: (defun test-remove () "NTS." (interactive) (remove-overlays)) (defvar test-keymap-default (let ((map (make-sparse-keymap))) (keymap-set map "TAB" 'test-remove) map)) (defvar test-keymap 'test-keymap-default "Keymap used within overlay. It should be set before the overlay is created.") (make-local-variable 'test-keymap) ;;;###autoload (defun test-overlay () "NTS." (interactive) (let ((ov (make-overlay (mark) (point)))) (overlay-put ov 'display '("...")) (overlay-put ov 'keymap test-keymap-default) )) ;;;###autoload (define-minor-mode test-minor-mode "Test mode." :init-value nil :keymap test-keymap :lighter "Test" ) I'm not exactly sure why I need to wrap my map in another map nor why I need to make it a local variable and why this make it all work but it does (I looked at what iedit was doing in the end)
arthurno1 · 2022-08-26 11:13
I don't thin you need need two maps. You should lookup local maps in the manual and how to make map local. Also backup previous one and restore it. If you need to make your map buffer local depends on how you use your minor mode. If you wish to have several files (buffers) with overlay active/inactive per each h buffet, then you need a buffer local. If you have only one buffer with your mode than it shouldn't matter. Otherwise I wouldn't do it the way you do at all. I would just overload tab key in your minor mode, and do whatever you wanna do if overlay is active, and just call the ordinary binding on the tab key otherwise. Not that previous approach can not work, bit I think the second one is easier, especially if you are very familiar with local naps and buffer local variables. By the way I am just typing on my phone while sitting on toilette, so I am not really much for looking at the code 😀, sorry if I missed something.
MonsieurPi · 2022-08-26 12:13
> I don't thin you need need two maps. You should lookup local maps in the manual and how to make map local. I tried with one map only but then tab will remove all overlays even if my cursor is not on the overlay (I looked at what iedit does) > Also backup previous one and restore it. Do you mean that the way I'm doing it right now overrides other minor modes keymaps? It didn't look like so when I tested it. > Otherwise I wouldn't do it the way you do at all. I would just overload tab key in your minor mode, and do whatever you wanna do if overlay is active, and just call the ordinary binding on the tab key otherwise. Not that previous approach can not work, bit I think the second one is easier, especially if you are very familiar with local naps and buffer local variables. I understand your approach but this would bind my function to tab with no possibility to change it. I'd rather have a keymap for it allowing users to change the keybind if tab doesn't suit them > By the way I am just typing on my phone while sitting on toilette, so I am not really much for looking at the code 😀, sorry if I missed something. No problem, les toilettes c'est important :)
ieure · 2022-08-26 15:51
Questions: > Paste the second code in a test-minor.el file, load it... How? `load-file`? `eval-buffer`? > ...if you use test-overlay then hit tab on the created overlay, nothing will happen In what buffer? What major mode? Do I enable `test-minor-mode`? Do I call `test-overlay` with `M-x test-overlay RET`, or some other way?