Package net.nicoulaj.idea.markdown.annotator

Source Code of net.nicoulaj.idea.markdown.annotator.MarkdownAnnotator$MarkdownASTVisitor

/*
* Copyright (c) 2011-2014 Julien Nicoulaud <julien.nicoulaud@gmail.com>
*
* 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 net.nicoulaj.idea.markdown.annotator;

import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.ExternalAnnotator;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import net.nicoulaj.idea.markdown.highlighter.MarkdownSyntaxHighlighter;
import net.nicoulaj.idea.markdown.settings.MarkdownGlobalSettings;
import net.nicoulaj.idea.markdown.settings.MarkdownGlobalSettingsListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.pegdown.PegDownProcessor;
import org.pegdown.ast.*;

import java.util.HashSet;
import java.util.Set;

import static net.nicoulaj.idea.markdown.lang.MarkdownTokenTypes.*;

/**
* {@link ExternalAnnotator} responsible for syntax highlighting Markdown files.
* <p/>
* This is a hack to avoid implementing {@link com.intellij.lexer.Lexer},
* and directly use the AST from <a href="http://pegdown.org">pegdown</a>'s {@link PegDownProcessor} instead.
*
* @author Julien Nicoulaud <julien.nicoulaud@gmail.com>
* @since 0.4
*/
public class MarkdownAnnotator extends ExternalAnnotator<String, Set<MarkdownAnnotator.HighlightableToken>> {

    /** Logger. */
    private static final Logger LOGGER = Logger.getInstance(MarkdownAnnotator.class);

    /**
     * The {@link com.intellij.openapi.fileTypes.SyntaxHighlighter} used by
     * {@link #apply(com.intellij.psi.PsiFile, java.util.Set, com.intellij.lang.annotation.AnnotationHolder)}.
     */
    private static final SyntaxHighlighter SYNTAX_HIGHLIGHTER = new MarkdownSyntaxHighlighter();

    /** The {@link PegDownProcessor} used for building the document AST. */
    private ThreadLocal<PegDownProcessor> processor = initProcessor();

    /** Init/reinit thread local {@link PegDownProcessor}. */
    private static ThreadLocal<PegDownProcessor> initProcessor() {
        return new ThreadLocal<PegDownProcessor>() {
            @Override protected PegDownProcessor initialValue() {
                return new PegDownProcessor(MarkdownGlobalSettings.getInstance().getExtensionsValue(),
                                            MarkdownGlobalSettings.getInstance().getParsingTimeout());
            }
        };
    }

    /** Build a new instance of {@link MarkdownAnnotator}. */
    public MarkdownAnnotator() {
        // Listen to global settings changes.
        MarkdownGlobalSettings.getInstance().addListener(new MarkdownGlobalSettingsListener() {
            public void handleSettingsChanged(@NotNull final MarkdownGlobalSettings newSettings) {
                initProcessor();
            }
        });
    }

    /**
     * Get the text source of the given file.
     *
     * @param file the {@link PsiFile} to process.
     * @return the file text.
     */
    @Nullable @Override
    public String collectInformation(@NotNull PsiFile file) {
        return file.getText();
    }

    /**
     * Collect {@link net.nicoulaj.idea.markdown.annotator.MarkdownAnnotator.HighlightableToken}s from the given file.
     *
     * @param source the source text to process.
     * @return a {@link Set} of {@link net.nicoulaj.idea.markdown.annotator.MarkdownAnnotator.HighlightableToken}s that should be used to do the file syntax highlighting.
     */
    @Override
    public Set<HighlightableToken> doAnnotate(final String source) {
        final MarkdownASTVisitor visitor = new MarkdownASTVisitor();
        try {
            processor.get().parseMarkdown(source.toCharArray()).accept(visitor);
        } catch (Exception e) {
            LOGGER.error("Failed processing Markdown document", e);
        }
        return visitor.getTokens();
    }

