unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Possible support for buffer local idle timers?
@ 2021-09-20 13:49 Campbell Barton
  2021-09-20 15:30 ` Eli Zaretskii
  0 siblings, 1 reply; 10+ messages in thread
From: Campbell Barton @ 2021-09-20 13:49 UTC (permalink / raw)
  To: emacs-devel

Hi, I was wondering if buffer-local idle timers had been considered.

The motivation for this feature is that a buffer-local minor mode may
want to perform an action when emacs is idle.

Currently the only convenient way to do this is to add a global timer
that checks the buffer and it's minor modes.

This has the reasonably big down-side that disabling the idle-timer
requires checking if any buffer currently uses this minor mode,
some packages don't bother and leave it turned on, e.g: [0], so
disabling the mode never turns off the idle timer.

It seems like elisp-only buffer-local idle-timers could be implemented
using something similar to this solution [1],
the main difference would be that the global-idle-timers would be
shared between users with matching delays.

Has this been considered or is there a reason this wouldn't be
acceptable as part of Emacs?

[0]: https://github.com/nonsequitur/idle-highlight-mode/blob/master/idle-highlight-mode.el
[1]: https://emacs.stackexchange.com/a/13275/2418



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Possible support for buffer local idle timers?
  2021-09-20 13:49 Possible support for buffer local idle timers? Campbell Barton
@ 2021-09-20 15:30 ` Eli Zaretskii
  2021-09-20 15:50   ` Campbell Barton
  0 siblings, 1 reply; 10+ messages in thread
From: Eli Zaretskii @ 2021-09-20 15:30 UTC (permalink / raw)
  To: Campbell Barton; +Cc: emacs-devel

> From: Campbell Barton <ideasman42@gmail.com>
> Date: Mon, 20 Sep 2021 23:49:28 +1000
> 
> Hi, I was wondering if buffer-local idle timers had been considered.

I'm not sure I understand how would that work.  Timers run as part of
the Emacs main command loop, so they are by their nature global.  How
did you imagine that a buffer-local timer would express its "locality"?



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Possible support for buffer local idle timers?
  2021-09-20 15:30 ` Eli Zaretskii
@ 2021-09-20 15:50   ` Campbell Barton
  2021-09-20 16:08     ` Eli Zaretskii
  0 siblings, 1 reply; 10+ messages in thread
From: Campbell Barton @ 2021-09-20 15:50 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

On Tue, Sep 21, 2021 at 1:31 AM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > From: Campbell Barton <ideasman42@gmail.com>
> > Date: Mon, 20 Sep 2021 23:49:28 +1000
> >
> > Hi, I was wondering if buffer-local idle timers had been considered.
>
> I'm not sure I understand how would that work.  Timers run as part of
> the Emacs main command loop, so they are by their nature global.  How
> did you imagine that a buffer-local timer would express its "locality"?

Yes, the idle timers themselves would be global, but only some buffers
would have call-backs registered to run with these timers.

Exactly how this works internally is an implementation detail, for the
purpose of discussion it could work like this:

