Creating a Decoration that updates on an interval

I’m creating a Decoration that displays the time elapsed since a timestamp which is written in the text of the document. I need for that elapsed time to update every second, so that it is a “live” timer.

Is there a recommended way to notify the Decoration/Widget to update itself on a schedule? I assume I’d do something like start a setInterval in the constructor of the widget, but I don’t believe there is a method to call which updates the view, nor a way to remove the interval when the view is no longer used. Can I set up this interval elsewhere?

If the line has the text “10:00:00am” and the current time is 10:05:30am then the Decoration would read “00:05:30”

Decorations aren’t stateful and don’t do imperative things. You’ll want the interval to be managed by a view plugin, which just replaces the decoration with a new one. If you really need to keep the existing DOM of your widget and just update it, you can give your widget type an updateDOM method that takes a different widget instance and updates the DOM representation.

Thanks, I understand, that works.

Can I use this technique with gutters? That is, can a view plugin provide gutters and/or update/replace gutters?

If not, I can stick to using a decoration for my use case.

Yes, you can do something similar with gutter markers.

The thing that has me stuck with doing that, is that the ViewPlugin’s PluginSpec object can provide a decorations function which receives an instance of the plugin, from which it can read a stored list of decorations off the instance, as the zebra stripes example does.

But to provide a gutter, I think I need to use the provide function on the PluginSpec object, which receives the plugin class definition, not an instance, and so I can’t store the updated gutter there.

Is there some other way to access/replace the gutters from the view plugin?

It gets the plugin itself, not the class. You can pass that to view.plugin to get its value. The idea would be to make your gutter’s marker function read from the plugin (or a state field, if that works better).

Ah right. But I don’t think I have a reference to view at that point?

My code is something like this:

const myPlugin = ViewPlugin.fromClass(MyPluginClass, {
  provide: (plugin) => {
    // how do I get a reference to view
    // So I can use view.plugin on the next line
    const value = view.plugin(plugin)
    return value.myGutter

Thank you for the help

Oh, I think I understand now that you are saying the gutter itself should find the view and the plugin. Perhaps I can change the gutter into a function that I pass the plugin to, which returns the gutter, something like:

const myGutter = (plugin) => gutter({lineMarker: (view, line) =>
  // here I have access to plugin and view

Right, your plugin’s provide option can create a gutter that has access to the plugin, in order to read marks from it.

I’m now able to create a view plugin which stores a list of markers, and a gutter which reads its markers from that view plugin, and the markers update themselves whenever there is a document update.

Where I’m stuck is how to force the gutters to re-render every second. Should I be forcing a document update every second? I tried doing that with an empty update view.update([]) which doesn’t trigger an update of the gutters.

Can you recommend a way to make the gutter to refresh its markers?

I realized that view.dispatch() will refresh the gutters. I put that on an interval in my ViewPlugin and now my gutters update on an interval as I had hoped.

I assume this is not the correct way to do it and that it updates more than is necessary.

But it feels like an amazing victory at this moment as I’ve spent far too long on this for today :slight_smile:

My gutter markers have a click event listener, but because the markers are replaced frequently, the element can change out from under the mouse between when it is pressed down and released.

Is there any way around that?