You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2020/02/04 16:00:55 UTC

[groovy] 02/02: Fix occasional syntax highlighting issues of groovy console

This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 7a423de2dcc2ff96bb35606b1481ba6ed46258b8
Author: Daniel Sun <su...@apache.org>
AuthorDate: Tue Feb 4 23:08:24 2020 +0800

    Fix occasional syntax highlighting issues of groovy console
    
    For example:
    1) input the following code in groovy console
    ```
    def a =
    123
    ```
    2) select all text and press the <TAB> key
    3) `123` is not rendered properly
    
    (cherry picked from commit 00b46e9212e1dd2cedbe18ef4b20f41188f2cdda)
---
 .../console/ui/text/MatchingHighlighter.java       | 12 ++--
 .../console/ui/text/SmartDocumentFilter.java       | 78 +++++++++++++++++++---
 .../groovy/groovy/console/ui/text/TextEditor.java  | 40 +++++++++--
 3 files changed, 109 insertions(+), 21 deletions(-)

diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/MatchingHighlighter.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/MatchingHighlighter.java
index 95a594f..62f2b8c 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/MatchingHighlighter.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/MatchingHighlighter.java
@@ -144,11 +144,13 @@ public class MatchingHighlighter implements CaretListener {
 
                 stack.push(tuple(token, triggerFlag));
             } else if (tokenType == matchedTokenType) {
-                Tuple2<Token, Boolean> tokenAndTriggerFlagTuple = stack.pop();
-                if (tokenAndTriggerFlagTuple.getV2()) {
-                    triggerToken = tokenAndTriggerFlagTuple.getV1();
-                    matchedToken = token;
-                    break;
+                if (!stack.isEmpty()) {
+                    Tuple2<Token, Boolean> tokenAndTriggerFlagTuple = stack.pop();
+                    if (tokenAndTriggerFlagTuple.getV2()) {
+                        triggerToken = tokenAndTriggerFlagTuple.getV1();
+                        matchedToken = token;
+                        break;
+                    }
                 }
             }
         }
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java
index 5e49ca6..9b103e6 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java
@@ -18,6 +18,7 @@
  */
 package groovy.console.ui.text;
 
+import groovy.lang.Tuple2;
 import org.antlr.v4.runtime.CharStream;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
@@ -26,6 +27,8 @@ import org.antlr.v4.runtime.LexerNoViableAltException;
 import org.antlr.v4.runtime.Token;
 import org.apache.groovy.parser.antlr4.GroovyLangLexer;
 import org.apache.groovy.parser.antlr4.GroovySyntaxError;
+import org.apache.groovy.parser.antlr4.util.PositionConfigureUtils;
+import org.apache.groovy.parser.antlr4.util.StringUtils;
 
 import javax.swing.text.AttributeSet;
 import javax.swing.text.BadLocationException;
@@ -41,6 +44,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import static org.apache.groovy.parser.antlr4.GroovyLexer.ABSTRACT;
 import static org.apache.groovy.parser.antlr4.GroovyLexer.AS;
