Help: Why does assigning StyledText to a TextArea so slow?

I’m trying to write a Markdown editor based on a TextArea subclass. Periodically a thread runs that parses the contents of the TextArea into a Markdown document (my own MarkdownKit module). This is fast and not a bottleneck. I then have a renderer that creates a new StyledText object, sets its text to the Markdown string in the TextArea and applies various styles to runs of characters. This too is pretty fast.

Where I’m seeing a massive slow down is in assigning the TextArea.StyledText property to my newly created StyledText object. It takes many seconds (sometimes up to a minute) to do the assignment.

I’ve verified this by stripping out all of my parsing and rendering code.

You can see this by creating a new window with a TextArea on it. Paste lots of text into the TextArea (feel free to use this Markdown string). You can almost instantaneously change the colour of the text via the StyledText property of the TextArea like this:

MyTextArea.StyledText.TextColor(0, MyTextArea.Value.Length) = Color.Red

You can create a separate StyledText object, assign it the same text and change its colour rapidly too but when you try to assign this new object to the TextArea.StyledText property it takes an obscene amount of time:

Var st As New StyledText
st.Text = someLongString
st.TextColor(0, st.Text.Length) = Color.Red
MyTextArea.StyledText = st // This is unbearably slow

Am I doing something wrong here? Is there anyway to speed this up. @Christian_Schmitz can your plugin help here?

You should read xDev - was in my Tips&Tricks column :wink:

Public Sub BeginFastEditing(Extends TA As TextArea)
  
  #If TargetCocoa Then
    
    Dim docView As Integer = TA.DocumentView
    ' Dim storage As Integer = TA.TextStorage( docView )
    Dim storage As Integer = TextStorage( docView )
    
    Declare Sub beginEditing Lib "AppKit" Selector "beginEditing" ( obj As Integer )
    beginEditing( storage )
    
  #EndIf
End Sub

and

Public Sub EndFastEditing(Extends TA As TextArea)
  
  #If TargetCocoa Then
    
    Dim docView As Integer = TA.DocumentView
    ' Dim storage As Integer = TA.TextStorage( docView )
    Dim storage As Integer = TextStorage( docView )
    
    Declare Sub endEditing Lib "AppKit" Selector "endEditing" ( obj As Integer )
    endEditing( storage )
    
  #Endif
End Sub

The display updates when you end the fast editing mode

MyTextArea.BeginFastEditing
MyTextArea.StyledText = st // This is no longer unbearably slow
MyTextArea.EndFastEditing
2 Likes

P.S. It’s based on a blog post by Greg, so why Xojo haven’t used this for TextArea behind the scenes is a mystery to me …

1 Like

Looks promising @Markus_Winter but is there code missing from your example or am I being daft? There isn’t a TextArea.DocumentView property or a global TextStorage method…

OK so I think the blog post @Markus_Winter was talking about was this one by Joe Ranieri.

Unfortunately placing the assignment between the BeginEditing and EndEditing extensions doesn’t speed things up at all:

Var st As New StyledText
st.Text = TextArea1.Value
st.TextColor(0, st.Text.Length) = Color.Red // This manipulation is fast.
Dim docView As Integer = TextArea1.DocumentView
Dim storage As Integer = TextArea1.TextStorage(docView)
TextArea1.BeginEditing(storage)
TextArea1.StyledText = st
TextArea1.EndEditing(storage)

This is so frustrating. I found an old post by @Christian_Schmitz so I tried using his MBS plugin but it won’t compile because it doesn’t look like you can directly assign StyledText objects…

Var st As New StyledText
st.Text = TextArea1.Value
st.TextColor(0, st.Text.Length) = Color.Red // This manipulation is fast.

If TargetMacOS Then
  TextArea1.NSTextViewMBS.textStorage.setAttributedString(st) // Error.
#EndIf

Can anyone help? It seems crazy that I can parse 30K characters with my Markdown parser in 100 ms but can’t colour a block of text in under a minute.

??? What are you doing there?

Should be:

TextArea1.BeginFastEditing
Var st As New StyledText
st.Text = TextArea1.Value
st.TextColor(0, st.Text.Length) = Color.Red // This manipulation is fast.
TextArea1.StyledText = st
TextArea1.EndFastEditing

DocumentView and Storage are dealt with inside the extension method.

From my “Making Better Examples” series (xDev 14.2 p33):

