RSyntaxTextArea icon indicating copy to clipboard operation
RSyntaxTextArea copied to clipboard

Editor hangs if it contains line with 3 million and more symbols

Open EkaterinaSorokina opened this issue 9 years ago • 6 comments

In my case editor has a long line containing 3 million or more characters without any blank or line feed. When I scroll to the end of the line and then try to set a cursor, the editor hangs.

EkaterinaSorokina avatar Dec 18 '15 12:12 EkaterinaSorokina

RSTA is just thinking; presumably in a very slow computation of where to place the cursor by summing the widths of all tokens on the very long line (e.g. java.awt.FontMetrics.stringWidth() multiple times).

One possible fix I've thought about for a long time, but never implemented, was the following (and is basically a workaround);

If a single font is used for all token types (bold or italic does not matter), and that font is
monospaced, manually compute token widths via (charCount * charWidth).

I'm hesitant to do this, primarily because I don't like special-case code paths - extra testing, and more code means more bugs

bobbylight avatar Jan 09 '16 19:01 bobbylight

Hi , We would be very interested by this optimization on JMeter project as per our previous opened issues:

  • https://github.com/bobbylight/RSyntaxTextArea/issues/55
  • https://github.com/bobbylight/RSyntaxTextArea/issues/41

We have currently opened bugs related to this issue.

thanks for taking into account this if possible ans thanks for your work on this nice library.

Regards

pmouawad avatar Jan 17 '16 19:01 pmouawad

Hello @bobbylight , Any chance to have this one fixed ? This bug is really nasty for us (JMeter) as it frequently happens that JSON requests contains text without spaces, whenever this happens, we end up having a hugely slow user experience.

I know you're working on your own time and helping open source, we cannot request anything but if you have time to fix it, it is really an important problem for us and probably for others. If you can give some guidance on how to fix it, I could try to investigate but I am not a swing expert as you.

Thank you Regards

pmouawad avatar Dec 20 '16 16:12 pmouawad

For recap the stacktrace shows this (maybe there is a workaround to avoid this and lose some feature instead) :

"AWT-EventQueue-0" #20 prio=6 os_prio=31 tid=0x00007fa8ab2a8800 nid=0xf907 runnable [0x0000700002202000] java.lang.Thread.State: RUNNABLE at org.fife.ui.rsyntaxtextarea.TokenUtils.getSubTokenList(TokenUtils.java:125) at org.fife.ui.rsyntaxtextarea.WrappedSyntaxView$WrappedLine.modelToView(WrappedSyntaxView.java:1280) at org.fife.ui.rsyntaxtextarea.WrappedSyntaxView.modelToView(WrappedSyntaxView.java:751) at org.fife.ui.rsyntaxtextarea.WrappedSyntaxView.modelToView(WrappedSyntaxView.java:824) at org.fife.ui.rtextarea.ChangeableHighlightPainter.paintLayer(ChangeableHighlightPainter.java:338) at javax.swing.text.DefaultHighlighter$LayeredHighlightInfo.paintLayeredHighlights(DefaultHighlighter.java:574) at javax.swing.text.DefaultHighlighter.paintLayeredHighlights(DefaultHighlighter.java:308) at org.fife.ui.rtextarea.RTextAreaHighlighter.paintLayeredHighlights(RTextAreaHighlighter.java:172) at org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaHighlighter.paintLayeredHighlights(RSyntaxTextAreaHighlighter.java:240) at org.fife.ui.rsyntaxtextarea.WrappedSyntaxView.drawViewWithSelection(WrappedSyntaxView.java:334) at org.fife.ui.rsyntaxtextarea.WrappedSyntaxView.paint(WrappedSyntaxView.java:922) at javax.swing.plaf.basic.BasicTextUI$RootView.paint(BasicTextUI.java:1434) at javax.swing.plaf.basic.BasicTextUI.paintSafely(BasicTextUI.java:737) at org.fife.ui.rtextarea.RTextAreaUI.paintSafely(RTextAreaUI.java:539) at javax.swing.plaf.basic.BasicTextUI.paint(BasicTextUI.java:881) at javax.swing.plaf.basic.BasicTextUI.update(BasicTextUI.java:860) at org.fife.ui.rtextarea.RTextAreaBase.paintComponent(RTextAreaBase.java:735) at org.fife.ui.rsyntaxtextarea.RSyntaxTextArea.paintComponent(RSyntaxTextArea.java:2072) at javax.swing.JComponent.paint(JComponent.java:1056) at javax.swing.JComponent.paintToOffscreen(JComponent.java:5210) at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1579) at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1502) at javax.swing.RepaintManager.paint(RepaintManager.java:1272) at javax.swing.JComponent._paintImmediately(JComponent.java:5158) at javax.swing.JComponent.paintImmediately(JComponent.java:4969) at javax.swing.RepaintManager$4.run(RepaintManager.java:831) at javax.swing.RepaintManager$4.run(RepaintManager.java:814) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80) at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:814) at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:789) at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:738) at javax.swing.RepaintManager.access$1200(RepaintManager.java:64) at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1732) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756) at java.awt.EventQueue.access$500(EventQueue.java:97) at java.awt.EventQueue$3.run(EventQueue.java:709) at java.awt.EventQueue$3.run(EventQueue.java:703) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80) at java.awt.EventQueue.dispatchEvent(EventQueue.java:726) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93) at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

pmouawad avatar Dec 20 '16 16:12 pmouawad

Swing expert, perhaps in a past life :)

From the stack trace, I see you have line wrapping enabled. Is this ticket different in any way from #41?

I'm sure you won't have better luck with line wrap disabled, but I can't think of any good workaround. Preprocess the input, and if it's > some length with no newlines (100k chars?), either truncate or replace the middle of the content with something like "..."? I know this isn't ideal since you're losing data, but I'm just brainstorming ideas you can implement without changes in RSTA.

Pull requests are welcome. The word wrapped view code is quite touchy and would need unit tests built before a major refactoring. The only solution would likely be caching more data (line break positions) or the faster codepath for monospaced fonts I mentioned above (though this will likely help less than my former suggestion).

bobbylight avatar Dec 31 '16 05:12 bobbylight

I took a look at the code that does the non-wrapping version in TokenImpl#getListOffset The problem is that it's using some sort of quadratic algo to calculate the width of the string until a given "view" cursor position due to the fact that it loops the token text one character at a time but still calculate the whole length of text from start to index.

I'm thinking a better way would be to do following:

  1. Wrap very long lines in a special token type as to avoid different font styles (i've seen sublime do this). This makes it easier to calculate later
  2. Find all tabs in a long token string. This is needed since tabs has a magic "width". Alternatively, always use space when tabs occur in long tokens.
  3. Each tab will be a "boundary" of a text segment. Use FontMetrics to calculate whole width of segment before tab ONCE. If the view position occurs within this segment width, then you split this segment in half and calculate length of left side etc. This way, we have a "binary" search and a bit cheaper implementation than a whole loop one (probably?!). If you're unlucky, there will be a lot of metrics ran, but i think it will still be much less than the quadratic version.
  4. Some heurestics could be use to "guess" a position by just measuring width of one char and check where it could likely reside. Since it's more likely cursor will be at the first segment we could avoid doing a lot of work.

I might code this up myself but yeah, it's a bit of work :D I haven't looked at how the wrapped version does it, but suspect it's similar?

siggemannen avatar Apr 08 '21 00:04 siggemannen