Skip to content

Fix more bad interactions with evil visual#1235

Open
real-or-random wants to merge 3 commits intokarthink:masterfrom
real-or-random:202602-timer-buffer
Open

Fix more bad interactions with evil visual#1235
real-or-random wants to merge 3 commits intokarthink:masterfrom
real-or-random:202602-timer-buffer

Conversation

@real-or-random
Copy link
Copy Markdown
Contributor

Follow up to #1205.

The first commit is a tweak to #1205. The other commits fix additional cases.

I hope this should fix any real functionality problems with evil-mode's visual selection. The remaining issues will then be issues when displaying the transient menu (#909). But let's first fix functionality.

Comment on lines +783 to +784
;; Ensure redisplay after applying presets
:refresh-suffixes t
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This forces rebuilding the entire transient after every key press.

If you don't like the overhead introduced by this, then an alternative is to call (transient--stack-pop) (transient--env-apply #'transient--refresh-transient)) (still, instead of the (run-at-time ...). This will also refresh the main transient prefix. But it relies on transient internals. Please let me know if you prefer this solution.

Copy link
Copy Markdown
Owner

@karthink karthink Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware of :refresh-suffixes and chose to avoid it in gptel-menu because of performance issues. I use it in some other transient menus, like gptel-tools, that have to be fully dynamic to function.

Why do you need to enforce a menu-wide change here?

There are actually many calls to transient-setup or equivalent in specific infixes of gptel-menu, not just the ones on line 548 and 1053 you've removed.

Copy link
Copy Markdown
Contributor Author

@real-or-random real-or-random Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are actually many calls to transient-setup or equivalent in specific infixes of gptel-menu, not just the ones on line 548 and 1053 you've removed.

Indeed. It took me a while to figure out what is special about the calls on lines 548 and 1053 (and why they need a timer): on these lines, the active transient is not gptel-menu but gptel--preset. So when you call transient-setup there without a timer, you refresh gptel--preset but not the parent gptel-menu.

I don't think transient offers an official way to refresh the parent. I tried calling (transient-setup 'gptel-menu) but that didn't work.

