/* * 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.st4.highlighter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.text.AttributeSet; import javax.swing.text.StyledDocument; import org.antlr.netbeans.editor.text.DocumentSnapshot; import org.antlr.netbeans.editor.text.OffsetRegion; import org.antlr.netbeans.parsing.spi.ParserData; import org.antlr.v4.runtime.Dependents; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.RuleDependency; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.misc.Tuple2; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.works.editor.antlr4.semantics.AbstractParseTreeSemanticHighlighter; import org.antlr.works.editor.antlr4.semantics.AbstractSemanticHighlighter; import org.antlr.works.editor.st4.StringTemplateEditorKit; import org.antlr.works.editor.st4.TemplateParserDataDefinitions; import org.antlr.works.editor.st4.experimental.generated.TemplateParser; import org.antlr.works.editor.st4.experimental.generated.TemplateParser.AnonymousTemplateContext; import org.antlr.works.editor.st4.experimental.generated.TemplateParser.AnonymousTemplateParametersContext; import org.antlr.works.editor.st4.experimental.generated.TemplateParser.DictDefContext; import org.antlr.works.editor.st4.experimental.generated.TemplateParser.FormalArgContext; import org.antlr.works.editor.st4.experimental.generated.TemplateParser.GroupContext; import org.antlr.works.editor.st4.experimental.generated.TemplateParser.IncludeExprContext; import org.antlr.works.editor.st4.experimental.generated.TemplateParser.OptionContext; import org.antlr.works.editor.st4.experimental.generated.TemplateParser.PrimaryContext; import org.antlr.works.editor.st4.experimental.generated.TemplateParser.TemplateDefContext; import org.antlr.works.editor.st4.experimental.generated.TemplateParserBaseListener; import org.netbeans.api.annotations.common.NonNull; 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.FontColorSettings; import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory; import org.netbeans.spi.editor.highlighting.support.OffsetsBag; import org.openide.util.Lookup; /** * * @author Sam Harwell */ public class SemanticHighlighter extends AbstractParseTreeSemanticHighlighter<SemanticHighlighter.SemanticAnalyzerListener, ParserRuleContext> { private final AttributeSet templateDeclarationAttributes; private final AttributeSet templateUseAttributes; private final AttributeSet regionDeclarationAttributes; private final AttributeSet regionUseAttributes; private final AttributeSet dictionaryDeclarationAttributes; private final AttributeSet dictionaryUseAttributes; private final AttributeSet parameterDeclarationAttributes; private final AttributeSet parameterUseAttributes; private final AttributeSet attributeUseAttributes; private final AttributeSet expressionOptionAttributes; private SemanticHighlighter(@NonNull StyledDocument document) { super(document, TemplateParserDataDefinitions.REFERENCE_PARSE_TREE); Lookup lookup = MimeLookup.getLookup(MimePath.parse(StringTemplateEditorKit.TEMPLATE_MIME_TYPE)); FontColorSettings settings = lookup.lookup(FontColorSettings.class); this.templateDeclarationAttributes = getFontAndColors(settings, "templateDeclaration"); this.templateUseAttributes = getFontAndColors(settings, "templateUse"); this.regionDeclarationAttributes = getFontAndColors(settings, "regionDeclaration"); this.regionUseAttributes = getFontAndColors(settings, "regionUse"); this.dictionaryDeclarationAttributes = getFontAndColors(settings, "dictionaryDeclaration"); this.dictionaryUseAttributes = getFontAndColors(settings, "dictionaryUse"); this.parameterDeclarationAttributes = getFontAndColors(settings, "parameterDeclaration"); this.parameterUseAttributes = getFontAndColors(settings, "parameterUse"); this.attributeUseAttributes = getFontAndColors(settings, "attributeUse"); this.expressionOptionAttributes = getFontAndColors(settings, "expressionOption"); } @Override protected SemanticAnalyzerListener createListener(ParserData<? extends ParserRuleContext> parserData) { return new SemanticAnalyzerListener(); } @Override protected ParseTree getParseTree(ParserData<? extends ParserRuleContext> parserData) { return parserData.getData(); } @Override protected void updateHighlights(OffsetsBag targetContainer, DocumentSnapshot sourceSnapshot, DocumentSnapshot currentSnapshot, SemanticAnalyzerListener listener) { List<Tuple2<OffsetRegion, AttributeSet>> intermediateContainer = new ArrayList<>(); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getTemplateDeclarations(), templateDeclarationAttributes); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getTemplateUses(), templateUseAttributes); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getRegionDeclarations(), regionDeclarationAttributes); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getRegionUses(), regionUseAttributes); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getDictionaryDeclarations(), dictionaryDeclarationAttributes); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getDictionaryUses(), dictionaryUseAttributes); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getParameterDeclarations(), parameterDeclarationAttributes); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getParameterUses(), parameterUseAttributes); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getAttributeUses(), attributeUseAttributes); addHighlights(intermediateContainer, sourceSnapshot, currentSnapshot, listener.getOptions(), expressionOptionAttributes); OffsetsBag container = new OffsetsBag(currentSnapshot.getVersionedDocument().getDocument()); fillHighlights(container, intermediateContainer); targetContainer.setHighlights(container); } @MimeRegistration(mimeType=StringTemplateEditorKit.TEMPLATE_MIME_TYPE, service=HighlightsLayerFactory.class) public static class LayerFactory extends AbstractLayerFactory { public LayerFactory() { super(SemanticHighlighter.class); } @Override protected AbstractSemanticHighlighter<?> createHighlighter(Context context) { return new SemanticHighlighter((StyledDocument)context.getDocument()); } } public static class SemanticAnalyzerListener extends TemplateParserBaseListener { private final Deque<Integer> memberContext = new ArrayDeque<>(); private final Deque<Set<String>> parameters = new ArrayDeque<>(); private final List<Token> templateDeclarations = new ArrayList<>(); private final List<Token> templateUses = new ArrayList<>(); private final List<Token> regionDeclarations = new ArrayList<>(); private final List<Token> regionUses = new ArrayList<>(); private final List<Token> dictionaryDeclarations = new ArrayList<>(); private final List<Token> dictionaryUses = new ArrayList<>(); private final List<Token> parameterDeclarations = new ArrayList<>(); private final List<Token> parameterUses = new ArrayList<>(); private final List<Token> attributeUses = new ArrayList<>(); private final List<Token> options = new ArrayList<>(); public List<Token> getTemplateDeclarations() { return templateDeclarations; } public List<Token> getTemplateUses() { return templateUses; } public List<Token> getRegionDeclarations() { return regionDeclarations; } public List<Token> getRegionUses() { return regionUses; } public List<Token> getDictionaryDeclarations() { return dictionaryDeclarations; } public List<Token> getDictionaryUses() { return dictionaryUses; } public List<Token> getParameterDeclarations() { return parameterDeclarations; } public List<Token> getParameterUses() { return parameterUses; } public List<Token> getAttributeUses() { return attributeUses; } public List<Token> getOptions() { return options; } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_dictDef, version=0, dependents=Dependents.PARENTS) public void enterDictDef(DictDefContext ctx) { if (ctx.name != null) { dictionaryDeclarations.add(ctx.name); } } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_templateDef, version=3, dependents=Dependents.PARENTS) public void enterTemplateDef(TemplateDefContext ctx) { parameters.push(new HashSet<String>()); if (ctx.name != null) { if (ctx.enclosing != null) { regionDeclarations.add(ctx.name); templateUses.add(ctx.enclosing); } else { templateDeclarations.add(ctx.name); } } if (ctx.alias != null) { templateDeclarations.add(ctx.alias); } if (ctx.target != null) { templateUses.add(ctx.target); } } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_templateDef, version=3, dependents=Dependents.PARENTS) public void exitTemplateDef(TemplateDefContext ctx) { parameters.pop(); } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_formalArg, version=1, dependents=Dependents.PARENTS) public void enterFormalArg(FormalArgContext ctx) { if (ctx.name != null) { parameterDeclarations.add(ctx.name); Set<String> currentParameters = parameters.peek(); currentParameters.add(ctx.name.getText()); } } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_anonymousTemplate, version=2, dependents=Dependents.PARENTS) public void enterAnonymousTemplate(AnonymousTemplateContext ctx) { parameters.push(new HashSet<String>()); } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_anonymousTemplate, version=2, dependents=Dependents.PARENTS) public void exitAnonymousTemplate(AnonymousTemplateContext ctx) { parameters.pop(); } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_anonymousTemplateParameters, version=0, dependents=Dependents.PARENTS) public void enterAnonymousTemplateParameters(AnonymousTemplateParametersContext ctx) { if (ctx.names != null) { parameterDeclarations.addAll(ctx.names); Set<String> currentParameters = parameters.peek(); for (Token token : ctx.names) { currentParameters.add(token.getText()); } } } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_option, version=0, dependents=Dependents.PARENTS) public void enterOption(OptionContext ctx) { if (ctx.name != null) { options.add(ctx.name); } } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_includeExpr, version=0, dependents=Dependents.PARENTS) public void enterIncludeExpr(IncludeExprContext ctx) { if (ctx.templateName != null) { if (ctx.AT() != null) { regionUses.add(ctx.templateName); } else { templateUses.add(ctx.templateName); } } if (ctx.regionName != null) { regionUses.add(ctx.regionName); } } @Override @RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_primary, version=1, dependents=Dependents.PARENTS) public void enterPrimary(PrimaryContext ctx) { TerminalNode id = ctx.ID(); if (id != null) { Set<String> currentParameters = parameters.isEmpty() ? null : parameters.peek(); if (currentParameters != null && currentParameters.contains(id.getSymbol().getText())) { parameterUses.add(id.getSymbol()); } else { attributeUses.add(id.getSymbol()); } } } @Override //@RuleDependency(recognizer=TemplateParser.class, rule=TemplateParser.RULE_group, version=0) public void exitGroup(GroupContext ctx) { } } }