change/update events the first time a line is drawn

Is there a way to get the change or update event to fire once the first time a line is displayed ?

Case in point, consider the indentwrap sample (http://codemirror.net/demo/indentwrap.html). All the work is done in renderLine however this seems very inefficient to me. renderLine is called in many cases where the line did not actually change (ie scrolling) whereas the operation we want to do (changing the textIndent and paddingLeft CSS) is only relevant when the text changes (that is a space in added in the beginning).

I tried to replace renderLine with the change event. The problem then is that because the event is not fired the first time the line is shown, the initial display of the editor will not have indentwrap.

Is there a better way to solve this ?

Thanks!

I don’t think you need to worry about the efficiency of using renderLine here. Doing it in a different way would require a lot of caching complexity (can I still use the data I computed before?) and the computations involved seem to be cheap compared to the cost of the DOM update anyway.

Thx marijn !

Yes indentwrap is very simple but I just used it as an example for my question. In my case the code in renderLine is much more expensive. Basically it’s a version of indentwrap supporting different font/sizes (see http://codepen.io/anon/pen/LVyXQQ). If you add a lot of lines there you’ll see how scrolling speed gets slow. Hence I’m looking for a better solution.

  • 'can I still use the data I computed before?'
    why not ? As far as I can tell indentwrap offset can only change in one of two cases - the line is changed (and a space was typed in the beginning) or the line is drawn for the first time (the editor was started with a given initial text)

  • 'the computations involved seem to be cheap compared to the cost of the DOM update anyway’
    yes but if I could move the code out of renderLine I’d prevent both the computations and the DOM line style update unless the line really changed

So what is needed is an event that fires both a line being changed (like change) and the first time the line is rendered, or another way of solving this efficiently …

Any help ?
Thanks!
Micha

You will have to keep your own cache, I’d say. Note that the editor line width can change when, for example, the line number gutter changes width (or the editor is resized, etc).

Hi Marjin,

I coming to this again with a more clear request that I think will be very useful in general.

Is it possible for you to implement an event similar to ‘beforeChange’ that fires before a line is rendered (that is renderLine is called) for the first time. (‘beforeFirstRender’ ?)

It is very useful to be able to do stuff before a line is drawn for the first time. For example it will allow indentwrap to be implemented much more cleanely without the renderLine hack.

Thank you!
Micha

I still feel the focus on ‘the first time a line is drawn’ is misguided. Most of the time when a line is drawn that is the first time it is drawn in that shape (most drawing happens in response to editing). If you’re scrolling around a lot you might end up drawing the same line a bunch of times, but ensuring that it is drawn with the exact same text, styling, and editor size is a nightmare (or a very very big cache), and not something I want to touch.

but ensuring that it is drawn with the exact same text, styling, and editor size is a nightmare …

I am not sure I follow you here. I mean for that event to fire once ever in the life of the line - before the first time it is rendered.

My point is that if you want to do something in the line before it is rendered every time it is changed you put it in beforeChange(), but you would often like that something to happen on all the lines also the first time so that the change is there when the text loads.

Thanks!
Micha

I still don’t get it. Since the line will have changed the next time it is rendered, what possible use could the information you gathered from the first time it was rendered be?

A good example is when you want to render the line differently if some condition applies based on it’s layout.

For example, supposed you want to highlight the whole line in blue if it’s matching a complicated regexp.

Naturally I want to put the code in beforeChange(). I do the regexp check and then add or remove the highlighting class on the line accordingly.

This will work perfectly when lines are edited but lines that match the regexp when the editor loaded will not be highlighted. For that I’ll need to do the regexp check also in a an event such as the one I’m suggesting, running before each line is rendered for the first time.

The other option is to do it all in renderLine, this would be an ugly hack for two reasons - first, renderLine breaks CM in many operations so if instead of the regexp I have something relying on for example charCoords() (like indentWrap with multi fonts) I cannot do it, and second, such regexp check is an expensive operation and it’s very inefficient to do it when the line did not change (ie just scrolled into view). In general I don’t want to use renderLine if possible.

Note that for something like indent wrap this assumes functions such as charCoords() will be available to this event as they are to beforeChange(). Basically it’s similar to having beforeChange() firing also before the line is first rendered.

Thanks!
Micha

Okay, now I think I see where you’re coming from. The way I’ve handled this (for example in the merge addon) is to listen for viewportChange events and keep information about the ‘clean’ (updated) part of the document. That way, whenever the viewport changes, you ensure that your clean view covers it. This has the advantage that you can let things go out of sync again once they are scrolled out of view, even if they were previously visible.

Thanks marijn

I’m not sure I’m following. I tried using viewportChange but it does not fire when lines are loaded the first time. So even if considering the example I gave, I put the regexp check+add class there, lines which match the regexp when the editor is loaded will not be highlighted.

Thanks!
Micha

You mean you are finding it difficult to run code when you initialize the editor? How so? If this is a CM addon, just use the defineOption callback for initialization.

ok I can’t seem to explain myself. I’ll try again

Supposed I have a renderFirstLine event. I could simply do something like:

function highlightIfMatch(cm, lineNo) {
    var lineText=cm.getLine(lineNo);
    var matching= // check if lineText matches regExp
    if (matching) cm.doc.addLineClass(lineNo, highlighted); else cm.doc.removeLineClass(lineNo, highlighted); // highlight/unhighlight
}

editor.on("beforeFirstRender", function(cm, lineNo) {
   highlightIfMatch(cm, lineNo);
});
editor.on("beforeChange", function(cm, changeObj) {
   highlightIfMatch(cm, changeObj.from.line);
});

This will be a clean simple and efficient way to get the matching lines highlighted when they first appears and as the user edits the document.

What are my other options ? As I wrote before renderLine is not a good place. Perhaps I’m missing the obvious ?

I hope this is clearer.

Thanks!
Micha

Except that it doesn’t work – changes can insert multiple lines.

But yes, you can create this effect with viewportChanged events by keeping a little state (the region you’ve matched). I’m not going to walk you through it, and it is a little more code than the example you showed. In any case, I am not going to add the event you described for the reasons I described earlier.

Thx marijn

As far as I can see viewportChanged is not relevant here as it does not fire before lines are displayed for the first time.

It seems CM does not have a way to change the state (ie class) of a line so that it is effective the first time the line is rendered/displayed (that is without using a renderLine hack).

This has been a long thread and I understand you don’t want to spend more time on it.

Thanks!
Micha