If you're concerned about performance, then there are two options:

  • As I said above, (transient--stack-pop) (transient--env-apply #'transient--refresh-transient) does the job at the cost of relying on a transient internal.
  • Or, let's just drop the third commit (and perhaps also the second)
    and keep the timers here. As I wrote below, removing these is not essential.

@real-or-random
Copy link
Copy Markdown
Contributor Author

real-or-random commented Feb 5, 2026

Hm, after taking a step, I'm not convinced anymore that the second and the third commit actually do fix anything.

They avoid cases in which we get the region wrong, i.e., the user has created a visual selection, but evil hasn't had a chance to set the region to this selection). All those cases appear when displaying the transient. However, this is not just about wrong info displayed to the user (as in #909) but they actually affect functionality: if the code displaying the transient believes that there's no region, then all the choices related to a region won't even appear (e.g., "r Rewrite" or "-r Add region to context").

The problem now is that the display code gets the region wrong only when called from the post-command-hook or from a timer (1), but not when called directly by the user (2). So the second and the third commit here potentially avoid issues for case 2 but not for case 1. For case 1, we'll anyway need a bit of evil-specific code to solve these issues entirely, even if we get rid of the timers.

What does this mean?

@real-or-random
Copy link
Copy Markdown
Contributor Author

See #1236.

* gptel-rewrite.el (gptel--suffix-rewrite): The goal of moving point
backward one char is to move the user's cursor, not for us to make
edits, so use the window point instead of the buffer point; note that
we're inside a (with-current buffer ...).  The right window to use is
not the selected one, which is a minibuffer or transient window, but the
one that was selected previously, i.e., before the minibuffer or
transient was opened.

This gets us rid of a hack (run-at-time 0 ...) hack.

This is a follow up to 7095771 inspired
by a discussion on emacs-devel, see:
https://lists.gnu.org/archive/html/emacs-devel/2026-02/msg00033.html
* gptel-rewrite.el (gptel--rewrite-read-message): Get rid of the
remaining (run-at-time 0 ...) hacks in gptel-rewrite, which fixes more
bad interactions with evil-mode.

For example, this commit ensures that the region is setup correctly when
invoking transient from the "rewrite minibuffer" by pressing M-RET.
Without this commit, the "r Rewrite" action may not be available in the
resulting transient popup because (use-region-p) is wrongly nil.
Before this commit, functions that apply prefixes when a transient window is
open call transient-setup via (run-at-time 0 ...) as some kind of hack
to force a redisplay of the entire transient window. This commit
replaces this by transient's native :refresh-suffices
functionality, which is already used in gptel-tools.

This fixes another case where the region is not correctly taken into
account when using evil-mode (similar to 7095771).

In theory, this commit should make a bit faster after applying presets
and a bit slower after pressing keys that don't apply presets.  In
practice, I'm not able to notice any difference in latency.
@karthink karthink force-pushed the 202602-timer-buffer branch from 38b2a59 to 55de653 Compare March 8, 2026 17:42
@karthink
Copy link
Copy Markdown
Owner

karthink commented Mar 8, 2026

  1. I'm not comfortable enabling :refresh-suffixes on gptel-menu. Why is this necessary to fix evil-mode compatibility?
  2. Does this need to be merged before gptel-transient: Fix display issues with evil visual selections #1236 or are they independent?

@real-or-random
Copy link
Copy Markdown
Contributor Author

real-or-random commented Mar 9, 2026

Sorry, I tried to explain what everything does, but I see that there's a lot of text in this PR, and it's not super clear since also my understanding of things has evolved over time.

Let me try to explain again, and more clearly.

  1. Does this need to be merged before gptel-transient: Fix display issues with evil visual selections #1236 or are they independent?

They are independent. In terms of fixed user-facing evil-mode issues, #1236 entirely subsumes this one because it addresses the problem at a different layer. My suggestion is to go ahead and merge #1236 if you're fine with the changes there. This should resolve all user-facing issues with evil-mode, so getting #1236 in should be the main priority in this "evil project".


Assuming #1236 gets in, all this PR here does is clean up the code by getting rid of the remaining (run-at-time 0 ...) hacks. After #1236, they shouldn't make any trouble specifically for evil users. Still, the hacks are not very clean because additional code can run before the timer fires (e.g., what if the selected window has changed before the timer fires?). But since they seem to do the job in practice, getting rid of them is merely desirable but not essential.

  1. I'm not comfortable enabling :refresh-suffixes on gptel-menu. Why is this necessary to fix evil-mode compatibility?

Each commit in this PR removes one run-at-time call. The third commit does so at the cost of adding :refresh-suffixes.

Why does it need :refresh-suffixes? The reason is what we've discussed in the conversation above. This addresses refreshing the gptel-menu transient in this specific case:

  1. Open gptel-menu
  2. Press @ to open the gptel--preset transient and press some character to apply a preset
  3. Now you end up back in the gptel-menu transient and should see the options refreshed according to the applied preset.

The current code calls (run-at-time 0 nil #'transient-setup) to perform the refresh. The reason why this works is that when the timer fires, the active transient is gptel-menu, so that transient gets refreshed.

However, if we remove the timer and simply call transient-setup, then at the time of the call, the active transient will still be gptel--preset. So gptel--preset gets refreshed instead of gptel-menu.

What we want to do here is to refresh the parent. But there does not seem to be an official way of doing this. The only options I could come up with:

  • Set :refresh-suffixes. That forces a refresh every time, but I can understand that you're not comfortable with that approach.
  • Run (transient--stack-pop) (transient--env-apply #'transient--refresh-transient) instead of (transient-setup). But that's not great either since it relies on transient internals. So this would merely replace the timer hack with another (probably worse) hack.

Here's my suggestion on how to move forward:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants