bobbylight / RSyntaxTextArea

A syntax highlighting, code folding text editor for Java Swing applications.
BSD 3-Clause "New" or "Revised" License
1.12k stars 259 forks source link

Parser errors (squiggly underline) are always rendered on first line #451

Closed antimatter84 closed 2 years ago

antimatter84 commented 2 years ago

Description Errors (ParserNotice) on any line are always displayed with squiggly underline on the first line in the RSTA.

Steps to Reproduce Specific steps to reproduce the behavior:

  1. Run the example program below The default text contains 'spam' on line 2. The demo parser will detect the first 'spam' per line and create a ParserNotice with the exact line, offset and length of this 'spam' occurence.
  2. Notice how unrelated text in line 1 is squiggly underlined as error. The offset and length matches the correct token in the actual line containing the error.

Expected behavior An error in any line, from character position [a, b] will be rendered squiggly underlined in the correct line from [a, b].

Actual behavior An error in any line, from character position [a, b] will always be rendered squiggly underlined in line 1 from [a, b].

If the first line is not long enough, the squiggly underline will move into the next line. Example:

foo bar
moo cow spam

Here, 'moo' and the following blank will be squiggly underlined.

Screenshots 2022-06-21_11-40-20 2022-06-21_11-40-41

Java version Java 8u322

Additional context I do not know how or if the method DefaultParseResult::setParsedLines should be used after evaluating the RSyntaxDocument. However, it does not affect the outcome of my demo application in any observable way.

Code example

    import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
    import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
    import org.fife.ui.rsyntaxtextarea.parser.AbstractParser;
    import org.fife.ui.rsyntaxtextarea.parser.DefaultParseResult;
    import org.fife.ui.rsyntaxtextarea.parser.DefaultParserNotice;
    import org.fife.ui.rsyntaxtextarea.parser.ParseResult;
    import org.fife.ui.rtextarea.RTextScrollPane;

    import javax.swing.*;
    import javax.swing.text.BadLocationException;
    import java.awt.*;

    public class ErrorHighlightDemo extends JFrame
    {
        private static final String DEFAULT_TEXT = "The barn is on fire.\nCan spam help?";
        private RSyntaxTextArea textArea;
        private DemoParser parser = new DemoParser();

        public ErrorHighlightDemo()
        {
            textArea = new RSyntaxTextArea(6, 20);
            textArea.setText(DEFAULT_TEXT);
            textArea.addParser(parser);
            textArea.setParserDelay(10);
            RTextScrollPane scrollPane = new RTextScrollPane(textArea);

            JPanel panel = new JPanel(new BorderLayout());
            panel.add(scrollPane, BorderLayout.CENTER);
            setContentPane(panel);
            setTitle("Text Editor Demo");
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            pack();
            setLocationRelativeTo(null);
            setSize(300, 200);
        }

        public static void main(String[] args)
        {
            SwingUtilities.invokeLater(() -> {
                try
                {
                    new ErrorHighlightDemo().setVisible(true);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            });
        }

        static class DemoParser extends AbstractParser
        {
            private final static String ERROR = "spam";

            @Override
            public ParseResult parse(RSyntaxDocument doc, String style)
            {
                DefaultParseResult result = new DefaultParseResult(this);
                try
                {
                    String input = doc.getText(0, doc.getLength());
                    String[] lines = input.split("\\r?\\n");
                    result.setParsedLines(0, lines.length - 1);
                    for (int i = 0; i < lines.length; i++)
                    {
                        String line = lines[i];
                        int pos = line.indexOf(ERROR);
                        if (pos >= 0)
                        {
                            System.out.printf("'err' found on line: %d, char [%d, %d]%n", i + 1, pos, pos + ERROR.length() - 1);
                            DefaultParserNotice notice = new DefaultParserNotice(this, "error here", i + 1, pos, ERROR.length());
                            result.addNotice(notice);
                        }
                    }
                }
                catch (BadLocationException e)
                {
                    e.printStackTrace();
                }
                return result;
            }
        }
    }
siggemannen commented 2 years ago

This is cause DefaultParserNotice is constructed wrong, the offset should be from the whole string, but now it's a offset of the specific line where the error is. Probably simplest is to aggregate offsetted chars in the loop (not that this code is extremely untested):


                    String[] lines = input.split("\\n"); //simplify countin'
                    int agg = 0; //aggregated length
                    result.setParsedLines(0, lines.length - 1);
                    for (int i = 0; i < lines.length; i++)
                    {
                        String line = lines[i];
                        int pos = line.indexOf(ERROR);
                        if (pos >= 0)
                        {
                            System.out.printf("'err' found on line: %d, char [%d, %d]%n", i + 1, pos + agg, pos + agg + ERROR.length() - 1);
                            DefaultParserNotice notice = new DefaultParserNotice(this, "error here", i + 1, pos + agg, ERROR.length());
                            result.addNotice(notice);
                        }
                        agg += line.length() + 1; //Add newline and rest of the string length
                    }
bobbylight commented 2 years ago

Yeah, @siggemannen is right, the offset parameter is from the start of the document, not the start of the line. IIRC this is done because the line argument is required, but offsete and length are optional, to support parsers that only identify errors at the line level (I know Perl does this, at least it used to, as do some XML parsers). And I think the absolute offset was used to avoid having to calculate that value for e.g. rendering functions that need that value.

If you parse directly from an RSyntaxTextArea instance, the Document/Element API can be used to quickly and efficiently get these values.