javaparsingoptimizationjtextpanedefaultstyleddocument

Java code optimization - how can I optimize this remove() function?


I am making a custom language as a project for a class called Compilers. The whole project is in Java, using JFlex as my lexical analyzer, and Cup as my syntactic analyzer.

I created a simple text editor for the language, which basically consists of a JTextPane, where the user can type the custom code that will be parsed. This JTextPane has a DefaultStyledDocument, used to set character attributes, e.g. change the color for keywords, comments, strings, numbers, etc., for the code (text) inside the JTextPane.

Here is the code I am using:

        DefaultStyledDocument doc = new DefaultStyledDocument() {
        @Override
        public void insertString(int offset, String str, AttributeSet a) throws BadLocationException { //cuando se insertan caracteres.
            super.insertString(offset, str, a);
            String text = getText(0, getLength());
            syntax = new SyntaxHighlighter(new java.io.StringReader(text));
            Token val;
            try {
                while ((val = syntax.yylex()) != null) {
                    switch (val.type) {
                        case TokenType.KEYWORD:
                            setCharacterAttributes(val.start, val.length, keyword, true);
                            break;
                        case TokenType.COMMENT:
                            setCharacterAttributes(val.start, val.length, comment, true);
                            break;
                        case TokenType.STRING:
                            setCharacterAttributes(val.start, val.length, string, true);
                            break;
                        case TokenType.FUNCTION:
                            setCharacterAttributes(val.start, val.length, function, true);
                            break;
                        case TokenType.NUMBER:
                            setCharacterAttributes(val.start, val.length, plain, true);
                            break;
                        case TokenType.OPERATOR:
                            setCharacterAttributes(val.start, val.length, operator, true);
                            break;
                        case TokenType.READ:
                            setCharacterAttributes(val.start, val.length, number, true);
                            break;
                        default:
                            setCharacterAttributes(val.start, val.length, plain, true);
                            break;
                    }
                }
            } catch (IOException ex) {
                JOptionPane.showMessageDialog(rootPane, "Oops! Exception triggered\n" + ex.getMessage());
            }
        }

        @Override
        //this is the method I want to optimize
        public void remove(int offs, int len) throws BadLocationException { 
            super.remove(offs, len);
            String text = getText(0, getLength());
            syntax = new SyntaxHighlighter(new java.io.StringReader(text));
            Token val;
            try {
                while ((val = syntax.yylex()) != null) {
                    switch (val.type) {
                        case TokenType.KEYWORD:
                            setCharacterAttributes(val.start, val.length, keyword, true);
                            break;
                        case TokenType.COMMENT:
                            setCharacterAttributes(val.start, val.length, comment, true);
                            break;
                        case TokenType.STRING:
                            setCharacterAttributes(val.start, val.length, string, true);
                            break;
                        case TokenType.FUNCTION:
                            setCharacterAttributes(val.start, val.length, function, true);
                            break;
                        case TokenType.NUMBER:
                            setCharacterAttributes(val.start, val.length, plain, true);
                            break;
                        case TokenType.OPERATOR:
                            setCharacterAttributes(val.start, val.length, operator, true);
                            break;
                        case TokenType.READ:
                            setCharacterAttributes(val.start, val.length, number, true);
                            break;
                        default:
                            setCharacterAttributes(val.start, val.length, plain, true);
                            break;
                    }
                }
            } catch (IOException ex) {
                JOptionPane.showMessageDialog(rootPane, "Oops! Exception triggered\n" + ex.getMessage());
            }
        }
    };

this.codeTextPane.setStyledDocument(doc);

The SyntaxHighlighter class is basically a lexer (made with JFlex) used only as a way to search specific pieces of text (keywords, strings, etc). Everything works perfectly, but...

The problem:

When the JTextPane has a decent amount of text in it, holding down the backspace key to remove text makes the program lag pretty hard. The reason I think this happens is probably because the SyntaxHighlighter runs with every character being removed, because holding down the backspace key calls the remove() function for each character being deleted. Inserting text is not a problem really because you can either load the code from a file (where the whole text in that file will be analyzed by the SyntaxHighlighter as a whole), or you just can't type fast enough to notice the lag.

Is there a way I can optimize this? Thank you all!


Solution

  • All in all this code seems to be structured and clear I understood it very fast. So my suggestion: leave it as it is as long as possible (forcing open-closed principle).

    The only change I would suggest is to separate character removal from highlighting. This was already mentioned. But the reason why you should do this is: You will be able to delay syntax highlighting until the user has remove a chunk of characters. So syntax highlighting will not be trigerred every time you remove only one char.

    I think you should break apart the main text into syntactical units and then apply the syntax highlighting only on changed syntactical units. The main problem is the parsing and the identification of the changed unit.

    As a previous author mentioned isolating syntax highlighting into a separated thread will improve performance as well.

    These changes are not trivial but possible.