;; Setup might look like this.
(setq-local my-timer-handle (run-with-idle-timer-local delay t 'my-fn))

Internally a pool of repeating timers would be kept, one for each
unique delay used by one or more handles, this handle would be created
and added to a list of handles associated with this timer.
Running the timer would loop over callbacks running them if their
buffer matches the current buffer. Any deleted buffers could have
their timer-handles discarded at this point.

;; Cancel might look like this.
(cancel-timer-local buffer timer-handle)

This would remove the handle as well as the timer if there are no
timer-handles left using this timer.

-- 
- Campbell



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Possible support for buffer local idle timers?
  2021-09-20 15:50   ` Campbell Barton
@ 2021-09-20 16:08     ` Eli Zaretskii
  2021-09-21  0:36       ` Campbell Barton
  0 siblings, 1 reply; 10+ messages in thread
From: Eli Zaretskii @ 2021-09-20 16:08 UTC (permalink / raw)
  To: Campbell Barton; +Cc: emacs-devel

> From: Campbell Barton <ideasman42@gmail.com>
> Date: Tue, 21 Sep 2021 01:50:47 +1000
> Cc: emacs-devel@gnu.org
> 
> > I'm not sure I understand how would that work.  Timers run as part of
> > the Emacs main command loop, so they are by their nature global.  How
> > did you imagine that a buffer-local timer would express its "locality"?
> 
> Yes, the idle timers themselves would be global, but only some buffers
> would have call-backs registered to run with these timers.
> 
> Exactly how this works internally is an implementation detail, for the
> purpose of discussion it could work like this:
> 
> ;; Setup might look like this.
> (setq-local my-timer-handle (run-with-idle-timer-local delay t 'my-fn))
> 
> Internally a pool of repeating timers would be kept, one for each
> unique delay used by one or more handles, this handle would be created
> and added to a list of handles associated with this timer.
> Running the timer would loop over callbacks running them if their
> buffer matches the current buffer.

So you want such a "buffer-local" timer call its timer function only
if/when the buffer is the current buffer?  That raises some issues
that I think would need to be figured out:

  . does the timer start measuring idle time only when the buffer is
    the current buffer, or regardless of that?
  . what to do when the timer expired but the buffer wasn't current,
    and then the buffer became current? does the callback gets called
    right away, or do we "miss" the timer in that case?
  . how to handle repeated timers?



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Possible support for buffer local idle timers?
  2021-09-20 16:08     ` Eli Zaretskii
@ 2021-09-21  0:36       ` Campbell Barton
  2021-09-21  6:07         ` Eli Zaretskii
  0 siblings, 1 reply; 10+ messages in thread
From: Campbell Barton @ 2021-09-21  0:36 UTC (permalink / raw)
  To: Eli Zaretskii, emacs-devel

On 9/21/21 02:08, Eli Zaretskii wrote:
>> From: Campbell Barton <ideasman42@gmail.com>
>> Date: Tue, 21 Sep 2021 01:50:47 +1000
>> Cc: emacs-devel@gnu.org
>>
>>> I'm not sure I understand how would that work.  Timers run as part of
>>> the Emacs main command loop, so they are by their nature global.  How
>>> did you imagine that a buffer-local timer would express its "locality"?
>>
>> Yes, the idle timers themselves would be global, but only some buffers
>> would have call-backs registered to run with these timers.
>>
>> Exactly how this works internally is an implementation detail, for the
>> purpose of discussion it could work like this:
>>
>> ;; Setup might look like this.
>> (setq-local my-timer-handle (run-with-idle-timer-local delay t 'my-fn))
>>
>> Internally a pool of repeating timers would be kept, one for each
>> unique delay used by one or more handles, this handle would be created
>> and added to a list of handles associated with this timer.
>> Running the timer would loop over callbacks running them if their
>> buffer matches the current buffer.
> 
> So you want such a "buffer-local" timer call its timer function only
> if/when the buffer is the current buffer?  That raises some issues
> that I think would need to be figured out:

Right, there are indeed some wrinkles to figure out... my sense is it's 
only practical to do this if it fits well with the existing global timers.

> 
>    . does the timer start measuring idle time only when the buffer is
>      the current buffer, or regardless of that?

would just go with default behavior of global idle timers since a user 
switching buffers will typically reset idle timers.

>    . what to do when the timer expired but the buffer wasn't current,
>      and then the buffer became current? does the callback gets called
>      right away, or do we "miss" the timer in that case?

default behavior could be to run timers on the previously active buffer 
(instead of missing them), although tracking this information could get 
more involved.

It may be useful for callers to request not to run in the case the 
buffer becomes inactive.

>    . how to handle repeated timers?

As far as I can see repeated timers would be the primary use-case, for 
this feature.

I'm not sure what you mean by "how to handle", all callbacks registered 
to run a repeated buffer local timer could share a timer, this would 
store a list of callbacks which would run each time.

---

It may be that all things considered - there are too many ambiguities 
and corner cases for this to be implemented cleanly.

To be sure of this I think I'd have to attempt to write a small API to 
test if it's practical.

Again, this is meant to be an alternative to packages registering their 
global repeating idle timers that are never removed.
If there are better alternatives to this (such as starting/stopping 
global-idle-timers on switching buffers, perhaps using: 
window-state-change-hook, then that might be better, although even then, 
it may be worth presenting this as a buffer-local-idle-timer API.



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Possible support for buffer local idle timers?
  2021-09-21  0:36       ` Campbell Barton
@ 2021-09-21  6:07         ` Eli Zaretskii
  2021-09-21  6:16           ` Lars Ingebrigtsen
  2021-09-21 10:19           ` Campbell Barton
  0 siblings, 2 replies; 10+ messages in thread
From: Eli Zaretskii @ 2021-09-21  6:07 UTC (permalink / raw)
  To: Campbell Barton; +Cc: emacs-devel

> Date: Tue, 21 Sep 2021 10:36:05 +1000
> From: Campbell Barton <ideasman42@gmail.com>
> 
> >    . does the timer start measuring idle time only when the buffer is
> >      the current buffer, or regardless of that?
> 
> would just go with default behavior of global idle timers since a user 
> switching buffers will typically reset idle timers.

What do you mean by "reset timers" here?

> >    . what to do when the timer expired but the buffer wasn't current,
> >      and then the buffer became current? does the callback gets called
> >      right away, or do we "miss" the timer in that case?
> 
> default behavior could be to run timers on the previously active buffer 
> (instead of missing them), although tracking this information could get 
> more involved.
> 
> It may be useful for callers to request not to run in the case the 
> buffer becomes inactive.

I don't understand what you mean by "acting on a buffer".  The timer
function can do anything it wants in any buffer.

> >    . how to handle repeated timers?
> 
> As far as I can see repeated timers would be the primary use-case, for 
> this feature.
> 
> I'm not sure what you mean by "how to handle", all callbacks registered 
> to run a repeated buffer local timer could share a timer, this would 
> store a list of callbacks which would run each time.

It's related to the second question: if timers don't fire when "their"
buffer is not the current buffer, then repeating timers will
accumulate.  Then it becomes an issue what to do when the buffer
eventually becomes the current one: do we run the timer function N
times or just once?

> It may be that all things considered - there are too many ambiguities 
> and corner cases for this to be implemented cleanly.

Indeed, I think the idea is not clear enough, and in general timers do
not interact cleanly with the notion of the current buffer, which in
Emacs is very ephemeral.  Emacs switches momentarily to other buffers,
including temporary buffers, a lot, so you could miss a timer tick
very easily due to that.

> Again, this is meant to be an alternative to packages registering their 
> global repeating idle timers that are never removed.

Never removed why? because of bugs?

> If there are better alternatives to this (such as starting/stopping 
> global-idle-timers on switching buffers, perhaps using: 
> window-state-change-hook, then that might be better, although even then, 
> it may be worth presenting this as a buffer-local-idle-timer API.

I find the notion of stopping a timer when Emacs switches buffers
strange.  A timer runs with the time, and time does not stop when you
switch buffers.  There's no such notion in Emacs as "buffer-local
time", and if there were, I wouldn't know how to explain its
semantics.  Why does it matter how long was a buffer the current
buffer?



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Possible support for buffer local idle timers?
  2021-09-21  6:07         ` Eli Zaretskii
@ 2021-09-21  6:16           ` Lars Ingebrigtsen
  2021-09-21 10:19           ` Campbell Barton
  1 sibling, 0 replies; 10+ messages in thread
From: Lars Ingebrigtsen @ 2021-09-21  6:16 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Campbell Barton, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> I don't understand what you mean by "acting on a buffer".  The timer
> function can do anything it wants in any buffer.

It is common to tie timers to buffers, though.  A common pattern in
timer functions is

(lambda ()
  (if (not (get-buffer buffer))
      (cancel-timer timer)
    (do-our-stuff...)))

and for idle timers

(lambda ()
  (when (eq (current-buffer) buffer)
    (do-our-stuff...)))

I think what's requested is just some syntactic sugar for these two
things.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Possible support for buffer local idle timers?
  2021-09-21  6:07         ` Eli Zaretskii
  2021-09-21  6:16           ` Lars Ingebrigtsen
@ 2021-09-21 10:19           ` Campbell Barton
  2021-09-21 10:43             ` Eli Zaretskii
  1 sibling, 1 reply; 10+ messages in thread
From: Campbell Barton @ 2021-09-21 10:19 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

On Tue, Sep 21, 2021 at 4:07 PM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > Date: Tue, 21 Sep 2021 10:36:05 +1000
> > From: Campbell Barton <ideasman42@gmail.com>
> >
> > >    . does the timer start measuring idle time only when the buffer is
> > >      the current buffer, or regardless of that?
> >
> > would just go with default behavior of global idle timers since a user
> > switching buffers will typically reset idle timers.
>
> What do you mean by "reset timers" here?

I assume that the the act of switching the buffer will cause Emacs not
to be idle, so after switching buffers idle timers will begin to run
again as their deadlines are met.
>
> > >    . what to do when the timer expired but the buffer wasn't current,
> > >      and then the buffer became current? does the callback gets called
> > >      right away, or do we "miss" the timer in that case?
> >
> > default behavior could be to run timers on the previously active buffer
> > (instead of missing them), although tracking this information could get
> > more involved.
> >
> > It may be useful for callers to request not to run in the case the
> > buffer becomes inactive.
>
> I don't understand what you mean by "acting on a buffer".  The timer
> function can do anything it wants in any buffer.

Yes, but as with buffer-local-variables and buffer-local-hooks it can
be useful to have buffer-local-timers (or have the functionality even
if they have to be implemented with global timers).

> > >    . how to handle repeated timers?
> >
> > As far as I can see repeated timers would be the primary use-case, for
> > this feature.
> >
> > I'm not sure what you mean by "how to handle", all callbacks registered
> > to run a repeated buffer local timer could share a timer, this would
> > store a list of callbacks which would run each time.
>
> It's related to the second question: if timers don't fire when "their"
> buffer is not the current buffer, then repeating timers will
> accumulate.  Then it becomes an issue what to do when the buffer
> eventually becomes the current one: do we run the timer function N
> times or just once?

I would assume just once, although if it's important that a timer runs
for a buffer - as opposed to the buffer loosing focus and not running
the timer at all, this could be performed.
>
> > It may be that all things considered - there are too many ambiguities
> > and corner cases for this to be implemented cleanly.
>
> Indeed, I think the idea is not clear enough, and in general timers do
> not interact cleanly with the notion of the current buffer, which in
> Emacs is very ephemeral.  Emacs switches momentarily to other buffers,
> including temporary buffers, a lot, so you could miss a timer tick
> very easily due to that.

It may be better to consider all visible buffers instead of the active buffer.
...

>
> > Again, this is meant to be an alternative to packages registering their
> > global repeating idle timers that are never removed.
>
> Never removed why? because of bugs?

Recently I referenced idle-highlight-mode, didn't write this package
so I can only guess,
I would assume this adds a global timer and never removes it's awkward
to know if the global timer is still needed by any open buffers. From
a quick check I suppose this could use buffer-list-update-hook,
although even in that case it's a little odd to be continuously
running a timer to highlight something for a buffer that might not be
visible.


> > If there are better alternatives to this (such as starting/stopping
> > global-idle-timers on switching buffers, perhaps using:
> > window-state-change-hook, then that might be better, although even then,
> > it may be worth presenting this as a buffer-local-idle-timer API.
>
> I find the notion of stopping a timer when Emacs switches buffers
> strange.  A timer runs with the time, and time does not stop when you
> switch buffers.  There's no such notion in Emacs as "buffer-local
> time", and if there were, I wouldn't know how to explain its
> semantics.  Why does it matter how long was a buffer the current
> buffer?

This is often used for calculating more computationally intensive
highlighting information that's associated with a buffer-local minor
mode.

--
- Campbell



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Possible support for buffer local idle timers?
  2021-09-21 10:19           ` Campbell Barton
@ 2021-09-21 10:43             ` Eli Zaretskii
  2021-09-25  7:26               ` Campbell Barton
  0 siblings, 1 reply; 10+ messages in thread
From: Eli Zaretskii @ 2021-09-21 10:43 UTC (permalink / raw)
  To: Campbell Barton; +Cc: emacs-devel

> From: Campbell Barton <ideasman42@gmail.com>
> Date: Tue, 21 Sep 2021 20:19:44 +1000
> Cc: emacs-devel@gnu.org
> 
> On Tue, Sep 21, 2021 at 4:07 PM Eli Zaretskii <eliz@gnu.org> wrote:
> >
> > > Date: Tue, 21 Sep 2021 10:36:05 +1000
> > > From: Campbell Barton <ideasman42@gmail.com>
> > >
> > > >    . does the timer start measuring idle time only when the buffer is
> > > >      the current buffer, or regardless of that?
> > >
> > > would just go with default behavior of global idle timers since a user
> > > switching buffers will typically reset idle timers.
> >
> > What do you mean by "reset timers" here?
> 
> I assume that the the act of switching the buffer will cause Emacs not
> to be idle, so after switching buffers idle timers will begin to run
> again as their deadlines are met.

No, that is not necessarily true.  "Idle" means there's no input for
Emacs to process, but it doesn't mean Emacs is not doing anything.  It
could run some timer-related code which switches buffers, for example.



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Possible support for buffer local idle timers?
  2021-09-21 10:43             ` Eli Zaretskii
@ 2021-09-25  7:26               ` Campbell Barton
  0 siblings, 0 replies; 10+ messages in thread
From: Campbell Barton @ 2021-09-25  7:26 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

On Tue, Sep 21, 2021 at 8:44 PM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > From: Campbell Barton <ideasman42@gmail.com>
> > Date: Tue, 21 Sep 2021 20:19:44 +1000
> > Cc: emacs-devel@gnu.org
> >
> > On Tue, Sep 21, 2021 at 4:07 PM Eli Zaretskii <eliz@gnu.org> wrote:
> > >
> > > > Date: Tue, 21 Sep 2021 10:36:05 +1000
> > > > From: Campbell Barton <ideasman42@gmail.com>
> > > >
> > > > >    . does the timer start measuring idle time only when the buffer is
> > > > >      the current buffer, or regardless of that?
> > > >
> > > > would just go with default behavior of global idle timers since a user
> > > > switching buffers will typically reset idle timers.
> > >
> > > What do you mean by "reset timers" here?
> >
> > I assume that the the act of switching the buffer will cause Emacs not
> > to be idle, so after switching buffers idle timers will begin to run
> > again as their deadlines are met.
>
> No, that is not necessarily true.  "Idle" means there's no input for
> Emacs to process, but it doesn't mean Emacs is not doing anything.  It
> could run some timer-related code which switches buffers, for example.

Thanks for the info, I have the impression that it would be difficult
to implement a buffer local timer that provides the kind of guarantees
script authors may expect from a built-in library.

Nevertheless I have found a solution for buffer-local-idle-timers,
where the global idle timer is created/destroyed as needed.

A summary of the solution:

- A buffer local window-state-change-hook is used to detect changes to
the buffer
  (enabling the global-idle-timer if the local mode is enabled).
- the global-idle-timer will remove itself if it runs on a buffer that
doesn't have the minor mode active.
- Buffers use a buffer local "dirty" tag so buffers that are activated
and lose focus before the idle timer runs
  will still be refreshed (if they are visible) next time the idle
timer is triggered.

This is a small package that demonstrates the solution:
https://emacs.stackexchange.com/a/68662/2418

-- 
- Campbell



^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2021-09-25  7:26 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-20 13:49 Possible support for buffer local idle timers? Campbell Barton
2021-09-20 15:30 ` Eli Zaretskii
2021-09-20 15:50   ` Campbell Barton
2021-09-20 16:08     ` Eli Zaretskii
2021-09-21  0:36       ` Campbell Barton
2021-09-21  6:07         ` Eli Zaretskii
2021-09-21  6:16           ` Lars Ingebrigtsen
2021-09-21 10:19           ` Campbell Barton
2021-09-21 10:43             ` Eli Zaretskii
2021-09-25  7:26               ` Campbell Barton

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).