You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by lk...@apache.org on 2022/10/12 18:01:03 UTC

[netbeans] branch master updated: Improve support for ANTLRv4 Grammars (#4773)

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

lkishalmi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 463fb27299 Improve support for ANTLRv4 Grammars (#4773)
463fb27299 is described below

commit 463fb27299708621ee904e99eb247f33d2aca9a2
Author: Laszlo Kishalmi <la...@gmail.com>
AuthorDate: Wed Oct 12 11:00:57 2022 -0700

    Improve support for ANTLRv4 Grammars (#4773)
    
    * Split references and occurrences
    
    * Support mode and channel references
    
    * Better code completion on ANTLRv4 grammars
    
    * Fixed fragment detection and some other stuff
---
 .../modules/refactoring/spi/ui/AccessorImpl.java   |   4 +-
 java/languages.antlr/nbproject/project.xml         |  32 +++
 .../languages/antlr/AntlrDeclarationFinder.java    |   8 +-
 ...ncesFinder.java => AntlrOccurrencesFinder.java} |  16 +-
 .../modules/languages/antlr/AntlrParser.java       |   6 +-
 .../modules/languages/antlr/AntlrParserResult.java |  33 ++-
 .../languages/antlr/AntlrStructureItem.java        |  10 +-
 .../languages/antlr/AntlrTokenSequence.java        | 172 +++++++++++++
 .../languages/antlr/refactoring/Refactoring.java   |   2 +-
 .../Antlr3CompletionProvider.java}                 |  70 ++----
 .../modules/languages/antlr/v3/Antlr3Language.java |   4 +-
 .../languages/antlr/v3/Antlr3ParserResult.java     |  24 +-
 .../antlr/v4/Antlr4CompletionProvider.java         | 273 +++++++++++++++++++++
 .../modules/languages/antlr/v4/Antlr4Language.java |   2 +-
 .../languages/antlr/v4/Antlr4ParserResult.java     |  71 ++++--
 .../languages/antlr/AntlrTokenSequenceTest.java    | 171 +++++++++++++
 16 files changed, 788 insertions(+), 110 deletions(-)

diff --git a/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/ui/AccessorImpl.java b/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/ui/AccessorImpl.java
index e012b8ef0c..5c4ff90ca2 100644
--- a/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/ui/AccessorImpl.java
+++ b/ide/refactoring.api/src/org/netbeans/modules/refactoring/spi/ui/AccessorImpl.java
@@ -28,7 +28,9 @@ final class AccessorImpl extends SPIUIAccessor {
 
     @Override
     public void reset(FiltersDescription desc) {
-        desc.reset();
+        if (desc != null) {
+            desc.reset();
+        }
     }
     
 }
diff --git a/java/languages.antlr/nbproject/project.xml b/java/languages.antlr/nbproject/project.xml
index 0088942f64..98efb939e6 100644
--- a/java/languages.antlr/nbproject/project.xml
+++ b/java/languages.antlr/nbproject/project.xml
@@ -25,6 +25,15 @@
         <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
             <code-name-base>org.netbeans.modules.languages.antlr</code-name-base>
             <module-dependencies>
+                <dependency>
+                    <code-name-base>org.netbeans.api.annotations.common</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.46</specification-version>
+                    </run-dependency>
+                </dependency>
                 <dependency>
                     <code-name-base>org.netbeans.core.multiview</code-name-base>
                     <build-prerequisite/>
@@ -86,6 +95,15 @@
                         <specification-version>1.26</specification-version>
                     </run-dependency>
                 </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>2</release-version>
+                        <specification-version>1.61</specification-version>
+                    </run-dependency>
+                </dependency>
                 <dependency>
                     <code-name-base>org.netbeans.modules.editor.lib2</code-name-base>
                     <build-prerequisite/>
@@ -212,6 +230,20 @@
                     </run-dependency>
                 </dependency>
             </module-dependencies>
+            <test-dependencies>
+                <test-type>
+                    <name>unit</name>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.libs.junit4</code-name-base>
+                        <compile-dependency/>
+                    </test-dependency>
+                    <test-dependency>
+                        <code-name-base>org.netbeans.modules.nbjunit</code-name-base>
+                        <recursive/>
+                        <compile-dependency/>
+                    </test-dependency>
+                </test-type>
+            </test-dependencies>
             <public-packages/>
         </data>
     </configuration>
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrDeclarationFinder.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrDeclarationFinder.java
index 1fa4169c42..f03c81fb9a 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrDeclarationFinder.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrDeclarationFinder.java
@@ -85,12 +85,12 @@ public class AntlrDeclarationFinder implements DeclarationFinder {
         }
         scannedFiles.add(fo);
 
-        AntlrParserResult result = AntlrParser.getParserResult(fo);
+        AntlrParserResult<?> result = AntlrParser.getParserResult(fo);
 
-        Reference ref = ((Map<String, Reference>) result.references).get(name);
+        Reference ref = result.references.get(name);
 
-        if(ref != null && ref.defOffset != null) {
-            AntlrStructureItem asi = new AntlrStructureItem.RuleStructureItem(name, fo, ref.defOffset.getStart(), ref.defOffset.getEnd());
+        if(ref != null && ref.defOffset != OffsetRange.NONE) {
+            AntlrStructureItem asi = new AntlrStructureItem.RuleStructureItem(name, false, fo, ref.defOffset.getStart(), ref.defOffset.getEnd());
             DeclarationLocation dln = new DeclarationFinder.DeclarationLocation(fo, ref.defOffset.getStart(), asi);
             if (resultDL == DeclarationLocation.NONE) {
                 resultDL = dln;
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrOccurancesFinder.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrOccurrencesFinder.java
similarity index 81%
rename from java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrOccurancesFinder.java
rename to java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrOccurrencesFinder.java
index c9ac3e0560..7b4f35b835 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrOccurancesFinder.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrOccurrencesFinder.java
@@ -19,6 +19,7 @@
 package org.netbeans.modules.languages.antlr;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import org.netbeans.api.lexer.Token;
 import org.netbeans.api.lexer.TokenHierarchy;
@@ -33,7 +34,7 @@ import org.netbeans.modules.parsing.spi.SchedulerEvent;
  *
  * @author lkishalmi
  */
-public class AntlrOccurancesFinder extends OccurrencesFinder<AntlrParserResult> {
+public class AntlrOccurrencesFinder extends OccurrencesFinder<AntlrParserResult> {
 
     private int caretPosition;
     private boolean cancelled;
@@ -82,7 +83,7 @@ public class AntlrOccurancesFinder extends OccurrencesFinder<AntlrParserResult>
         return false;
     }
 
-    private void computeOccurrences(AntlrParserResult result) {
+    private void computeOccurrences(AntlrParserResult<?> result) {
         TokenHierarchy<?> tokenHierarchy = result.getSnapshot().getTokenHierarchy();
         TokenSequence<?> ts = tokenHierarchy.tokenSequence();
         ts.move(caretPosition);
@@ -91,15 +92,8 @@ public class AntlrOccurancesFinder extends OccurrencesFinder<AntlrParserResult>
             Token<?> token = ts.token();
             if (token.id() == AntlrTokenId.RULE || token.id() == AntlrTokenId.TOKEN) {
                 String refName = String.valueOf(token.text());
-                Map<String, AntlrParserResult.Reference> refs = result.references;
-                AntlrParserResult.Reference ref = refs.get(refName);
-                if (ref != null) {
-                    if(ref.defOffset != null) {
-                        occurrences.put(ref.defOffset, ColoringAttributes.MARK_OCCURRENCES);
-                    }
-                    for (OffsetRange occurance : ref.occurances) {
-                        occurrences.put(occurance, ColoringAttributes.MARK_OCCURRENCES);
-                    }
+                for (OffsetRange occurance : result.getOccurrences(refName)) {
+                    occurrences.put(occurance, ColoringAttributes.MARK_OCCURRENCES);
                 }
             }
         }
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrParser.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrParser.java
index b81ba9e72f..3502a62119 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrParser.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrParser.java
@@ -82,8 +82,8 @@ public abstract class AntlrParser extends org.netbeans.modules.parsing.spi.Parse
         }
     }
 
-    public static AntlrParserResult getParserResult(FileObject fo) {
-        AntlrParserResult result = null;
+    public static AntlrParserResult<?> getParserResult(FileObject fo) {
+        AntlrParserResult<?> result = null;
         java.lang.ref.Reference<AntlrParserResult> ceReference;
         synchronized (CACHE) {
             ceReference = CACHE.get(fo);
@@ -94,7 +94,7 @@ public abstract class AntlrParser extends org.netbeans.modules.parsing.spi.Parse
 
         if (result == null) {
             try {
-                AntlrParserResult[] parserResult = new AntlrParserResult[1];
+                AntlrParserResult<?>[] parserResult = new AntlrParserResult<?>[1];
                 ParserManager.parse(Collections.singleton(Source.create(fo)), new UserTask() {
                     @Override
                     public void run(ResultIterator resultIterator) throws Exception {
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrParserResult.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrParserResult.java
index f903eeb4f9..eaaae99e85 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrParserResult.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrParserResult.java
@@ -19,6 +19,7 @@
 package org.netbeans.modules.languages.antlr;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -45,14 +46,20 @@ public abstract class AntlrParserResult<T extends Parser> extends ParserResult {
 
     public final List<DefaultError> errors = new ArrayList<>();
     public final Map<String, Reference> references = new TreeMap<>();
+    public final Map<String, List<OffsetRange>> occurrences = new HashMap<>();
 
     public final List<OffsetRange> folds = new ArrayList<>();
     public final List<AntlrStructureItem> structure = new ArrayList<>();
 
     volatile boolean finished = false;
 
+    public static final Reference EOF = new Reference("EOF", OffsetRange.NONE); //NOI18N
+    
     public AntlrParserResult(Snapshot snapshot) {
         super(snapshot);
+        
+        references.put(EOF.name, EOF);
+        
     }
 
     public AntlrParserResult get() {
@@ -63,12 +70,12 @@ public abstract class AntlrParserResult<T extends Parser> extends ParserResult {
             parser.addParseListener(createFoldListener());
             parser.addParseListener(createReferenceListener());
             parser.addParseListener(createImportListener());
-            parser.addParseListener(createStructureListener());
-            parser.addParseListener(createOccurancesListener());
             evaluateParser(parser);
 
             // Start a second parsing phase for checking;
             parser = createParser(getSnapshot());
+            parser.addParseListener(createStructureListener());
+            parser.addParseListener(createOccurancesListener());
             parser.addParseListener(createCheckReferences());
             evaluateParser(parser);
             finished = true;
@@ -93,13 +100,10 @@ public abstract class AntlrParserResult<T extends Parser> extends ParserResult {
 
     public static class Reference {
         public final String name;
-        public FileObject source;
-        public OffsetRange defOffset;
-        public final List<OffsetRange> occurances = new ArrayList<>();
+        public final OffsetRange defOffset;
 
-        public Reference(String name, FileObject source, OffsetRange defOffset) {
+        public Reference(String name, OffsetRange defOffset) {
             this.name = name;
-            this.source = source;
             this.defOffset = defOffset;
         }
     }
@@ -107,6 +111,21 @@ public abstract class AntlrParserResult<T extends Parser> extends ParserResult {
     protected final FileObject getFileObject() {
         return getSnapshot().getSource().getFileObject();
     }
+    
+    public final List<? extends OffsetRange> getOccurrences(String refName) {
+        ArrayList<OffsetRange> ret = new ArrayList<>();
+        if (references.containsKey(refName)) {
+            ret.add(references.get(refName).defOffset);
+        }
+        if (occurrences.containsKey(refName)) {
+            ret.addAll(occurrences.get(refName));
+        }
+        return ret;
+    } 
+    
+    protected final void markOccurrence(String refName, OffsetRange or) {
+        occurrences.computeIfAbsent(refName, s -> new ArrayList<>()).add(or);
+    }
 
     protected abstract T createParser(Snapshot snapshot);
     protected abstract void evaluateParser(T parser);
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrStructureItem.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrStructureItem.java
index e357e2d630..57bf8f4aff 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrStructureItem.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrStructureItem.java
@@ -148,8 +148,10 @@ public abstract class AntlrStructureItem implements ElementHandle, StructureItem
 
     public static final class RuleStructureItem extends AntlrStructureItem {
 
-        public RuleStructureItem(String name, FileObject source, int startOffset, int stopOffset) {
+        final boolean fragment;
+        public RuleStructureItem(String name, boolean fragment, FileObject source, int startOffset, int stopOffset) {
             super(name, source, startOffset, stopOffset);
+            this.fragment = fragment;
         }
 
         @Override
@@ -164,7 +166,11 @@ public abstract class AntlrStructureItem implements ElementHandle, StructureItem
 
         @Override
         public ElementKind getKind() {
-            return Character.isUpperCase(name.charAt(0)) ? ElementKind.FIELD : ElementKind.RULE;
+            if (fragment) {
+                return ElementKind.CONSTANT;
+            } else {
+                return Character.isUpperCase(name.charAt(0)) ? ElementKind.FIELD : ElementKind.RULE;
+            }
         }
     }
 }
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrTokenSequence.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrTokenSequence.java
new file mode 100644
index 0000000000..785f1ea6ff
--- /dev/null
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrTokenSequence.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.languages.antlr;
+
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.util.Optional;
+import java.util.function.Predicate;
+import org.antlr.v4.runtime.Lexer;
+import static org.antlr.v4.runtime.Recognizer.EOF;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenSource;
+
+/**
+ *
+ * @author lkishalmi
+ */
+public final class AntlrTokenSequence {
+
+    private final TokenSource tokens;
+    private boolean eofRead = false;
+    private int readIndex;
+    private int cursorOffset;
+    private ListIterator<Token> cursor;
+    
+    private final ArrayList<Token> tokenList = new ArrayList<>(200);
+
+    public static final Predicate<Token> DEFAULT_CHANNEL = new ChannelFilter(Lexer.DEFAULT_TOKEN_CHANNEL);
+    
+    public static final class ChannelFilter implements Predicate<Token> {
+        private final int channel;
+
+        public ChannelFilter(int channel) {
+            this.channel = channel;
+        }
+
+        @Override
+        public boolean test(Token t) {
+            return channel == t.getChannel();
+        }
+    }
+    
+    public AntlrTokenSequence(TokenSource tokens) {
+        this.tokens = tokens;
+        this.cursor = tokenList.listIterator();
+    }
+    
+    public void seekTo(int offset) {
+        if (offset > readIndex) {
+            if (cursor.hasNext()) {
+                //replace the cursor if it is not at the end of the list.
+                cursor = tokenList.listIterator(tokenList.size());
+            }
+            Token t = read();
+            while ((t != null) && (t.getStopIndex() + 1 < offset)) {
+                t = read();
+            }
+            if (t != null && (offset < t.getStopIndex() + 1)) {
+                cursor.previous();
+                cursorOffset = t.getStartIndex();
+            } else {
+                cursorOffset = readIndex;
+            }
+        } else {
+            if (offset > getOffset()) {
+                next((t) -> t.getStopIndex() > offset);
+                if (cursor.hasPrevious()) {
+                    cursor.previous();
+                }
+                
+            } else {
+                previous((t) -> t.getStartIndex() > offset);
+            }
+        }
+        
+    }
+    
+    public boolean isEmpty() {
+        return tokenList.isEmpty() && !hasNext();
+    }
+    
+    public boolean hasNext() {
+        if (!eofRead && (cursorOffset == readIndex) && !cursor.hasNext()) {
+            Token t = read();
+            if (t != null) {
+                cursor.previous();
+            }
+        }
+        return !(eofRead && !cursor.hasNext());
+    }
+    
+    public boolean hasPrevious() {
+        return cursor.hasPrevious();
+    }
+    
+    public int getOffset() {
+        return cursorOffset;
+    }
+    
+    public Optional<Token> previous() {
+        Optional<Token> ret = cursor.hasPrevious() ? Optional.of(cursor.previous()) : Optional.empty();
+        cursorOffset = cursor.hasPrevious() ? ret.get().getStartIndex() : 0;
+        return ret;
+        
+    }
+    
+    public Optional<Token> previous(Predicate<Token> filter) {
+        Optional<Token> ot = previous();
+        while (ot.isPresent() && !filter.test(ot.get())) {
+            ot = previous();
+        }
+        return ot;
+    }
+    
+    public Optional<Token> previous(int tokenType){
+        return previous((Token t) -> t.getType() == tokenType);
+    }
+    
+    public Optional<Token> next() {
+        if (hasNext()) {
+            Token t = cursor.next();
+            cursorOffset = t.getStopIndex() + 1;
+            return Optional.of(t);
+        } else {
+            return Optional.empty();
+        }
+    }
+    
+    public Optional<Token> next(Predicate<Token> filter) {
+        Optional<Token> ot = next();
+        while (ot.isPresent() && !filter.test(ot.get())) {
+            ot = next();
+        }
+        return ot;
+    }
+    
+    public Optional<Token> next(int tokenType){
+        return next((Token t) -> t.getType() == tokenType);
+    }
+
+    private Token read() {
+        if (eofRead) {
+            return null;
+        }
+        Token t = tokens.nextToken();
+        if (t.getType() != EOF) {
+            cursor.add(t);
+            readIndex = t.getStopIndex() + 1;
+            return t;
+        } else {
+            eofRead = true;
+            return null;
+        }
+    }
+    
+}
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/refactoring/Refactoring.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/refactoring/Refactoring.java
index 747feaea47..027ec05012 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/refactoring/Refactoring.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/refactoring/Refactoring.java
@@ -148,8 +148,8 @@ public class Refactoring {
                         if(ref.defOffset != null) {
                             ranges.add(ref.defOffset);
                         }
-                        ranges.addAll(ref.occurances);
                     }
+                    ranges.addAll(result.getOccurrences(name));
 
                     for(OffsetRange or : ranges) {
                         PositionBounds bounds;
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrCompletionProvider.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3CompletionProvider.java
similarity index 63%
rename from java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrCompletionProvider.java
rename to java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3CompletionProvider.java
index 6f12999ff9..ffa79594b7 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/AntlrCompletionProvider.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3CompletionProvider.java
@@ -16,11 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.netbeans.modules.languages.antlr;
+package org.netbeans.modules.languages.antlr.v3;
 
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 import java.util.prefs.Preferences;
 import javax.swing.text.Document;
 import javax.swing.text.JTextComponent;
@@ -28,14 +26,13 @@ import org.netbeans.api.editor.document.EditorDocumentUtils;
 import org.netbeans.api.editor.mimelookup.MimeLookup;
 import org.netbeans.api.editor.mimelookup.MimePath;
 import org.netbeans.api.editor.mimelookup.MimeRegistration;
-import org.netbeans.api.editor.mimelookup.MimeRegistrations;
 import org.netbeans.api.editor.settings.SimpleValueNames;
 import org.netbeans.api.lexer.Token;
 import org.netbeans.api.lexer.TokenHierarchy;
 import org.netbeans.api.lexer.TokenSequence;
-import org.netbeans.modules.languages.antlr.v3.Antlr3Language;
-import org.netbeans.modules.languages.antlr.v4.Antlr4Language;
-import org.netbeans.modules.languages.antlr.v4.Antlr4ParserResult;
+import org.netbeans.modules.languages.antlr.AntlrParser;
+import org.netbeans.modules.languages.antlr.AntlrParserResult;
+import org.netbeans.modules.languages.antlr.AntlrTokenId;
 import org.netbeans.spi.editor.completion.CompletionItem;
 import org.netbeans.spi.editor.completion.CompletionProvider;
 import org.netbeans.spi.editor.completion.CompletionResultSet;
@@ -49,11 +46,8 @@ import org.openide.filesystems.FileObject;
  *
  * @author Laszlo Kishalmi
  */
-@MimeRegistrations({
-    @MimeRegistration(mimeType = Antlr3Language.MIME_TYPE, service = CompletionProvider.class),
-    @MimeRegistration(mimeType = Antlr4Language.MIME_TYPE, service = CompletionProvider.class),
-})
-public class AntlrCompletionProvider implements CompletionProvider {
+@MimeRegistration(mimeType = Antlr3Language.MIME_TYPE, service = CompletionProvider.class)
+public class Antlr3CompletionProvider implements CompletionProvider {
 
     @Override
     public CompletionTask createTask(int queryType, JTextComponent component) {
@@ -106,45 +100,29 @@ public class AntlrCompletionProvider implements CompletionProvider {
                     return;
                 }
 
-                Set<FileObject> scannedFiles = new HashSet<>();
-                addReferencesForFile(fo, isCaseSensitive, caretOffset - prefix.length(), prefix, resultSet, scannedFiles);
+                String mprefix = isCaseSensitive ? prefix : prefix.toUpperCase();
+
+                AntlrParserResult result = AntlrParser.getParserResult(fo);
+                Map<String, AntlrParserResult.Reference> refs = result.references;
+
+                int startOffset = caretOffset - prefix.length();
+                for (String ref : refs.keySet()) {
+                    String mref = isCaseSensitive ? ref : ref.toUpperCase();
+                    boolean match = mref.startsWith(mprefix);
+                    if (match) {
+                        CompletionItem item = CompletionUtilities.newCompletionItemBuilder(ref)
+                                .startOffset(startOffset)
+                                .leftHtmlText(ref)
+                                .sortText(ref)
+                                .build();
+                        resultSet.addItem(item);
+                    }
+                }
 
             } finally {
                 resultSet.finish();
             }
         }
 
-        public void addReferencesForFile(FileObject fo, boolean isCaseSensitive, int removeLength, String prefix, CompletionResultSet resultSet, Set<FileObject> scannedFiles) {
-            if(scannedFiles.contains(fo)) {
-                return;
-            }
-            scannedFiles.add(fo);
-
-            String mprefix = isCaseSensitive ? prefix : prefix.toUpperCase();
-
-            AntlrParserResult result = AntlrParser.getParserResult(fo);
-            Map<String, AntlrParserResult.Reference> refs = result.references;
-            for (String ref : refs.keySet()) {
-                String mref = isCaseSensitive ? ref : ref.toUpperCase();
-                boolean match = mref.startsWith(mprefix);
-                if (match) {
-                    CompletionItem item = CompletionUtilities.newCompletionItemBuilder(ref)
-                            .startOffset(removeLength)
-                            .leftHtmlText(ref)
-                            .sortText(ref)
-                            .build();
-                    resultSet.addItem(item);
-                }
-            }
-
-            if(result instanceof Antlr4ParserResult) {
-                for(String s: ((Antlr4ParserResult) result).getImports()) {
-                    FileObject importedFo = fo.getParent().getFileObject(s, "g4");
-                    if(importedFo != null) {
-                        addReferencesForFile(importedFo, isCaseSensitive, removeLength, prefix, resultSet, scannedFiles);
-                    }
-                }
-            }
-        }
     }
 }
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3Language.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3Language.java
index 419e91c11c..296990aded 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3Language.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3Language.java
@@ -27,7 +27,7 @@ import org.netbeans.modules.csl.api.StructureScanner;
 import org.netbeans.modules.csl.spi.DefaultLanguageConfig;
 import org.netbeans.modules.csl.spi.LanguageRegistration;
 import org.netbeans.modules.languages.antlr.AntlrDeclarationFinder;
-import org.netbeans.modules.languages.antlr.AntlrOccurancesFinder;
+import org.netbeans.modules.languages.antlr.AntlrOccurrencesFinder;
 import org.netbeans.modules.languages.antlr.AntlrParser;
 import org.netbeans.modules.languages.antlr.AntlrParserResult;
 import org.netbeans.modules.languages.antlr.AntlrStructureScanner;
@@ -172,7 +172,7 @@ public final class Antlr3Language extends DefaultLanguageConfig {
 
     @Override
     public OccurrencesFinder getOccurrencesFinder() {
-        return new AntlrOccurancesFinder();
+        return new AntlrOccurrencesFinder();
     }
 
     @Override
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3ParserResult.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3ParserResult.java
index 84c7583fd3..74b325bef8 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3ParserResult.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v3/Antlr3ParserResult.java
@@ -19,6 +19,7 @@
 package org.netbeans.modules.languages.antlr.v3;
 
 import java.util.function.Consumer;
+import java.util.logging.Logger;
 import org.antlr.parser.antlr3.ANTLRv3Lexer;
 import org.antlr.parser.antlr3.ANTLRv3Parser;
 import org.antlr.parser.antlr3.ANTLRv3ParserBaseListener;
@@ -37,6 +38,8 @@ import org.netbeans.modules.parsing.api.Snapshot;
  * @author lkishalmi
  */
 public final class Antlr3ParserResult extends AntlrParserResult<ANTLRv3Parser> {
+    
+    private static final Logger LOG = Logger.getLogger(Antlr3ParserResult.class.getName());
 
     public Antlr3ParserResult(Snapshot snapshot) {
         super(snapshot);
@@ -63,12 +66,8 @@ public final class Antlr3ParserResult extends AntlrParserResult<ANTLRv3Parser> {
                 Token token = ctx.id_().getStart();
                 OffsetRange range = new OffsetRange(token.getStartIndex(), token.getStopIndex() + 1);
                 String name = token.getText();
-                if (references.containsKey(name)) {
-                    references.get(name).defOffset = range;
-                } else {
-                    Reference ref = new Reference(name, getFileObject(), range);
-                    references.put(ref.name, ref);
-                }
+                Reference ref = new Reference(name, range);
+                references.put(ref.name, ref);
             }
         };
     }
@@ -77,7 +76,7 @@ public final class Antlr3ParserResult extends AntlrParserResult<ANTLRv3Parser> {
     protected ParseTreeListener createCheckReferences() {
         return new ANTLRv3OccuranceListener((token) -> {
             String name = token.getText();
-            if (!"EOF".equals(name) && (!references.containsKey(name) || references.get(name).defOffset == null)) {
+            if (!references.containsKey(name)) {
                 //TODO: It seems the ANTLRv3 Grammar Occurance finder could be a bit smarter
                 //Adding the following line could produce false positives.
                 //errors.add(new DefaultError(null, "Unknown Reference: " + name, null, source, token.getStartIndex(), token.getStopIndex() + 1, Severity.ERROR));
@@ -137,9 +136,10 @@ public final class Antlr3ParserResult extends AntlrParserResult<ANTLRv3Parser> {
 
             @Override
             public void exitRule_(ANTLRv3Parser.Rule_Context ctx) {
+                boolean fragment = ctx.FRAGMENT() != null;
                 if (ctx.id_() != null) {
                     AntlrStructureItem.RuleStructureItem rule = new AntlrStructureItem.RuleStructureItem(
-                            ctx.id_().getText(), getFileObject(), ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex() + 1);
+                            ctx.id_().getText(), fragment, getFileObject(), ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex() + 1);
                     structure.add(rule);
                 }
             }
@@ -151,12 +151,8 @@ public final class Antlr3ParserResult extends AntlrParserResult<ANTLRv3Parser> {
     protected ParseTreeListener createOccurancesListener() {
         return new ANTLRv3OccuranceListener((token) -> {
                 String refName = token.getText();
-                Reference ref = references.get(refName);
-                if (ref == null) {
-                    ref = new Reference(refName, getSnapshot().getSource().getFileObject(), null);
-                    references.put(ref.name, ref);
-                }
-                ref.occurances.add(new OffsetRange(token.getStartIndex(), token.getStopIndex() + 1));
+                OffsetRange or = new OffsetRange(token.getStartIndex(), token.getStopIndex() + 1);
+                markOccurrence(refName, or);
         });
     }
 
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4CompletionProvider.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4CompletionProvider.java
new file mode 100644
index 0000000000..b072a74d02
--- /dev/null
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4CompletionProvider.java
@@ -0,0 +1,273 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.languages.antlr.v4;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import org.netbeans.modules.languages.antlr.*;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.prefs.Preferences;
+import javax.swing.text.AbstractDocument;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.JTextComponent;
+import org.antlr.parser.antlr4.ANTLRv4Lexer;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.Token;
+import org.netbeans.api.editor.document.EditorDocumentUtils;
+import org.netbeans.api.editor.document.LineDocument;
+import org.netbeans.api.editor.document.LineDocumentUtils;
+import org.netbeans.api.editor.mimelookup.MimeLookup;
+import org.netbeans.api.editor.mimelookup.MimePath;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.editor.settings.SimpleValueNames;
+import org.netbeans.spi.editor.completion.CompletionItem;
+import org.netbeans.spi.editor.completion.CompletionProvider;
+import org.netbeans.spi.editor.completion.CompletionResultSet;
+import org.netbeans.spi.editor.completion.CompletionTask;
+import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery;
+import org.netbeans.spi.editor.completion.support.AsyncCompletionTask;
+import org.netbeans.spi.editor.completion.support.CompletionUtilities;
+import org.openide.filesystems.FileObject;
+
+import org.netbeans.api.annotations.common.StaticResource;
+
+import static org.antlr.parser.antlr4.ANTLRv4Lexer.*;
+import static org.netbeans.modules.languages.antlr.AntlrTokenSequence.DEFAULT_CHANNEL;
+
+/**
+ *
+ * @author Laszlo Kishalmi
+ */
+@MimeRegistration(mimeType = Antlr4Language.MIME_TYPE, service = CompletionProvider.class)
+public class Antlr4CompletionProvider implements CompletionProvider {
+
+    @StaticResource
+    private static final String ANTLR_ICON = "org/netbeans/modules/languages/antlr/resources/antlr.png";
+
+    @Override
+    public CompletionTask createTask(int queryType, JTextComponent component) {
+        return new AsyncCompletionTask(new AntlrCompletionQuery(isCaseSensitive()), component);
+    }
+
+    @Override
+    public int getAutoQueryTypes(JTextComponent component, String typedText) {
+        return 0;
+    }
+
+    private static boolean isCaseSensitive() {
+        Preferences prefs = MimeLookup.getLookup(MimePath.EMPTY).lookup(Preferences.class);
+        return prefs.getBoolean(SimpleValueNames.COMPLETION_CASE_SENSITIVE, false);
+    }
+
+    private class AntlrCompletionQuery extends AsyncCompletionQuery {
+
+        final boolean caseSensitive;
+
+        public AntlrCompletionQuery(boolean caseSensitive) {
+            this.caseSensitive = caseSensitive;
+        }
+
+        //TODO: This is a Lexer based pretty dumb implementation. Only offer
+        //      prefix if the cursor is at the end of a start of token/lexer rule.
+        //      Shall be replaced with a better approach.
+        private String getPrefix(Document doc, int caretOffset, boolean upToOffset) throws BadLocationException {
+            LineDocument lineDoc = LineDocumentUtils.asRequired(doc, LineDocument.class);
+            int start = LineDocumentUtils.getWordStart(lineDoc, caretOffset);
+            int end = LineDocumentUtils.getWordEnd(lineDoc, caretOffset);
+            String prefix = doc.getText(start, (upToOffset ? caretOffset : end) - start);
+
+            return (prefix.length() > 0) && !Character.isWhitespace(prefix.codePointAt(prefix.length() - 1)) ? prefix : "";
+        }
+
+        @Override
+        protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) {
+            AbstractDocument adoc = (AbstractDocument) doc;
+            try {
+                FileObject fo = EditorDocumentUtils.getFileObject(doc);
+                if (fo == null) {
+                    return;
+                }
+
+                String prefix = "";
+                adoc.readLock();
+                AntlrTokenSequence tokens;
+                try {
+                    String text = doc.getText(0, doc.getLength());
+                    tokens = new AntlrTokenSequence(new ANTLRv4Lexer(CharStreams.fromString(text)));
+                } catch (BadLocationException ex) {
+                    return;
+                } finally {
+                    adoc.readUnlock();
+                }
+
+                if (!tokens.isEmpty()) {
+
+                    tokens.seekTo(caretOffset);
+                    // Check if we are in a comment (that is filtered out from the token stream)
+
+                    int tokenOffset = tokens.getOffset();
+                    if (tokens.hasNext()) {
+                        Token nt = tokens.next().get();
+                        if (caretOffset > tokenOffset) {
+                            // Caret is in a token
+                            if ((nt.getChannel() == COMMENT) || (nt.getType() == ACTION_CONTENT)) {
+                                // We are in comment or action, no code completion
+                                return;
+                            }
+                            if (nt.getChannel() == DEFAULT_TOKEN_CHANNEL) {
+                                prefix = nt.getText().substring(0, caretOffset - tokenOffset);
+                            }
+                        } else if (nt.getType() == ACTION_CONTENT) {
+                            //We are in action
+                            return;
+                        }
+                        tokens.previous();
+                        lookAround(fo, tokens, caretOffset, prefix, resultSet);
+                    } else {
+                        //Empty grammar so far offer lexer and grammar
+                        addTokens("", caretOffset, resultSet, "lexer", "grammar");
+                    }
+                }
+            } catch (Throwable th) {
+                System.out.println(th);
+            } finally {
+                resultSet.finish();
+            }
+        }
+
+        private void lookAround(FileObject fo, AntlrTokenSequence tokens, int caretOffset, String prefix, CompletionResultSet resultSet) {
+            Optional<Token> opt = tokens.previous(DEFAULT_CHANNEL);
+            if (!opt.isPresent()) {
+                //At the start of the file;
+                Optional<Token> t = tokens.next(DEFAULT_CHANNEL);
+                if (t.isPresent() && t.get().getType() != LEXER) {
+                    addTokens(prefix, caretOffset, resultSet, "lexer");
+                }                
+                if (t.isPresent() && (t.get().getType() != LEXER) && (t.get().getType() != GRAMMAR)) {
+                    addTokens(prefix, caretOffset, resultSet, "grammar");
+                }
+                return;
+            } else {
+                Token pt = opt.get();
+                if (((pt.getType() == RULE_REF) || (pt.getType() == TOKEN_REF)) && (caretOffset == pt.getStopIndex() + 1)) {
+                    // Could be start of some keywords
+                    prefix = pt.getText();
+                    opt = tokens.previous(DEFAULT_CHANNEL);
+                }
+                if (!opt.isPresent()) {
+                    addTokens(prefix, caretOffset, resultSet, "lexer", "grammar");
+                    return;
+                } else {
+                    pt = opt.get();
+                    switch (pt.getType()) {
+                        case LEXER:
+                            Optional<Token> t = tokens.next(DEFAULT_CHANNEL);
+                            if (!t.isPresent() || t.get().getType() != GRAMMAR) {
+                                addTokens(prefix, caretOffset, resultSet, "grammar");
+                            }
+                            return;
+
+                        case SEMI:
+                            //Could be the begining of a new rule def.
+                            addTokens(prefix, caretOffset, resultSet, "mode", "fragment");
+                            return;
+                        case RARROW:
+                            //Command: offer 'channel', 'skip', etc...
+                            addTokens(prefix, caretOffset, resultSet, "skip", "more", "type", "channel", "mode", "pushMode", "popMode");
+                            return;
+                        default:
+                            tokens.seekTo(caretOffset);
+                            Optional<Token> semi = tokens.previous(SEMI);
+                            tokens.seekTo(caretOffset);
+                            Optional<Token> colon = tokens.previous(COLON);
+                            if (semi.isPresent() && colon.isPresent()
+                                    && semi.get().getStartIndex() < colon.get().getStartIndex()) {
+                                // we are in lexer/parser ruledef
+                                
+                                Set<FileObject> scanned = new HashSet<>();
+                                Map<String,AntlrParserResult.Reference> matchingRefs = new HashMap<>();
+                                addReferencesForFile(fo, prefix, matchingRefs, scanned);
+                                
+                                int startOffset = caretOffset - prefix.length();
+                                for (AntlrParserResult.Reference ref : matchingRefs.values()) {
+                                    CompletionItem item = CompletionUtilities.newCompletionItemBuilder(ref.name)
+                                            .startOffset(startOffset)
+                                            .leftHtmlText(ref.name)
+                                            .sortText(ref.name)
+                                            .build();
+                                    resultSet.addItem(item);
+                                    
+                                }
+                            }
+
+                    }
+
+                }
+            }
+
+        }
+
+        public void addTokens(String prefix, int caretOffset, CompletionResultSet resultSet, String... tokens) {
+            String uprefix = caseSensitive ? prefix : prefix.toUpperCase();
+            for (String token : tokens) {
+                String utoken = caseSensitive ? token : token.toUpperCase();
+                if (utoken.startsWith(uprefix)) {
+                    CompletionItem item = CompletionUtilities.newCompletionItemBuilder(token)
+                            .iconResource(ANTLR_ICON)
+                            .startOffset(caretOffset - prefix.length())
+                            .leftHtmlText(token)
+                            .build();
+                    resultSet.addItem(item);
+                }
+            }
+        }
+
+        public void addReferencesForFile(FileObject fo, String prefix, Map<String,AntlrParserResult.Reference> matching, Set<FileObject> scannedFiles) {
+            if (scannedFiles.contains(fo)) {
+                return;
+            }
+            scannedFiles.add(fo);
+
+            String mprefix = caseSensitive ? prefix : prefix.toUpperCase();
+
+            AntlrParserResult<?> result = AntlrParser.getParserResult(fo);
+            Map<String, AntlrParserResult.Reference> refs = result.references;
+            for (String ref : refs.keySet()) {
+                String mref = caseSensitive ? ref : ref.toUpperCase();
+                boolean match = mref.startsWith(mprefix);
+                if (match && !matching.containsKey(ref)) {
+                    matching.put(ref, refs.get(ref));
+                }
+            }
+
+            if (result instanceof Antlr4ParserResult) {
+                for (String s : ((Antlr4ParserResult) result).getImports()) {
+                    FileObject importedFo = fo.getParent().getFileObject(s, "g4");
+                    if (importedFo != null) {
+                        addReferencesForFile(importedFo, prefix, matching, scannedFiles);
+                    }
+                }
+            }
+        }
+
+    }
+}
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4Language.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4Language.java
index 70739c6c18..d411060999 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4Language.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4Language.java
@@ -168,7 +168,7 @@ public final class Antlr4Language extends DefaultLanguageConfig {
 
     @Override
     public OccurrencesFinder getOccurrencesFinder() {
-        return new AntlrOccurancesFinder();
+        return new AntlrOccurrencesFinder();
     }
 
     @Override
diff --git a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4ParserResult.java b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4ParserResult.java
index ffffb31cc2..b6823cc56c 100644
--- a/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4ParserResult.java
+++ b/java/languages.antlr/src/org/netbeans/modules/languages/antlr/v4/Antlr4ParserResult.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
+import java.util.logging.Logger;
 import org.antlr.parser.antlr4.ANTLRv4Lexer;
 import org.antlr.parser.antlr4.ANTLRv4Parser;
 import org.antlr.parser.antlr4.ANTLRv4ParserBaseListener;
@@ -32,6 +33,7 @@ import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
 import org.antlr.v4.runtime.Token;
 import org.antlr.v4.runtime.tree.ParseTreeListener;
+import org.antlr.v4.runtime.tree.TerminalNode;
 import org.netbeans.modules.csl.api.OffsetRange;
 import org.netbeans.modules.csl.api.Severity;
 import org.netbeans.modules.csl.spi.DefaultError;
@@ -47,10 +49,14 @@ import org.openide.filesystems.FileObject;
  */
 public final class Antlr4ParserResult extends AntlrParserResult<ANTLRv4Parser> {
 
-    private List<String> imports = new ArrayList<>();
+    private final List<String> imports = new ArrayList<>();
 
+    private static final Logger LOG = Logger.getLogger(Antlr4ParserResult.class.getName());
+    public static final Reference HIDDEN = new Reference("HIDDEN", OffsetRange.NONE);
+    
     public Antlr4ParserResult(Snapshot snapshot) {
         super(snapshot);
+        references.put(HIDDEN.name, HIDDEN);
     }
     
     @Override
@@ -66,6 +72,11 @@ public final class Antlr4ParserResult extends AntlrParserResult<ANTLRv4Parser> {
         parser.grammarSpec();
     }
 
+    private static Token getIdentifierToken(ANTLRv4Parser.IdentifierContext ctx) {
+        TerminalNode tn = ctx.RULE_REF() != null ? ctx.RULE_REF() : ctx.TOKEN_REF();
+        return tn.getSymbol();
+    }
+    
     @Override
     protected ParseTreeListener createReferenceListener() {
         return new ANTLRv4ParserBaseListener() {
@@ -91,15 +102,24 @@ public final class Antlr4ParserResult extends AntlrParserResult<ANTLRv4Parser> {
                 }
             }
 
+            @Override
+            public void exitChannelsSpec(ANTLRv4Parser.ChannelsSpecContext ctx) {
+                List<ANTLRv4Parser.IdentifierContext> ids = ctx.idList().identifier();
+                for (ANTLRv4Parser.IdentifierContext id : ids) {
+                    addReference(getIdentifierToken(id));
+                }
+            }
+
+            @Override
+            public void exitModeSpec(ANTLRv4Parser.ModeSpecContext ctx) {
+                addReference(getIdentifierToken(ctx.identifier()));
+            }
+
             public void addReference(Token token) {
                 OffsetRange range = new OffsetRange(token.getStartIndex(), token.getStopIndex() + 1);
                 String name = token.getText();
-                if(references.containsKey(name)) {
-                    references.get(name).defOffset = range;
-                } else {
-                    Reference ref = new Reference(name, getFileObject(), range);
-                    references.put(ref.name, ref);
-                }
+                Reference ref = new Reference(name, range);
+                references.put(ref.name, ref);
             }
 
         };
@@ -118,7 +138,7 @@ public final class Antlr4ParserResult extends AntlrParserResult<ANTLRv4Parser> {
         }
         return new ANTLRv4OccuranceListener((token) -> {
             String name = token.getText();
-            if(!"EOF".equals(name) && (!allRefs.containsKey(name) || (allRefs.get(name).defOffset == null))) {
+            if(!allRefs.containsKey(name)) {
                 errors.add(new DefaultError(null, "Unknown Reference: " + name, null, getFileObject(), token.getStartIndex(), token.getStopIndex() + 1, Severity.ERROR));
             }
         });
@@ -211,17 +231,22 @@ public final class Antlr4ParserResult extends AntlrParserResult<ANTLRv4Parser> {
 
             @Override
             public void exitLexerRuleSpec(ANTLRv4Parser.LexerRuleSpecContext ctx) {
-                if ((ctx.FRAGMENT() == null) && (ctx.TOKEN_REF() != null)) {
+                boolean fragment = ctx.FRAGMENT() != null;
+                if (ctx.TOKEN_REF() != null) {
                     // Do not represent fragments in the structure
-                    AntlrStructureItem.RuleStructureItem rule = new AntlrStructureItem.RuleStructureItem(ctx.TOKEN_REF().getText(), getFileObject(), ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex() + 1);
-                    lexerStructure.add(rule);
+                    AntlrStructureItem.RuleStructureItem rule = new AntlrStructureItem.RuleStructureItem(ctx.TOKEN_REF().getText(), fragment, getFileObject(), ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex() + 1);
+                    if (fragment) {
+                        structure.add(rule);
+                    } else {
+                        lexerStructure.add(rule);
+                    }
                 }
             }
 
             @Override
             public void exitParserRuleSpec(ANTLRv4Parser.ParserRuleSpecContext ctx) {
                 if (ctx.RULE_REF() != null) {
-                    AntlrStructureItem.RuleStructureItem rule = new AntlrStructureItem.RuleStructureItem(ctx.RULE_REF().getText(), getFileObject(), ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex() + 1);
+                    AntlrStructureItem.RuleStructureItem rule = new AntlrStructureItem.RuleStructureItem(ctx.RULE_REF().getText(), false, getFileObject(), ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex() + 1);
                     structure.add(rule);
                 }
             }
@@ -250,12 +275,8 @@ public final class Antlr4ParserResult extends AntlrParserResult<ANTLRv4Parser> {
 
     private void addOccurance(Token token) {
         String refName = token.getText();
-        Reference ref = references.get(refName);
-        if (ref == null) {
-            ref = new Reference(refName, getSnapshot().getSource().getFileObject(), null);
-            references.put(ref.name, ref);
-        }
-       ref.occurances.add(new OffsetRange(token.getStartIndex(), token.getStopIndex() + 1));
+        OffsetRange or = new OffsetRange(token.getStartIndex(), token.getStopIndex() + 1);
+        markOccurrence(refName, or);
     }
 
     @Override
@@ -287,5 +308,19 @@ public final class Antlr4ParserResult extends AntlrParserResult<ANTLRv4Parser> {
                 onOccurance.accept(ctx.RULE_REF().getSymbol());
             }
         }
+
+        @Override
+        public void exitLexerCommandExpr(ANTLRv4Parser.LexerCommandExprContext ctx) {
+            onOccurance.accept(getIdentifierToken(ctx.identifier()));
+        }
+
+        
+        @Override
+        public void exitChannelsSpec(ANTLRv4Parser.ChannelsSpecContext ctx) {
+            List<ANTLRv4Parser.IdentifierContext> ids = ctx.idList().identifier();
+            for (ANTLRv4Parser.IdentifierContext id : ids) {
+                onOccurance.accept(getIdentifierToken(id));
+            }
+        }
     }
 }
diff --git a/java/languages.antlr/test/unit/src/org/netbeans/modules/languages/antlr/AntlrTokenSequenceTest.java b/java/languages.antlr/test/unit/src/org/netbeans/modules/languages/antlr/AntlrTokenSequenceTest.java
new file mode 100644
index 0000000000..1e3f775a5a
--- /dev/null
+++ b/java/languages.antlr/test/unit/src/org/netbeans/modules/languages/antlr/AntlrTokenSequenceTest.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.languages.antlr;
+
+import org.antlr.parser.antlr4.ANTLRv4Lexer;
+import org.antlr.v4.runtime.CharStreams;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.netbeans.modules.languages.antlr.AntlrTokenSequence.DEFAULT_CHANNEL;
+/**
+ *
+ * @author lkishalmi
+ */
+public class AntlrTokenSequenceTest {
+    
+    public AntlrTokenSequenceTest() {
+    }
+    
+    /**
+     * Test of seekTo method, of class AntlrTokenSequence.
+     */
+    @Test
+    public void testSeekTo1() {
+        System.out.println("seekTo");
+        int offset = 0;
+        AntlrTokenSequence instance = sequence("");
+        instance.seekTo(offset);
+        assertTrue(instance.isEmpty());
+    }
+
+    @Test
+    public void testSeekTo2() {
+        System.out.println("seekTo");
+        int offset = 0;
+        AntlrTokenSequence instance = sequence("/**/");
+        instance.seekTo(offset);
+        assertFalse(instance.isEmpty());
+        assertTrue(instance.next().isPresent());
+    }
+
+    @Test
+    public void testSeekTo3() {
+        System.out.println("seekTo");
+        int offset = 4;
+        AntlrTokenSequence instance = sequence("/**/");
+        instance.seekTo(offset);
+        assertFalse(instance.isEmpty());
+        assertFalse(instance.next().isPresent());
+        assertTrue(instance.hasPrevious());
+    }
+
+    @Test
+    public void testSeekTo4() {
+        System.out.println("seekTo");
+        int offset = 5;
+        AntlrTokenSequence instance = sequence("/* */lexer");
+        instance.seekTo(offset);
+        assertFalse(instance.isEmpty());
+        assertTrue(instance.next().isPresent());
+        assertTrue(instance.hasPrevious());
+    }
+
+    @Test
+    public void testSeekTo5() {
+        System.out.println("seekTo");
+        AntlrTokenSequence instance = sequence("/* */lexer");
+        instance.seekTo(10);
+        assertFalse(instance.next().isPresent());
+        instance.seekTo(5);
+        assertFalse(instance.isEmpty());
+        assertTrue(instance.next().isPresent());
+        assertTrue(instance.previous().isPresent());
+        assertFalse(instance.hasPrevious());
+    }
+
+    /**
+     * Test of isEmpty method, of class AntlrTokenSequence.
+     */
+    @Test
+    public void testIsEmpty() {
+        System.out.println("isEmpty");
+        AntlrTokenSequence instance = sequence("");
+        assertTrue(instance.isEmpty());
+    }
+
+    @Test
+    public void testHasNext1() {
+        System.out.println("hasNext");
+        AntlrTokenSequence instance = sequence("lexer");
+        assertTrue(instance.hasNext());
+    }
+
+    @Test
+    public void testHasNext2() {
+        System.out.println("hasNext");
+        AntlrTokenSequence instance = sequence("");
+        assertFalse(instance.hasNext());
+    }
+
+    @Test
+    public void testHasNext3() {
+        System.out.println("hasNext");
+        AntlrTokenSequence instance = sequence("lexer");
+        assertTrue(instance.hasNext());
+        instance.next();
+        assertFalse(instance.hasNext());
+    }
+
+    @Test
+    public void testHasPrevious1() {
+        AntlrTokenSequence instance = sequence("lexer");
+        instance.next();
+        assertTrue(instance.hasPrevious());
+    }
+
+    @Test
+    public void testHasPrevious2() {
+        AntlrTokenSequence instance = sequence("");
+        assertFalse(instance.hasPrevious());
+    }
+
+    @Test
+    public void testGetOffset1() {
+        AntlrTokenSequence instance = sequence("/* */lexer");
+        instance.seekTo(7);
+        assertTrue(instance.hasNext());
+        assertEquals(5, instance.getOffset());
+        instance.previous();
+        assertEquals(0, instance.getOffset());
+    }
+
+    @Test
+    public void testNextPredicate1() {
+        AntlrTokenSequence instance = sequence("/* */lexer grammar");
+        assertTrue(instance.hasNext());
+        instance.next(DEFAULT_CHANNEL).ifPresent((t) -> assertEquals("lexer", t.getText()));
+        instance.next(DEFAULT_CHANNEL).ifPresent((t) -> assertEquals("grammar", t.getText()));
+        assertFalse(instance.hasNext());        
+    }
+
+    @Test
+    public void testPreviousPredicate1() {
+        AntlrTokenSequence instance = sequence("/* */lexer grammar");
+        instance.seekTo(18);
+        assertFalse(instance.hasNext());
+        instance.previous(DEFAULT_CHANNEL).ifPresent((t) -> assertEquals("grammar", t.getText()));
+        instance.previous(DEFAULT_CHANNEL).ifPresent((t) -> assertEquals("lexer", t.getText()));
+        assertTrue(instance.hasPrevious());        
+    }
+
+    private AntlrTokenSequence sequence(String s) {
+        return new AntlrTokenSequence(new ANTLRv4Lexer(CharStreams.fromString(s)));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists