universal-ctags / citre

A superior code reading & auto-completion tool with pluggable backends.
GNU General Public License v3.0
320 stars 26 forks source link

how to customize `citre-peek-keymap` without changing the source code? #127

Closed milanglacier closed 2 years ago

milanglacier commented 2 years ago

Hi! I am new to elisp and not quiet familiar it, how can I change the keymaps defined in citre-peek-keymap without directly changing the source code? If I directly call (define-key blablabla) in my config file, then such keymaps would have effects globally. Besides, I am using evil with doom, so it would be better it is evil-compatible. Thanks for your correspondence!

AmaiKinono commented 2 years ago

Hi ;)

You can just learn from how citre-peek defines it. citre-peek defines keybind like this (just open citre-peek.el and search the code!):

(define-key map (kbd "M-n") 'citre-peek-next-line)

So you could do:

(define-key citre-peek-keymap (kbd "M-n") 'citre-peek-next-line)

See the documentation of define-key and kbd to learn more (C-h k, or M-x describe-function).

I am using evil with doom, so it would be better it is evil-compatible.

I don't use doom and haven't been using evil for a long time, so I may not have a working solution for this. Keybindings defined in a minor mode can be overriden by motion state keymap (which also applies to normal state), and the simplest way is to just avoid motion state keybindings in citre-peek-keymap. But doing that for a all minor modes you use can be time consuming.

My own solution is to let the minor mode keymap override motion state keymap. It's easy:

(evil-make-overriding-map citre-peek-keymap 'motion)

Then I add back h/j/k/l, :, G and gg so basic motions could still work. You can find my solution here, see the function toki/add-minimal-evil-keybinds-to-local-map.

milanglacier commented 2 years ago

Thank you for you help! Though followed your instruction, I still find that for some reason, my following config does not work.

(use-package! citre
  :defer t
  :init
  (require 'citre-config)
  (map! ;; this is the doom macro to make defining modal keymap more easily
   :n "C-]" 'citre-jump
   :n "C-w ]" 'citre-peek
   :n "C-t" 'citre-jump-back)
  (evil-make-overriding-map citre-peek-keymap 'motion)
  (evil-make-overriding-map citre-peek-keymap 'normal)
  ;; the above three keymaps work, but the following two keymaps doesn't work
  (define-key citre-peek-map (kbd "]t") 'citre-peek-next-definition)
  (define-key citre-peek-map (kbd "[t") 'citre-peek-prev-definition)
  ;; or define keymaps using evil-define-keys also not work
  (evil-define-key 'normal citre-peek-keymap
    (kbd "]t") #'citre-peek-next-definition
    (kbd "[t") #'citre-peek-prev-definition))
AmaiKinono commented 2 years ago

First, a piece of advice: when configuring Emacs, it's better to make sure a small snippet of code works, then put it into your init file, then working on the next small snippet of code, rather than just write things in the init file and hoping it to work. C-x C-e (eval-last-sexp) is a good helper.

but the following two keymaps doesn't work

But how? You got an error? The keybinding doesn't do anything? Or it triggers another command?

My guess is you got an error saying "]" is not a prefix key. This is expected as normally pressing "]" should insert it, so any keybinding starting with "]" could not work. To make it work, let "]" do nothing in citre-peek-keymap so it could be used as a prefix key:

(define-key citre-peek-map (kbd "]") nil)
(define-key citre-peek-map (kbd "]t") 'citre-peek-next-definition)

But this is undesired. When citre-peek-mode is enabled, it makes it impossible to insert "[".

or define keymaps using evil-define-keys also not work

Sorry but I don't know about this function. And again, how does it "not work"?

AmaiKinono commented 2 years ago

I tried and your code actually works for me:

(evil-define-key 'normal citre-peek-keymap
  (kbd "]t") #'citre-peek-next-definition
  (kbd "[t") #'citre-peek-prev-definition)

The problem may be you called it before citre-peek is loaded, so citre-peek-keymap is still undefined. evil-define-key doesn't signal an error in this case. Try put it in the :config block instead.

milanglacier commented 2 years ago

Hi! The thing is: nothing happens, what happens is completely the same as what happens if I didn't define those keymaps:

In both case: evil-define-key and define-key, when I press ]t, the thing happening is the same: the minibuffer has output: no more matches. I was thinking about this is because ] is defined by evil in normal state, then ]t doesn't override evil setting.

describe-key ]t shows that it is defined by evil:

] t runs the command hl-todo-next (found in evil-motion-state-map), which is an
interactive compiled Lisp function in ‘hl-todo.el’.

It is bound to ] t.

(hl-todo-next ARG)

Jump to the next TODO or similar keyword.
The prefix argument ARG specifies how many keywords to move.
A negative argument means move backward that many keywords.

then I am wondering, why my override settings doesn't work? But I also defines I think this may not be a citre specific question but rather than a evil specific question, I think may be I should search for evil's github repo‘s documentation. But since I am new to elisp, I would be very appreciated if you could provide me some idea on what's happening here.

milanglacier commented 2 years ago

I tried and your code actually works for me:

(evil-define-key 'normal citre-peek-keymap
  (kbd "]t") #'citre-peek-next-definition
  (kbd "[t") #'citre-peek-prev-definition)

The problem may be you called it before citre-peek is loaded, so citre-peek-keymap is still undefined. evil-define-key doesn't signal an error in this case. Try put it in the :config block instead.

Thank you! I havn't see this thread before I make my reply as above! I will give it a try!

milanglacier commented 2 years ago

And this works for me. So I just need to put it in the config part.

May I ask about a question? What config options should I set before the package being loaded, like I set C-] as citre-jump at the init part, i.e. before the package is loaded and it works but I need to define keymaps of citre-peek-keymap after the package is loaded, how do I know when to set the options?

AmaiKinono commented 2 years ago

It's not easy to answer :D

You bind citre-jump in global-map, and it is part of Emacs, not Citre. So this works before Citre is loaded.

But you bind peek related commands in citre-peek-map, which is defined by Citre, so it could not work before Citre is loaded.

If you need any value/function... defined in the package, you should put it in the :config block. Normally if you use them before they are defined, Emacs will signal an error, but evil-define-key is a macro, it could choose to evaluate its arguments or not, so it may not produce the error.

A package could be "lazy loaded". That is, it's loaded after you use any of the specified functions in it. Package developers usually specify interactive commands as these functions, so the package is only loaded after you call the commands.

:defer t and several other keywords enables lazy loading. So if that's the case, make sure you bind these commands in the :init block. You need to think a bit here: You may call citre-peek before Citre is loaded, so you bind it in :init block; But you only use commands like citre-peek-next-definition after you call citre-peek, and by then the package is already loaded, so you could bind it in the :config block.

Normally, to reduce the impact on Emacs startup time, we put only necessary code ino the :init block, and the rest into the :config block.

milanglacier commented 2 years ago

thanks for your help. After some tweaking, I find that where the keymaps are defined (at :config or :init block) doesn’t matter. What matters is I need to firstly enter the insert mode after I opens the citre-peek-window(only once) After that all the keymaps I defined in citre-peek-keymap will work. I think the reason might be evil-define-key will lazy load the keymaps you defined. And only after entering insert mode will evil-define-key knows that you have entering citre-peek-mode and then the keymaps you defined will be activated.

I am new to elisp and I don’t know this behavior is designed as expected or not. But I’m fine with just entering insert mode once, since it doesn’t take me much more key strokes.

milanglacier commented 1 year ago

I solve the issue of evil specific citre-mode-map not activated unless a state transition by the following config

(add-hook 'citre-mode-hook #'evil-normalize-keymaps)
(add-hook 'citre-peek--mode-hook #'evil-normalize-keymaps)

see the rationale at https://github.com/emacs-evil/evil/issues/301#issuecomment-591569346