    /**
     * Convert collected {@link net.nicoulaj.idea.markdown.annotator.MarkdownAnnotator.HighlightableToken}s in syntax highlighting annotations.
     *
     * @param file             the source file.
     * @param annotationResult the {@link Set} of {@link net.nicoulaj.idea.markdown.annotator.MarkdownAnnotator.HighlightableToken}s collected on the file.
     * @param holder           the annotation holder.
     */
    @Override
    public void apply(final @NotNull PsiFile file,
                      final Set<HighlightableToken> annotationResult,
                      final @NotNull AnnotationHolder holder) {
        for (final HighlightableToken token : annotationResult) {
            final TextAttributesKey[] attrs = SYNTAX_HIGHLIGHTER.getTokenHighlights(token.getElementType());
            if (attrs.length > 0) holder.createInfoAnnotation(token.getRange(), null).setTextAttributes(attrs[0]);
        }
    }

    /**
     * Describes a range of text that should be highlighted with a specific element type.
     *
     * @author Julien Nicoulaud <julien.nicoulaud@gmail.com>
     * @since 0.8
     */
    protected class HighlightableToken {

        /** The text range. */
        protected final TextRange range;

        /** The associated element type. */
        protected final IElementType elementType;

        /**
         * Build a new instance of {@link net.nicoulaj.idea.markdown.annotator.MarkdownAnnotator.HighlightableToken}.
         *
         * @param range       the text range.
         * @param elementType the associated element type.
         */
        public HighlightableToken(final TextRange range, final IElementType elementType) {
            this.range = range;
            this.elementType = elementType;
        }

        /**
         * Get the token text range.
         *
         * @return {@link #range}
         */
        public TextRange getRange() {
            return range;
        }

        /**
         * Get the token element type.
         *
         * @return {@link #elementType}
         */
        public IElementType getElementType() {
            return elementType;
        }
    }

    /**
     * {@link org.pegdown.ast.Visitor} used by {@link MarkdownAnnotator} to highlight a Markdown document.
     *
     * @author Julien Nicoulaud <julien.nicoulaud@gmail.com>
     * @since 0.4
     */
    protected class MarkdownASTVisitor implements Visitor {

        /** The collected token set. */
        protected final Set<HighlightableToken> tokens = new HashSet<HighlightableToken>(20);

        /**
         * Get the collected tokens set.
         *
         * @return {@link #tokens}
         */
        public Set<HighlightableToken> getTokens() {
            return tokens;
        }

        /**
         * Visit the {@link org.pegdown.ast.RootNode}.
         *
         * @param node the {@link org.pegdown.ast.RootNode} to visit
         */
        public void visit(RootNode node) {
            for (AbbreviationNode abbreviationNode : node.getAbbreviations()) abbreviationNode.accept(this);
            for (ReferenceNode referenceNode : node.getReferences()) referenceNode.accept(this);
            visitChildren(node);
        }

        /**
         * Visit the {@link org.pegdown.ast.SimpleNode}.
         *
         * @param node the {@link org.pegdown.ast.SimpleNode} to visit
         */
        public void visit(SimpleNode node) {
            switch (node.getType()) {
            case HRule:
                addToken(node, HRULE);
                break;
            case Apostrophe:
            case Ellipsis:
            case Emdash:
            case Endash:
            case Linebreak:
            case Nbsp:
                break;
            }
        }

        /**
         * Visit the {@link org.pegdown.ast.SuperNode}.
         *
         * @param node the {@link org.pegdown.ast.SuperNode} to visit
         */
        public void visit(SuperNode node) {
            visitChildren(node);
        }

        /**
         * Visit the {@link org.pegdown.ast.ParaNode}.
         *
         * @param node the {@link org.pegdown.ast.ParaNode} to visit
         */
        public void visit(ParaNode node) {
            visitChildren(node);
        }

        /**
         * Visit the {@link org.pegdown.ast.Node}.
         * <p/>
         * This method should never get called, highlights node as error.
         *
         * @param node the {@link org.pegdown.ast.Node} to visit
         */
        public void visit(Node node) {
            addToken(node, ERROR_ELEMENT);
        }

        /**
         * Visit the {@link TextNode}.
         *
         * @param node the {@link TextNode} to visit
         */
        public void visit(TextNode node) {
            addToken(node, TEXT);
        }

        /**
         * Visit the {@link SpecialTextNode}.
         *
         * @param node the {@link SpecialTextNode} to visit
         */
        public void visit(SpecialTextNode node) {
            addToken(node, SPECIAL_TEXT);
        }

        /**
         * Visit the {@link StrikeNode}.
         *
         * @param node the {@link StrikeNode} to visit
         */
        @Override
        public void visit(StrikeNode node) {
            addToken(node, STRIKETHROUGH);
            visitChildren(node);
        }

