package org.plantuml.idea.lang.annotator; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.ExternalAnnotator; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import net.sourceforge.plantuml.FileSystem; import net.sourceforge.plantuml.syntax.SyntaxChecker; import net.sourceforge.plantuml.syntax.SyntaxResult; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.plantuml.idea.lang.settings.PlantUmlSettings; import org.plantuml.idea.plantuml.PlantUml; import org.plantuml.idea.plantuml.PlantUmlIncludes; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.lang.System.currentTimeMillis; import static org.plantuml.idea.lang.annotator.LanguageDescriptor.IDEA_DISABLE_SYNTAX_CHECK; /** * Author: Eugene Steinberg * Date: 9/13/14 */ public class PlantUmlExternalAnnotator extends ExternalAnnotator<PsiFile, FileAnnotationResult> { private static final Logger logger = Logger.getInstance(PlantUmlExternalAnnotator.class); @Nullable @Override public PsiFile collectInformation(@NotNull PsiFile file) { return file; } @Nullable @Override public FileAnnotationResult doAnnotate(PsiFile file) { FileAnnotationResult result = new FileAnnotationResult(); if (PlantUmlSettings.getInstance().isErrorAnnotationEnabled()) { String text = file.getFirstChild().getText(); Map<Integer, String> sources = PlantUml.extractSources(text); for (Map.Entry<Integer, String> sourceData : sources.entrySet()) { Integer sourceOffset = sourceData.getKey(); SourceAnnotationResult sourceAnnotationResult = new SourceAnnotationResult(sourceOffset); String source = sourceData.getValue(); sourceAnnotationResult.addAll(annotateSyntaxErrors(file, source)); sourceAnnotationResult.addAll(annotateSyntaxHighlight(source, LanguagePatternHolder.INSTANCE.keywordsPattern, DefaultLanguageHighlighterColors.KEYWORD)); sourceAnnotationResult.addAll(annotateSyntaxHighlight(source, LanguagePatternHolder.INSTANCE.pluginSettingsPattern, DefaultLanguageHighlighterColors.KEYWORD)); sourceAnnotationResult.addAll(annotateSyntaxHighlight(source, LanguagePatternHolder.INSTANCE.typesPattern, DefaultLanguageHighlighterColors.LABEL)); sourceAnnotationResult.addAll(annotateSyntaxHighlight(source, LanguagePatternHolder.INSTANCE.preprocPattern, DefaultLanguageHighlighterColors.METADATA)); result.add(sourceAnnotationResult); } } return result; } @Nullable private Collection<SourceAnnotation> annotateSyntaxErrors(PsiFile file, String source) { if (source.contains(IDEA_DISABLE_SYNTAX_CHECK)) { return Collections.emptyList(); } Collection<SourceAnnotation> result = new ArrayList<SourceAnnotation>(); long start = currentTimeMillis(); SyntaxResult syntaxResult = checkSyntax(file, source); logger.debug("syntax checked in ", currentTimeMillis() - start, "ms"); if (syntaxResult.isError()) { String beforeInclude = StringUtils.substringBefore(source, "!include"); int includeLineNumber = StringUtils.splitPreserveAllTokens(beforeInclude, "\n").length; //todo hack because plantuml returns line number from source with inlined includes if (syntaxResult.getErrorLinePosition() < includeLineNumber) { ErrorSourceAnnotation errorSourceAnnotation = new ErrorSourceAnnotation( syntaxResult.getErrors(), syntaxResult.getSuggest(), syntaxResult.getErrorLinePosition() ); result.add(errorSourceAnnotation); } } return result; } private SyntaxResult checkSyntax(PsiFile file, String source) { try { File baseDir = new File(file.getVirtualFile().getParent().getPath()); FileSystem.getInstance().setCurrentDir(baseDir); PlantUmlIncludes.commitIncludes(source, baseDir); return SyntaxChecker.checkSyntaxFair(source); } finally { FileSystem.getInstance().reset(); } } private Collection<SourceAnnotation> annotateSyntaxHighlight(String source, Pattern pattern, TextAttributesKey textAttributesKey) { Collection<SourceAnnotation> result = new ArrayList<SourceAnnotation>(); Matcher matcher = pattern.matcher(source); while (matcher.find()) { result.add(new SyntaxHighlightAnnotation(matcher.start(), matcher.end(), textAttributesKey)); } return result; } @Override public void apply(@NotNull PsiFile file, FileAnnotationResult fileAnnotationResult, @NotNull AnnotationHolder holder) { if (null != fileAnnotationResult) { Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); if (document != null) { fileAnnotationResult.annotate(holder, document); } } } }