Format the text inside a Spark RichEditableText control

Posted in Best practices, Flex on August 26th, 2010 by bogdan – 5 Comments Tags: , , , , , , , , , , ,

Lately I’ve been working on a code editor, based on Flex 4.1, that should be similar to a modern
editor with the so needed syntax highlighting feature. In order to highlight reserved words, strings
and comments, the text is transformed into tokens, which contain useful information for coloring
such as its position in text, length and the type, used to decide which color should be used to
highlight the token.

Everything went well until formatting RichEditableText to highlight the tokens, which was tricky.
My first implementation was something like the code below :

	for (var i:int = 0; i < tokens.length; i++)
	{
		var tlf:TextLayoutFormat = new TextLayoutFormat();
		tlf.color = tokens[i].color;
		richEditableText.setFormatOfRange(tlf,
			tokens[i].beginIndex, tokens[i].endIndex);
	}

This implementation was way too slow: if I pasted a text with around 125 of words to be colored, it would take up to 19 seconds. That was unbelievable, so I started searching about the performance of setFormatOfRange method. By reading some forums and the source code for RichEditableText class, I noticed that this method triggers update on all the elements of textFlow, which happened for each of those 125 words and dropped so much the speed of formatting and displaying the text.

So to solve this issue, all the format operations should be wrapped into a single one to be executed, and when this is done, trigger the update only once.

Fortunately, the Text Layout Framework provides an API for doing just this through flashx.textLayout.operations package. So I wrapped each format operation into an ApplyFormatOperation, which was added to a CompositeOperation object and executed the latter one.
Below is a sample implementation for my improved solution :

	var compositeFormatOperation:CompositeOperation =
		new CompositeOperation();
	for (var i:int = 0; i < tokens.length; i++)
	{
		var operationState:SelectionState =
			new SelectionState(richEditableText.textFlow,
				tokens[i].beginIndex, tokens[i].endIndex);
		var leafFormat:TextLayoutFormat = new TextLayoutFormat();
		leafFormat.color = tokens[i].color;
		var formatOperation:ApplyFormatOperation =
			new ApplyFormatOperation(operationState,leafFormat,null);
		compositeFormatOperation.addOperation(formatOperation);
	}
	var success:Boolean = compositeFormatOperation.doOperation();
	if (success)
	{
		richEditableText.textFlow.flowComposer.updateAllControllers();
	}

Notice that after the composite operation is executed, I update the textFlow only once.
This improved solution dropped the time from 19 seconds to 0.3 seconds, an 63x improvement that makes
my code editor usable again.

I didn’t use an EditManager because it dispatches TextOperationEvent.CHANGE, which I listen only for text changes (insert, paste, cut, delete) on my RichEditableText. So if text changed, I would set color on some tokens by calling applyFormat on EditManager, which would send a new TextOperationEvent.CHANGE and I would ended up in a infinite loop.

I’m sure there might be better ways of doing this (I just haven’t discovered yet), so I’m open to new suggestions and I hope this post would serve as a starting point for others having similar task with the new text controls inside Flex Spark framework.

For more information on Text Layout Framework you may have a look at this PDF file

5 Comments on “ Format the text inside a Spark RichEditableText control ”

  • jason
    September 7th, 2010 10:14 pm

    Thanks for this – one question – how would you go about backing off these changes?

    I tried putting this into a class where I maintain the state of compositeFormatOperation – so I can loop through its operations array and cast each one to an ApplyFormatOperation and then call undo() on this.

    This works for the most part but strangely – every now and then it won’t undo my formatting.

    I can’t discern what is going on when it doesn’t undo and am not sure if there is a better way to revert a TextFlow back to before my changes.

    Any ideas are appreciated.

  • bogdan
    September 8th, 2010 2:03 pm

    try calling updateAllControllers on textFlow.flowComposer after each undo is performed

  • jason
    September 9th, 2010 5:31 pm

    thanks bogdan – I tried adding that but still occasionally one of the undo operations fails…

    I also tried calling canUndo on the operation first but even the ones that don’t undo properly still return true for canUndo…

    basically loop looks like

    for (var i:int = 0;i<compositeFormatOperation.operations.length;i++)
    {
    var afo:ApplyFormatOperation = compositeFormatOperation.operations[i] as ApplyFormatOperation;
    if (afo)
    {
    if (afo.canUndo())
    {
    afo.undo();
    flow.flowComposer.updateAllControllers();
    }
    }
    }
    compositeFormatOperation.operations = [];

  • bogdan
    September 10th, 2010 3:09 pm

    I think there’s more that should be done when applying operations on a text flow. It seems that when you call undo, textFlow.generation and operation.endGeneration should match, so there may be times when they don’t match and the undo operation is not executed. I’ll try to explore this issues in the following days and I’ll let you know if I got this solved.

  • Johnny
    September 24th, 2010 7:16 pm

    Great post! Maybe you could do a follow up on this topic??

    Johnny

Leave a Reply