        /**
         * Visit the {@link StrongEmphSuperNode}.
         *
         * @param node the {@link StrongEmphSuperNode} to visit
         */
        public void visit(StrongEmphSuperNode node) {
            addToken(node, node.isStrong() ? BOLD : ITALIC);
            visitChildren(node);
        }

        /**
         * Visit the {@link ExpImageNode}.
         *
         * @param node the {@link ExpImageNode} to visit
         */
        public void visit(ExpImageNode node) {
            addToken(node, IMAGE);
            visitChildren(node);
        }

        /**
         * Visit the {@link ExpLinkNode}.
         *
         * @param node the {@link ExpLinkNode} to visit
         */
        public void visit(ExpLinkNode node) {
            addToken(node, EXPLICIT_LINK);
            visitChildren(node);
        }

        /**
         * Visit the {@link RefLinkNode}.
         *
         * @param node the {@link RefLinkNode} to visit
         */
        public void visit(final RefLinkNode node) {
            addToken(node, REFERENCE_LINK);
            visitChildren(node);
        }

        /**
         * Visit the {@link AutoLinkNode}.
         *
         * @param node the {@link AutoLinkNode} to visit
         */
        public void visit(AutoLinkNode node) {
            addToken(node, AUTO_LINK);
        }

        /**
         * Visit the {@link MailLinkNode}.
         *
         * @param node the {@link MailLinkNode} to visit
         */
        public void visit(MailLinkNode node) {
            addToken(node, MAIL_LINK);
        }

        /**
         * Visit the {@link HeaderNode}.
         *
         * @param node the {@link HeaderNode} to visit
         */
        public void visit(HeaderNode node) {
            switch (node.getLevel()) {
            case 1:
                addToken(node, HEADER_LEVEL_1);
                break;
            case 2:
                addToken(node, HEADER_LEVEL_2);
                break;
            case 3:
                addToken(node, HEADER_LEVEL_3);
                break;
            case 4:
                addToken(node, HEADER_LEVEL_4);
                break;
            case 5:
                addToken(node, HEADER_LEVEL_5);
                break;
            case 6:
                addToken(node, HEADER_LEVEL_6);
                break;
            }
            visitChildren(node);
        }

        /**
         * Visit the {@link CodeNode}.
         *
         * @param node the {@link CodeNode} to visit
         */
        public void visit(CodeNode node) {
            addToken(node, CODE);
        }

        /**
         * Visit the {@link VerbatimNode}.
         *
         * @param node the {@link VerbatimNode} to visit
         */
        public void visit(VerbatimNode node) {
            addToken(node, VERBATIM);
        }

        /**
         * Visit the {@link WikiLinkNode}.
         *
         * @param node the {@link WikiLinkNode} to visit
         */
        public void visit(WikiLinkNode node) {
            addToken(node, WIKI_LINK);
        }

        /**
         * Visit the {@link QuotedNode}.
         *
         * @param node the {@link QuotedNode} to visit
         */
        public void visit(QuotedNode node) {
            addToken(node, QUOTE);
        }

        /**
         * Visit the {@link BlockQuoteNode}.
         *
         * @param node the {@link BlockQuoteNode} to visit
         */
        public void visit(BlockQuoteNode node) {
            addToken(node, BLOCK_QUOTE);
            visitChildren(node);
        }

        /**
         * Visit the {@link BulletListNode}.
         *
         * @param node the {@link BulletListNode} to visit
         */
        public void visit(BulletListNode node) {
            addToken(node, BULLET_LIST);
            visitChildren(node);
        }

        /**
         * Visit the {@link OrderedListNode}.
         *
         * @param node the {@link OrderedListNode} to visit
         */
        public void visit(OrderedListNode node) {
            addToken(node, ORDERED_LIST);
            visitChildren(node);
        }

        /**
         * Visit the {@link ListItemNode}.
         *
         * @param node the {@link ListItemNode} to visit
         */
        public void visit(ListItemNode node) {
            addToken(node, LIST_ITEM);
            visitChildren(node);
        }

        /**
         * Visit the {@link DefinitionListNode}.
         *
         * @param node the {@link DefinitionListNode} to visit
         */
        public void visit(DefinitionListNode node) {
            addToken(node, DEFINITION_LIST);
            visitChildren(node);
        }