@@ -163,7 +167,7 @@ public class SmartDocumentFilter extends DocumentFilter {
 
     private String replaceMetaCharacters(String string) {
         // just in case remove carriage returns
-        string = string.replace("\\t", TAB_REPLACEMENT);
+        string = string.replace("\r\n", "\n");
         return string;
     }
 
@@ -192,7 +196,12 @@ public class SmartDocumentFilter extends DocumentFilter {
         }
 
         List<Token> tokenList = tokenStream.getTokens();
-        List<Token> tokenListToRender = findTokensToRender(tokenList);
+        List<Token> tokenListToRender;
+        try {
+            tokenListToRender = findTokensToRender(tokenList);
+        } finally {
+            this.setRenderRange(null);
+        }
 
         for (Token token : tokenListToRender) {
             int tokenType = token.getType();
@@ -228,18 +237,35 @@ public class SmartDocumentFilter extends DocumentFilter {
     }
 
     private List<Token> findTokensToRender(List<Token> tokenList) {
-        int tokenListSize = tokenList.size();
-        int latestTokenListSize = latestTokenList.size();
+        final Tuple2<Integer, Integer> renderRange = getRenderRange();
+        if (null != renderRange) {
+            long startLine = lineNumber(renderRange.getV1());
+            long stopLine = lineNumber(renderRange.getV2());
+
+            if (startLine < 0 || stopLine < 0) return tokenList; // should never happen
+
+            return tokenList.stream()
+                    .filter(e -> e.getLine() >= startLine && PositionConfigureUtils.endPosition(e).getV1() <= stopLine)
+                    .collect(Collectors.toList());
+        }
 
-        if (0 == tokenListSize || 0 == latestTokenListSize) {
+        List<Token> tmpLatestTokenList = filterNewlines(this.latestTokenList);
+        int latestTokenListSize = tmpLatestTokenList.size();
+        if (0 == latestTokenListSize) {
+            return tokenList;
+        }
+
+        List<Token> tmpTokenList = filterNewlines(tokenList);
+        int tokenListSize = tmpTokenList.size();
+        if (0 == tokenListSize) {
             return tokenList;
         }
 
         int startTokenIndex = 0;
         int minSize = Math.min(tokenListSize, latestTokenListSize);
         for (int i = 0; i < minSize; i++) {
-            Token token = tokenList.get(i);
-            Token latestToken = latestTokenList.get(i);
+            Token token = tmpTokenList.get(i);
+            Token latestToken = tmpLatestTokenList.get(i);
 
             if (token.getType() == latestToken.getType()
                     && token.getStartIndex() == latestToken.getStartIndex()
@@ -251,8 +277,8 @@ public class SmartDocumentFilter extends DocumentFilter {
             break;
         }
 
-        List<Token> newTokenList = new ArrayList<>(tokenList);
-        List<Token> newLatestTokenList = new ArrayList<>(latestTokenList);
+        List<Token> newTokenList = new ArrayList<>(tmpTokenList);
+        List<Token> newLatestTokenList = new ArrayList<>(tmpLatestTokenList);
 
         Collections.reverse(newTokenList);
         Collections.reverse(newLatestTokenList);
@@ -277,12 +303,36 @@ public class SmartDocumentFilter extends DocumentFilter {
         }
 
         if (startTokenIndex <= stopTokenIndex) {
-            return tokenList.subList(startTokenIndex, stopTokenIndex);
+            return tmpTokenList.subList(startTokenIndex, stopTokenIndex);
         }
 
         return tokenList; // should never reach here. If unexpected error occurred, it's better to render all tokens
     }
 
+    /**
+     * Returns line number starting with 1
+     */
+    private long lineNumber(int position) {
+        long result = -1;
+
+        if (position < 0) return result;
+
+        try {
+            String text = this.styledDocument.getText(0, position + 1);
+            result = StringUtils.countChar(text, '\n') + 1;
+        } catch (BadLocationException e) {
+            e.printStackTrace(System.err);
+        }
+
+        return result;
+    }
+
+    private List<Token> filterNewlines(List<Token> tokenList) {
+        return tokenList.stream()
+                .filter(e -> !(NL == e.getType() && e.getText().trim().isEmpty()))
+                .collect(Collectors.toList());
+    }
+
     private Style findStyleByTokenType(int tokenType) {
         Style style = styleContext.getStyle(String.valueOf(tokenType));
 
@@ -347,7 +397,7 @@ public class SmartDocumentFilter extends DocumentFilter {
 
     private volatile boolean latest = false;
     private volatile List<Token> latestTokenList = Collections.emptyList();
-    private static final String TAB_REPLACEMENT = "    ";
+    private volatile Tuple2<Integer, Integer> renderRange;
 
     public boolean isLatest() {
         return latest;
@@ -357,4 +407,10 @@ public class SmartDocumentFilter extends DocumentFilter {
         return latestTokenList;
     }
 
+    public void setRenderRange(Tuple2<Integer, Integer> renderRange) {
+        this.renderRange = renderRange;
+    }
+    public Tuple2<Integer, Integer> getRenderRange() {
+        return renderRange;
+    }
 }
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextEditor.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextEditor.java
index 5be4773..5c62211 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextEditor.java
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/TextEditor.java
@@ -18,17 +18,35 @@
  */
 package groovy.console.ui.text;
 
-import javax.swing.*;
+import groovy.lang.Tuple;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
+import javax.swing.JTextPane;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
 import javax.swing.plaf.ComponentUI;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.Caret;
 import javax.swing.text.DefaultCaret;
 import javax.swing.text.DefaultEditorKit;
+import javax.swing.text.DefaultStyledDocument;
 import javax.swing.text.Document;
+import javax.swing.text.DocumentFilter;
 import javax.swing.text.Element;
 import javax.swing.text.JTextComponent;
 import javax.swing.text.Utilities;
-import java.awt.*;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
@@ -416,9 +434,10 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
                             TAB_BACK_PATTERN.matcher(getSelectedText()).replaceAll("") :
                             getSelectedText().replaceAll("^\t", "");
 
+                    int stop = start + text.length();
+                    setRenderRange(start, stop);
                     TextEditor.this.replaceSelection(text);
-
-                    TextEditor.this.select(start, start + text.length());
+                    TextEditor.this.select(start, stop);
                 }
             } catch (Exception e) {
                 e.printStackTrace();
@@ -441,8 +460,11 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
 
                     String toReplace = TextEditor.this.getSelectedText();
                     toReplace = LINE_START.matcher(toReplace).replaceAll(text);
+
+                    int stop = start + toReplace.length();
+                    setRenderRange(start, stop);
                     TextEditor.this.replaceSelection(toReplace);
-                    TextEditor.this.select(start, start + toReplace.length());
+                    TextEditor.this.select(start, stop);
                 } else {
                     int pos = TextEditor.this.getCaretPosition();
                     doc.insertString(pos, text, null);
@@ -453,6 +475,14 @@ public class TextEditor extends JTextPane implements Pageable, Printable {
         }
     }
 
+    private void setRenderRange(int start, int stop) {
+        DocumentFilter documentFilter = ((DefaultStyledDocument) TextEditor.this.getDocument()).getDocumentFilter();
+        if (documentFilter instanceof SmartDocumentFilter) {
+            SmartDocumentFilter smartDocumentFilter = (SmartDocumentFilter) documentFilter;
+            smartDocumentFilter.setRenderRange(Tuple.tuple(start, stop));
+        }
+    }
+
     /**
      * Paint a horizontal line the width of a column and 1 pixel high
      */