Call Later in Dogelog Notebooks

## Introduction
Dogelog Player is a Prolog system mostly written in Prolog itself. It is available for the JavaScript, Python and Java platform. In the browser environment it offers Notebooks that run without server roundtrip.

![4832.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416192/ea8e7052-5585-4e32-8e76-d9a27b571deb.gif)

Compared to for example SWISH or Tinker from SWI-Prolog, the syntax coloring is in its early stage. Notebook cells were only colored during instrumentation. Things have a little improved with delayed re-coloring now.

## Stackless Coroutines
Dogelog Player provides stackfull and stackless coroutines via the abstraction of tasks and timers. So far the APIs for these concepts had only experimental and internal status with a `os_` prefix. We decided to make them first class citizens. Concerning stackless coroutines there was only one predicate:

- **call_later(G, D):**
  The predicate succeeds. As a side effect it schedules the goal G to be executed after D milliseconds

We the upcoming release 1.3.3. the end-user will have access to:

- **call_later(G, D, T):**
  The ternary predicate succceeds in T with the scheduled timer
- **timer_cancel(T):**
  The predicate succeeds. As a side effect it cancels the timer T

It should be noted that timers are realized as stackless coroutines, they hook into the stack of the task that created the timer. In as far to simplify the realization of our cooperative multitasking we have restricted their functionality. Basically they run protected from yielding.

## React Library
Although timers can extend their limited functionality via creating their own tasks, they have already found a couple of use cases in their limited form. One realization that we would like to mention is our take the predicate `call_with_time_limit/2`.
```prolog 
call_with_time_limit(Delay, Goal) :-
   current_task(Task),
   setup_once_cleanup(
      call_later(sys_timeout_signal(Task), Delay, Timer),
      Goal,
      timer_cancel(Timer)).
```
The predicate is usually realized in a multi-threaded Prolog system by having an alert thread that tears down the watched thread. The above shows how the same can be elegantly implemented with cooperative multitasking when the watched task yields enough.

Further applications of stackless coroutines are found in our `library(react)`. The library allows to attach a stackless coroutine to a DOM element as an event handler. This corresponds to `addEventListener()` where the listener is not async:

- **tag_bind(T, P, G):**
  The predicate succeeds. As a side effect it adds a type T stackless
  event handler with formal event parameter P and callback goal G to the cursor

A further extension is:

- **tag_bind(T, P, G, L):**
  The quaternary predicate allows specifying event options

Among the event options we have `block(true)`, which then realizes the waitForInput() pattern based on `addEventListener()` and `removeEventListener()`. In our variant the stackless coroutine is not called strickly once, since it acts as a filter via its Prolog failure and success result.

## Debounced Recoloring
To implement recoloring of a Notebook cell, we found it useful to resort to another event handling pattern that didn't make it into our event options lists. It can be easily realized by storing a timer in a DOM element (sic!). The Prolog code reads as follows. The instrumentation is the moment we add the event handler:
```prolog
instrument_elem(E) :-
   colorize_elem(E),
   ir_cell_bind(E, 'input', _, notify_elem(E), 0).
```
If the event is triggered before the delay we cancel the old timer:
```prolog
notify_elem(E) :-
   ir_object_current(E, is_dirty, T), !,
   timer_cancel(T),
   call_later(mirror_elem(E), 1000, S),
   ir_object_set(E, is_dirty, S).
notify_elem(E) :-
   call_later(mirror_elem(E), 1000, S),
   ir_object_set(E, is_dirty, S).
```
Recoloring removes the current time which has now fired:
```prolog
mirror_elem(E) :-
   ir_object_reset(E, is_dirty),
   colorize_elem(E).
```
The above event handling pattern is called debouncing, and the term is derived from the debouncing of electronic contacts such as in computer keyboards. In electronics this can be done by various methods such as wetted contacts, filters and Schmitt triggers. The term made it now into the Mozilla Glossary [here](https://developer.mozilla.org/en-US/docs/Glossary/Debounce).

## User Experience
We opted for a 1000 ms delay since IntelliJ IDE seems to use this delay sometimes. It doesn't put a lot of computational load on the front end. We combined it with the attribute value `plaintext-only` for the attribute name `contenteditable` of a DOM element. Since we use a full recoloring not much goes wrong:

![Aufzeichnung 2025-06-07 113817.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416192/948e408c-2efb-497a-ad82-6010c9242371.gif)

Here is an example from SWI Tinker where recoloring gives a hickup:

![Aufzeichnung 2025-06-07 112633.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416192/941e5cad-bda9-4b24-b945-344d05572d01.gif)

In general recoloring requires a more carefully crafted coloring library. The requirement changes from coloring valid tokens to coloring potentially invalid and incomplete tokens. We went through a couple of iterations of our `library(fancy)` to make it fit for the new requirement.

## Conclusions
To implement recoloring of a Notebook cell, we found it useful to resort to the debouncing event handling pattern. It can be easily realized by storing a timer in a DOM element (sic!). Although recoloring requires coloring potentially invalid and incomplete tokens, since we use a full cell recoloring, not much goes wrong.