        /**
         * Visit the {@link DefinitionNode}.
         *
         * @param node the {@link DefinitionNode} to visit
         */
        public void visit(DefinitionNode node) {
            addToken(node, DEFINITION);
            visitChildren(node);
        }

        /**
         * Visit the {@link DefinitionTermNode}.
         *
         * @param node the {@link DefinitionTermNode} to visit
         */
        public void visit(DefinitionTermNode node) {
            addToken(node, DEFINITION_TERM);
            visitChildren(node);
        }

        /**
         * Visit the {@link TableNode}.
         *
         * @param node the {@link TableNode} to visit
         */
        public void visit(TableNode node) {
            addToken(node, TABLE);
            visitChildren(node);
        }

        /**
         * Visit the {@link TableBodyNode}.
         *
         * @param node the {@link TableBodyNode} to visit
         */
        public void visit(TableBodyNode node) {
            addToken(node, TABLE_BODY);
            visitChildren(node);
        }

        /**
         * Visit the {@link TableCellNode}.
         *
         * @param node the {@link TableCellNode} to visit
         */
        public void visit(TableCellNode node) {
            addToken(node, TABLE_CELL);
            visitChildren(node);
        }

        /**
         * Visit the {@link TableColumnNode}.
         *
         * @param node the {@link TableColumnNode} to visit
         */
        public void visit(TableColumnNode node) {
            addToken(node, TABLE_COLUMN);
            visitChildren(node);
        }

        /**
         * Visit the {@link TableHeaderNode}.
         *
         * @param node the {@link TableHeaderNode} to visit
         */
        public void visit(TableHeaderNode node) {
            addToken(node, TABLE_HEADER);
            visitChildren(node);
        }

        /**
         * Visit the {@link TableRowNode}.
         *
         * @param node the {@link TableRowNode} to visit
         */
        public void visit(TableRowNode node) {
            addToken(node, TABLE_ROW);
            visitChildren(node);
        }

        /**
         * Visit the {@link TableCaptionNode}.
         *
         * @param node the {@link TableCaptionNode} to visit
         */
        public void visit(TableCaptionNode node) {
            addToken(node, TABLE_CAPTION);
            visitChildren(node);
        }

        /**
         * Visit the {@link HtmlBlockNode}.
         * <p/>
         * TODO: Real HTML support not implemented.
         *
         * @param node the {@link HtmlBlockNode} to visit
         */
        public void visit(HtmlBlockNode node) {
            addToken(node, HTML_BLOCK);
        }

        /**
         * Visit the {@link InlineHtmlNode}.
         * <p/>
         * TODO: Real HTML support not implemented.
         *
         * @param node the {@link InlineHtmlNode} to visit
         */
        public void visit(InlineHtmlNode node) {
            addToken(node, INLINE_HTML);
        }

        /**
         * Visit the {@link ReferenceNode}.
         *
         * @param node the {@link ReferenceNode} to visit
         */
        public void visit(ReferenceNode node) {
            addToken(node, REFERENCE);
            visitChildren(node);
        }

        /**
         * Visit the {@link RefImageNode}.
         *
         * @param node the {@link RefImageNode} to visit
         */
        public void visit(RefImageNode node) {
            addToken(node, REFERENCE_IMAGE);
            visitChildren(node);
        }

        /**
         * Visit the {@link AbbreviationNode}.
         *
         * @param node the {@link AbbreviationNode} to visit
         */
        public void visit(AbbreviationNode node) {
            addToken(node, ABBREVIATION);
            visitChildren(node);
        }

        /**
         * Visit a {@link SuperNode}'s children.
         *
         * @param node the {@link Node} to visit
         */
        protected void visitChildren(SuperNode node) {
            for (Node child : node.getChildren()) child.accept(this);
        }

        /**
         * Add the given {@link Node} to the set of highlightable tokens.
         *
         * @param node      the {@link Node} to setup highlighting for
         * @param tokenType the {IElementType} to use for highlighting
         */
        protected void addToken(Node node, IElementType tokenType) {
            if (node.getStartIndex() < node.getEndIndex())
                tokens.add(new HighlightableToken(new TextRange(node.getStartIndex(), node.getEndIndex()), tokenType));
        }
    }
}
TOP

Related Classes of net.nicoulaj.idea.markdown.annotator.MarkdownAnnotator$MarkdownASTVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.