/* * 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.highlighter; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collection; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import javax.swing.text.StyledDocument; import org.antlr.netbeans.editor.navigation.Description; import org.antlr.netbeans.editor.text.OffsetRegion; import org.antlr.netbeans.editor.text.SnapshotPositionRegion; import org.antlr.netbeans.editor.text.TrackingPositionRegion; import org.antlr.netbeans.editor.text.VersionedDocument; import org.antlr.netbeans.editor.text.VersionedDocumentUtilities; import org.antlr.netbeans.parsing.spi.ParserTaskManager; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Token; import org.antlr.v4.tool.Grammar; import org.antlr.works.editor.antlr4.highlighting.ANTLRHighlighterBaseV4; import org.antlr.works.editor.antlr4.highlighting.TokenSourceWithStateV4; import org.antlr.works.editor.grammar.GoToSupport; import org.antlr.works.editor.grammar.GrammarEditorKit; import org.antlr.works.editor.grammar.completion.GrammarCompletionProvider; import org.antlr.works.editor.grammar.experimental.GrammarParser; import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.editor.settings.AttributesUtilities; import org.netbeans.api.editor.settings.EditorStyleConstants; import org.netbeans.api.editor.settings.FontColorSettings; import org.netbeans.lib.editor.util.swing.DocumentUtilities; import org.netbeans.spi.editor.highlighting.HighlightAttributeValue; import org.openide.text.NbDocument; import org.openide.util.Lookup; /** * * @author Sam Harwell */ public class GrammarHighlighter extends ANTLRHighlighterBaseV4<GrammarHighlighterLexerState> { public static final String DOCUMENT_PROPERTY = "grammar-highlighter"; private static final AttributeSet TOOLTIP = AttributesUtilities.createImmutable(EditorStyleConstants.Tooltip, new TooltipResolver()); private final AttributeSet identifierAttributes; private final AttributeSet keywordAttributes; private final AttributeSet commentAttributes; private final AttributeSet stringLiteralAttributes; private final AttributeSet stringLiteralEscapeAttributes; private final AttributeSet stringLiteralEscapeInvalidAttributes; private final AttributeSet numberLiteralAttributes; private final AttributeSet symbolDefinitionAttributes; private final AttributeSet symbolReferenceAttributes; private final AttributeSet parserRuleAttributes; private final AttributeSet lexerRuleAttributes; private final AttributeSet astOperatorAttributes; private final AttributeSet directiveAttributes; private final AttributeSet validOptionAttributes; private final AttributeSet invalidOptionAttributes; private final AttributeSet actionLiteralAttributes; private final AttributeSet actionLiteralEscapeAttributes; private final AttributeSet actionCommentAttributes; private final AttributeSet actionStringLiteralAttributes; private final AttributeSet actionStringLiteralEscapeAttributes; private final AttributeSet actionSymbolReferenceAttributes; private boolean legacyMode; private GrammarHighlighterLexerWrapper lexerWrapper; @SuppressWarnings("LeakingThisInConstructor") public GrammarHighlighter(final StyledDocument document) { super(document); DocumentUtilities.addPropertyChangeListener(document, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt == null || evt.getPropertyName() == null || GrammarEditorKit.PROP_LEGACY_MODE.equals(evt.getPropertyName())) { legacyMode = GrammarEditorKit.isLegacyMode(document); GrammarHighlighter.this.forceRehighlightLines(0, NbDocument.findLineRootElement(document).getElementCount() - 1); } } }); document.putProperty(DOCUMENT_PROPERTY, this); Lookup lookup = MimeLookup.getLookup(MimePath.parse(GrammarEditorKit.GRAMMAR_MIME_TYPE)); FontColorSettings settings = lookup.lookup(FontColorSettings.class); identifierAttributes = getFontAndColors(settings, "identifier", true); keywordAttributes = getFontAndColors(settings, "keyword"); commentAttributes = getFontAndColors(settings, "comment"); stringLiteralAttributes = getFontAndColors(settings, "stringliteral", true); stringLiteralEscapeAttributes = getFontAndColors(settings, "stringliteralescape", true); stringLiteralEscapeInvalidAttributes = getFontAndColors(settings, "stringliteralescapeinvalid", true); numberLiteralAttributes = getFontAndColors(settings, "number"); symbolDefinitionAttributes = getFontAndColors(settings, "definition", true); symbolReferenceAttributes = getFontAndColors(settings, "reference"); parserRuleAttributes = getFontAndColors(settings, "parserrule", true); lexerRuleAttributes = getFontAndColors(settings, "lexerrule", true); astOperatorAttributes = getFontAndColors(settings, "astoperator"); directiveAttributes = getFontAndColors(settings, "directive"); validOptionAttributes = getFontAndColors(settings, "validoption"); invalidOptionAttributes = getFontAndColors(settings, "invalidoption"); actionLiteralAttributes = getFontAndColors(settings, "actionliteral"); actionLiteralEscapeAttributes = getFontAndColors(settings, "actionliteralescape"); actionCommentAttributes = getFontAndColors(settings, "actioncomment"); actionStringLiteralAttributes = getFontAndColors(settings, "actionstringliteral"); actionStringLiteralEscapeAttributes = getFontAndColors(settings, "actionstringliteralescape"); actionSymbolReferenceAttributes = getFontAndColors(settings, "actionreference"); legacyMode = GrammarEditorKit.isLegacyMode(document); } private static AttributeSet getFontAndColors(FontColorSettings settings, String category) { return getFontAndColors(settings, category, false); } private static AttributeSet getFontAndColors(FontColorSettings settings, String category, boolean tooltip) { AttributeSet attributes = settings.getTokenFontColors(category); if (tooltip) { attributes = AttributesUtilities.createComposite(attributes, TOOLTIP); } return attributes; } @Override protected CharStream createInputStream(OffsetRegion span) throws BadLocationException { return super.createInputStream(span); } @Override protected GrammarHighlighterLexerState getStartState() { return GrammarHighlighterLexerState.INITIAL; } @Override protected TokenSourceWithStateV4<GrammarHighlighterLexerState> createLexer(CharStream input, GrammarHighlighterLexerState startState) { if (lexerWrapper == null) { lexerWrapper = new GrammarHighlighterLexerWrapper(input, startState); } else { lexerWrapper.setState(input, startState); } return lexerWrapper; } @Override protected AttributeSet highlightToken(Token token) { switch (token.getType()) { // common keywords case GrammarHighlighterLexer.LEXER: case GrammarHighlighterLexer.PARSER: case GrammarHighlighterLexer.CATCH: case GrammarHighlighterLexer.FINALLY: case GrammarHighlighterLexer.GRAMMAR: case GrammarHighlighterLexer.PRIVATE: case GrammarHighlighterLexer.PROTECTED: case GrammarHighlighterLexer.PUBLIC: case GrammarHighlighterLexer.RETURNS : case GrammarHighlighterLexer.THROWS: case GrammarHighlighterLexer.IMPORT: case GrammarHighlighterLexer.FRAGMENT : case GrammarHighlighterLexer.TOKENS: case GrammarHighlighterLexer.OPTIONS: return keywordAttributes; // v4 only keywords case GrammarHighlighterLexer.MODE: case GrammarHighlighterLexer.LOCALS: case GrammarHighlighterLexer.CHANNELS: return !legacyMode ? keywordAttributes : getIdentifierAttributes(lexerWrapper.getLexer(), token.getText()); // v3 only keywords case GrammarHighlighterLexer.TEMPLATE: case GrammarHighlighterLexer.TREE: case GrammarHighlighterLexer.SCOPE: return legacyMode ? keywordAttributes : getIdentifierAttributes(lexerWrapper.getLexer(), token.getText()); case GrammarHighlighterLexer.IDENTIFIER: return getIdentifierAttributes(lexerWrapper.getLexer(), token.getText()); case GrammarHighlighterLexer.LABEL: return symbolDefinitionAttributes; case GrammarHighlighterLexer.COMMENT: case GrammarHighlighterLexer.ML_COMMENT: return commentAttributes; case GrammarHighlighterLexer.CHAR_LITERAL: case GrammarHighlighterLexer.STRING_LITERAL: case GrammarHighlighterLexer.LexerCharSet_TEXT: return stringLiteralAttributes; case GrammarHighlighterLexer.LexerCharSet_ESCAPE: return stringLiteralEscapeAttributes; case GrammarHighlighterLexer.LexerCharSet_INVALID_ESCAPE: return stringLiteralEscapeInvalidAttributes; case GrammarHighlighterLexer.DIRECTIVE: return directiveAttributes; case GrammarHighlighterLexer.REFERENCE: return symbolReferenceAttributes; case GrammarHighlighterLexer.BANG: case GrammarHighlighterLexer.REWRITE: case GrammarHighlighterLexer.ROOT: return astOperatorAttributes; case GrammarHighlighterLexer.INT: return numberLiteralAttributes; case GrammarHighlighterLexer.Action_COMMENT: case GrammarHighlighterLexer.Action_ML_COMMENT: return actionCommentAttributes; case GrammarHighlighterLexer.ArgAction_TEXT: case GrammarHighlighterLexer.Action_TEXT: return actionLiteralAttributes; case GrammarHighlighterLexer.Action_ESCAPE: return actionLiteralEscapeAttributes; case GrammarHighlighterLexer.ArgAction_CHAR_LITERAL: case GrammarHighlighterLexer.Action_CHAR_LITERAL: case GrammarHighlighterLexer.ArgAction_STRING_LITERAL: case GrammarHighlighterLexer.Action_STRING_LITERAL: return actionStringLiteralAttributes; case GrammarHighlighterLexer.ArgAction_REFERENCE: case GrammarHighlighterLexer.Action_REFERENCE: return actionSymbolReferenceAttributes; case GrammarHighlighterLexer.ValidGrammarOption: return validOptionAttributes; case GrammarHighlighterLexer.InvalidGrammarOption: return invalidOptionAttributes; default: return null; } } private AttributeSet getIdentifierAttributes(GrammarHighlighterLexer lexer, String text) { if (lexer.isInOptions()) { return identifierAttributes; } if (Grammar.isTokenName(text)) { return lexerRuleAttributes; } else { return parserRuleAttributes; } } private static final class TooltipResolver implements HighlightAttributeValue<String> { @Override public String getValue(JTextComponent component, Document document, Object attributeKey, int startOffset, int endOffset) { Token token = GoToSupport.getContext(document, startOffset); if (token == null) { return ""; } String ruleName; switch (token.getType()) { case GrammarParser.RULE_REF: case GrammarParser.TOKEN_REF: ruleName = token.getText(); break; default: return ""; } ParserTaskManager taskManager = Lookup.getDefault().lookup(ParserTaskManager.class); VersionedDocument versionedDocument = VersionedDocumentUtilities.getVersionedDocument(document); Collection<Description> rules = GrammarCompletionProvider.getRulesFromGrammar(taskManager, versionedDocument.getCurrentSnapshot(), false); Description target = null; for (Description rule : rules) { if (rule.getName() != null && rule.getName().equals(ruleName)) { target = rule; break; } } if (target == null) { return ""; } SnapshotPositionRegion region = target.getSpan(); if (region != null) { TrackingPositionRegion trackingRegion = region.getSnapshot().createTrackingRegion(region.getRegion(), TrackingPositionRegion.Bias.Forward); return trackingRegion.getText(versionedDocument.getCurrentSnapshot()); } String result = target.getHtmlHeader(); if (result == null) { result = target.getName(); } return result; } } }