I’m always on the lookout for nice tips for the Tips & Tricks column, so from time to time I go over all the latest Xojo related blog posts that I know of. So on the Xojo blog I came across a tip from Joe Ranieri on how to speed up text modifications on OS X ( http://blog.xojo.com/blog/speeding_up_textarea_modifications_under_cocoa_reprint ).

It is a good tip, and we had already covered it in a previous issue (see xDev 12.4, Tips & Tricks, Tip 3). It is also a repost of a tip on the old REALsoftware blog from 2012 ( http://www.realsoftwareblog.com/2012/11/speeding-up-textarea-modifications.html ).

On reading it again and wondering why I don’t use it much, I realized something that annoys me about so many tips and examples.

(Now let’s be clear: being annoyed can be a good thing. Because it tells you there is something that can be improved upon and made to be less annoying – you just have to figure out what it really is that annoys you.)

At first I thought it was the following sentence, which I already moaned about it in 2012 when I pointed out the following as not being beginner-friendly:

If you find you are going to use these Declares often, you might want to add them to a module as Extension methods so that you can call them more easily, which I’ll leave as an exercise for the reader.

Now extensions are easy, I grant you that. But so is separating eggs (see Figure 1). Or braising beef. Or doing a restriction digest or running a PCR.

But keep in mind: Many things are only easy if you already know what they are and/or how to do them . For example, I had to look up what braising actually means as I was not familiar with the term.

The same can be said for beginners and extension methods. Yes, that’s generally what people call learning, but bear with me. Because that’s not what caused my annoyance.

Joe obviously took my complaint about being nice to beginners to heart as the new Xojo blog post contains an example project, and in it he includes the extension methods he alluded to in his original post on the REALsoftware blog.

But look at the final code for using the extension methods in Figure 2. Can you spot in the screenshot what annoyed me and caused my epiphany?

Go on, have a close look. And no peeking at the text further down.

No? You don’t find anything annoying about the code in this tip? Nothing you would want to change or improve?

If I should summarize in one word what annoys me here then I would say it’s the usability of the tip. For one thing it is the Dim statements. Am I really supposed to remember to type

Dim docView As Integer = ExampleTextArea.DocumentView
Dim storage As Integer = ExampleTextArea.TextStorage(docView)

every time I want to use fast editing? I do have a bad memory, so while I might remember the existence of a fast edit mode (and auto-complete really comes in handy here), I would have to look up the code each time (and most likely copy/paste it). Furthermore, the dim statement requires the passing of a parameter (storage) to the extension method. Is this really necessary? And finally BeginEditing is far too non-descript for my liking as it doesn’t convey the purpose of the extension method. Why not BeginFastEditing instead?

Wouldn’t this speed-up tip be so much easier to use, not to mention be more concise and elegant, if we could simply write:

ExampleTextArea.Text = ""
ExampleTextArea.BeginFastEditing
FillTextArea("Lorem ipsum ")
ExampleTextArea.EndFastEditing

Of course it would. And it’s easy to do too. All you have to do is move the Dim statements into the extension methods, rename them, and remove the storage parameter from the extension method declarations. So this

Sub BeginEditing(Extends ta As TextArea, storage As Integer)
  #If TargetMacOS Then
    Declare Sub beginEditing Lib "AppKit" Selector "beginEditing" ( obj As Integer )
    beginEditing(storage)
  #Endif
End Sub

becomes this:

Sub BeginFastEditing(Extends ta As TextArea)
  #If TargetMacOS Then
    Dim docView As Integer = ta.DocumentView
    Dim storage As Integer = ta.TextStorage( docView )
    Declare Sub beginEditing Lib "AppKit" Selector "beginEditing" ( obj As Integer )
    beginEditing( storage )
  #Endif
End Sub

and

Sub EndEditing(Extends ta As TextArea, storage As Integer)
  #If TargetMacOS Then
    Declare Sub endEditing Lib "AppKit" Selector "endEditing" ( obj As Integer )
    endEditing( storage )
  #Endif
End Sub

becomes

Sub EndFastEditing(Extends ta As TextArea)
  #If TargetMacOS Then
    Dim docView As Integer = ta.DocumentView
    Dim storage As Integer = ta.TextStorage( docView )
    Declare Sub endEditing Lib "AppKit" Selector "endEditing" ( obj As Integer )
    endEditing( storage )
  #Endif
End Sub

Note to beginners : since this is an extension to a TextArea, you do not need to pass the TextArea as a parameter as you would with a normal method. The extension method already specifies which TextArea to use: the one ( ta ) calling the extension.

But does moving the Dim statements into the extension method not impact negatively on the speed of the extension method? In my tests I called both versions of the extension method 5,000 times but saw no significant difference between them. But feel free to play around with the little test app I made (see Figure 3) – you can also enable/disable pragmas, or use styled text, and see what a difference that makes.

Here’s a simple binary project: https://www.dropbox.com/s/ad7ofye8clmp518/StyledText%20Performance.xojo_binary_project?dl=0

Here’s the code in the button’s Action event:

Dim docView As Integer = TextArea1.DocumentView
Dim storage As Integer = TextArea1.TextStorage(docView)
TextArea1.BeginEditing(storage)
Var st As New StyledText
st.Text = TextArea1.Value
st.TextColor(0, st.Text.Length) = Color.Red // This manipulation is not fast for me.
TextArea1.StyledText = st
TextArea1.EndEditing(storage)

I used Joe’s original Extensions module for the example but that’s not the issue.

Using a 250 line piece of sample text, it takes 8 seconds on an iMac Pro to assign the StyledText to the TextArea after clicking the button. If you omit the assignment (but manipulate the colour of the StyledText object) it’s instantaneous.

Nothing at your link.

Oops - forgot two extension methods :flushed: :flushed:

Public Function DocumentView(Extends ta As TextArea) as Integer
  #If TargetMacOS Then
    Declare Function documentView Lib "AppKit" Selector "documentView" ( obj As Integer ) As Integer
    Return documentView( ta.Handle )
  #Endif
End Function

andf

Public Function TextStorage(Extends ta As TextArea, docView As Integer) as Integer
  #If TargetMacOS Then
    Declare Function textStorage Lib "AppKit" Selector "textStorage" ( obj As Integer ) As Integer
    Return textStorage( docView )
  #Endif
End Function

Here is my test project (appending a styled text to a TextArea 5000 times): https://www.dropbox.com/s/e05t9gtg842f5hz/FastCocoaTextAreaUpdates%204.xojo_binary_project.zip?dl=0

Takes 8.5 sec without FastEditing, 1.2 sec with

Edited the link to my test project: https://www.dropbox.com/s/ad7ofye8clmp518/StyledText%20Performance.xojo_binary_project?dl=0

Your test project is missing a SpeedTest file @Markus_Winter.

Try again. Made the SpeedTest module internal but forgot to save, so compressed the old version :flushed: :flushed: :flushed:

So need :coffee: :coffee: :coffee:

404 at that link buddy.

Dropbox might need a bit to catch up, but I now also put it in my public DropBox folder

I hope that 404 Bird isn’t hurt to bad :bird:

Oh, I see the assignment slows it down massively. I use .append but if I do

ExampleTextArea.BeginFastEditing

Dim count As Integer
count = st.StyleRunCount  //get the number of StyleRuns

Dim st1 As New StyledText

For i As Integer = 0 To NumberOfRepeats
  
  For j As Integer = 0 To count-1  //loop through them
    
    st1.AppendStyleRun st.StyleRun( j )
    
  Next
  
Next

ExampleTextArea.StyledText = st1

ExampleTextArea.EndFastEditing

instead of

Dim count As Integer
count = st.StyleRunCount  //get the number of StyleRuns


For i As Integer = 0 To NumberOfRepeats
  
  For j As Integer = 0 To count-1  //loop through them
    
    ExampleTextArea.StyledText.AppendStyleRun st.StyleRun( j )
    
  Next
  
Next

then I loose all the speedup - :poop: :scream:

Bingo. Smells like a bug with Xojo?

Oh that bug has been known for a looooong time. The assignment gets geometrically worse the longer your styled text is …

<https://xojo.com/issue/25533>

Why that was ever considered “fixed” is a mystery …

Can you iterate over the style runs and use .append?

:sob:

I cannot believe I am going to have to write my own canvas-based text area just to get around this. I shouldn’t have to do this. I did toy with using the FormattedTextControl but it doesn’t support emoji.

Is there a feasible workaround to this issue @Greg_O_Lone or @William_Yu? I know @William_Yu has been messing around with the TextArea during the current testing cycle as he fixed an emoji bug in the TextArea (much appreciated).

What is so frustrating is that I can do all the required styling on a StyledText object very quickly. Where the bottleneck is happening is switching the StyledText property of the TextArea with my newly created object. This strikes me as something that should be much faster.

TextInputCanvas :stuck_out_tongue:
Its just a pile of work to get the basics going

Maybe these could help?

nope …