/*
* Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC
* All rights reserved.
*
* The source code of this document is proprietary work, and is not licensed for
* distribution. For information about licensing, contact Sam Harwell at:
* sam@tunnelvisionlabs.com
*/
package org.antlr.works.editor.grammar.completion;
import com.tvl.spi.editor.completion.CompletionProvider;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.logging.Logger;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.antlr.netbeans.editor.navigation.Description;
import org.antlr.netbeans.editor.text.DocumentSnapshot;
import org.antlr.netbeans.parsing.spi.ParserDataOptions;
import org.antlr.netbeans.parsing.spi.ParserTaskManager;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.Token;
import org.antlr.works.editor.antlr4.completion.AbstractCompletionProvider;
import org.antlr.works.editor.antlr4.completion.AbstractCompletionQuery;
import org.antlr.works.editor.grammar.GoToSupport;
import org.antlr.works.editor.grammar.GrammarEditorKit;
import org.antlr.works.editor.grammar.GrammarParserDataDefinitions;
import org.antlr.works.editor.grammar.codemodel.ChannelModel;
import org.antlr.works.editor.grammar.codemodel.CodeElementModel;
import org.antlr.works.editor.grammar.codemodel.CodeElementPositionRegion;
import org.antlr.works.editor.grammar.codemodel.FileModel;
import org.antlr.works.editor.grammar.codemodel.LexerRuleModel;
import org.antlr.works.editor.grammar.codemodel.ModeModel;
import org.antlr.works.editor.grammar.codemodel.RuleModel;
import org.antlr.works.editor.grammar.codemodel.TokenData;
import org.antlr.works.editor.grammar.experimental.GrammarLexer;
import org.antlr.works.editor.grammar.navigation.DeclarationKind;
import org.antlr.works.editor.grammar.navigation.GrammarNode.GrammarNodeDescription;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.openide.util.NbBundle;
/**
*
* @author Sam Harwell
*/
@MimeRegistration(mimeType=GrammarEditorKit.GRAMMAR_MIME_TYPE, service=CompletionProvider.class)
@NbBundle.Messages({
"GCP-imported-items=",
"GCP-instance-members="
})
public class GrammarCompletionProvider extends AbstractCompletionProvider {
// -J-Dorg.antlr.works.editor.grammar.GrammarCompletionProvider.level=FINE
private static final Logger LOGGER = Logger.getLogger(GrammarCompletionProvider.class.getName());
private static final String grammarCompletionAutoPopupTriggers = "$";
private static final String grammarCompletionSelectors = " :;[]<>";
@Override
protected AbstractCompletionQuery createCompletionQuery(int queryType, int caretOffset, boolean extend) {
return new GrammarCompletionQuery(this, queryType, caretOffset, true, extend);
}
@Override
public boolean autoPopupOnIdentifierPart() {
return true;
}
@Override
public String getCompletionAutoPopupTriggers() {
return grammarCompletionAutoPopupTriggers;
}
@Override
public String getCompletionSelectors() {
return grammarCompletionSelectors;
}
@Override
public boolean isIdentifierPart(String text) {
return GrammarCompletionQuery.isGrammarIdentifierPart(text);
}
@Override
public Token getContext(Document document, int offset) {
return GoToSupport.getContext(document, offset);
}
@Override
public boolean isContext(JTextComponent component, int offset, int queryType) {
return isContext(getContext(component, offset), offset, true, true);
}
public boolean isContext(JTextComponent component, int offset, boolean allowInStrings, boolean allowInActions) {
return isContext(getContext(component, offset), offset, allowInStrings, allowInActions);
}
@Override
public boolean isContext(Token token, int offset, int queryType) {
boolean allowInStrings = false;
boolean allowInActions = (queryType & TRIGGERED_QUERY_TYPE) != 0;
return isContext(token, offset, allowInStrings, allowInActions);
}
/*package*/ boolean isContext(Token token, int offset, boolean allowInStrings, boolean allowInActions) {
if (token == null) {
return false;
}
switch (token.getType()) {
case GrammarLexer.LEXER_CHAR_SET:
case GrammarLexer.ACTION_COMMENT:
return false;
case GrammarLexer.STRING_LITERAL:
case GrammarLexer.DOUBLE_QUOTE_STRING_LITERAL:
return allowInStrings;
case GrammarLexer.ARG_ACTION_WORD:
case GrammarLexer.ACTION_WORD:
return allowInActions;
case GrammarLexer.WS:
return true;
default:
return token.getChannel() == Lexer.DEFAULT_TOKEN_CHANNEL;
}
}
public static Collection<Description> getRulesFromGrammar(ParserTaskManager taskManager, DocumentSnapshot snapshot, boolean ignoreLexerOnlyRules) {
Map<String, Description> rules = new HashMap<>();
Description rootDescription = GrammarParserDataDefinitions.tryGetData(taskManager, snapshot, GrammarParserDataDefinitions.NAVIGATOR_ROOT, EnumSet.of(ParserDataOptions.SYNCHRONOUS));
if (rootDescription != null) {
Queue<Description> workList = new ArrayDeque<>();
workList.add(rootDescription);
while (!workList.isEmpty()) {
Description description = workList.remove();
if (description.getOffset() > 0) {
rules.put(description.getName(), description);
}
workList.addAll(description.getChildren());
}
}
FileModel fileModel = GrammarParserDataDefinitions.tryGetData(taskManager, snapshot, GrammarParserDataDefinitions.FILE_MODEL, EnumSet.of(ParserDataOptions.SYNCHRONOUS));
if (fileModel != null) {
for (RuleModel ruleModel : fileModel.getRules()) {
if (ignoreLexerOnlyRules && ruleModel instanceof LexerRuleModel && ((LexerRuleModel)ruleModel).getTokenData() == null) {
rules.remove(ruleModel.getName());
continue;
}
if (rules.containsKey(ruleModel.getName())) {
continue;
}
DeclarationKind declarationKind;
if (ruleModel instanceof LexerRuleModel) {
if (((LexerRuleModel)ruleModel).isFragment()) {
declarationKind = DeclarationKind.FRAGMENT_RULE;
} else {
declarationKind = DeclarationKind.LEXER_RULE;
}
} else {
declarationKind = DeclarationKind.PARSER_RULE;
}
GrammarNodeDescription description = new GrammarNodeDescription(declarationKind, ruleModel.getName());
addSeekPositionToDescription(description, ruleModel);
rules.put(ruleModel.getName(), description);
}
if (!ignoreLexerOnlyRules) {
for (ChannelModel channelModel : fileModel.getChannels()) {
GrammarNodeDescription description = new GrammarNodeDescription(DeclarationKind.CHANNEL, channelModel.getName());
addSeekPositionToDescription(description, channelModel);
rules.put(channelModel.getName(), description);
}
for (ModeModel modeModel : fileModel.getModes()) {
GrammarNodeDescription modeDescription = new GrammarNodeDescription(DeclarationKind.MODE, modeModel.getName());
addSeekPositionToDescription(modeDescription, modeModel);
rules.put(modeModel.getName(), modeDescription);
for (RuleModel ruleModel : modeModel.getRules()) {
if (rules.containsKey(ruleModel.getName())) {
continue;
}
GrammarNodeDescription description = new GrammarNodeDescription(DeclarationKind.LEXER_RULE, ruleModel.getName());
addSeekPositionToDescription(description, ruleModel);
rules.put(ruleModel.getName(), description);
}
}
}
for (TokenData tokenData : fileModel.getVocabulary().getTokens()) {
if (rules.containsKey(tokenData.getName())) {
continue;
}
GrammarNodeDescription description = new GrammarNodeDescription(DeclarationKind.TOKEN, tokenData.getName());
Collection<? extends RuleModel> resolved = tokenData.resolve();
for (RuleModel ruleModel : resolved) {
if (addSeekPositionToDescription(description, ruleModel)) {
break;
}
}
rules.put(tokenData.getName(), description);
}
}
return rules.values();
}
private static boolean addSeekPositionToDescription(GrammarNodeDescription description, CodeElementModel codeElementModel) {
CodeElementPositionRegion position = codeElementModel.getSeek();
if (position == null) {
return false;
}
description.setOffset(null, position.getFileObject(), position.getOffsetRegion().getStart());
return true;
}
}