blob: 4dbd98b19ffc3433c545fe2a54d7457fc1d17bd5 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed 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 com.intellij.codeInsight.daemon.impl;
import com.intellij.codeInsight.daemon.impl.analysis.ErrorQuickFixProvider;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder;
import com.intellij.codeInsight.highlighting.HighlightErrorFilter;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageAnnotators;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.Annotator;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
/**
* @author yole
*/
public class DefaultHighlightVisitor implements HighlightVisitor, DumbAware {
private AnnotationHolderImpl myAnnotationHolder;
public static final ExtensionPointName<HighlightErrorFilter> FILTER_EP_NAME = ExtensionPointName.create("com.intellij.highlightErrorFilter");
private final HighlightErrorFilter[] myErrorFilters;
private final Project myProject;
private final boolean myHighlightErrorElements;
private final boolean myRunAnnotators;
private final DumbService myDumbService;
private HighlightInfoHolder myHolder;
private final boolean myBatchMode;
@SuppressWarnings("UnusedDeclaration")
public DefaultHighlightVisitor(@NotNull Project project) {
this(project, true, true, false);
}
public DefaultHighlightVisitor(@NotNull Project project, boolean highlightErrorElements, boolean runAnnotators, boolean batchMode) {
myProject = project;
myHighlightErrorElements = highlightErrorElements;
myRunAnnotators = runAnnotators;
myErrorFilters = Extensions.getExtensions(FILTER_EP_NAME, project);
myDumbService = DumbService.getInstance(project);
myBatchMode = batchMode;
}
@Override
public boolean suitableForFile(@NotNull final PsiFile file) {
return true;
}
@Override
public boolean analyze(@NotNull final PsiFile file,
final boolean updateWholeFile,
@NotNull final HighlightInfoHolder holder,
@NotNull final Runnable action) {
myHolder = holder;
myAnnotationHolder = new AnnotationHolderImpl(holder.getAnnotationSession(), myBatchMode);
try {
action.run();
}
finally {
myAnnotationHolder.clear();
myAnnotationHolder = null;
myHolder = null;
}
return true;
}
@Override
public void visit(@NotNull PsiElement element) {
if (element instanceof PsiErrorElement) {
if (myHighlightErrorElements) visitErrorElement((PsiErrorElement)element);
}
else {
if (myRunAnnotators) runAnnotators(element);
}
if (myAnnotationHolder.hasAnnotations()) {
for (Annotation annotation : myAnnotationHolder) {
myHolder.add(HighlightInfo.fromAnnotation(annotation, null, myBatchMode));
}
myAnnotationHolder.clear();
}
}
@Override
@NotNull
public HighlightVisitor clone() {
return new DefaultHighlightVisitor(myProject, myHighlightErrorElements, myRunAnnotators, myBatchMode);
}
@Override
public int order() {
return 2;
}
private static final ThreadLocalAnnotatorMap<Annotator,Language> cachedAnnotators = new ThreadLocalAnnotatorMap<Annotator, Language>() {
@NotNull
@Override
public Collection<Annotator> initialValue(@NotNull Language key) {
return LanguageAnnotators.INSTANCE.allForLanguage(key);
}
};
static {
LanguageAnnotators.INSTANCE.addListener(new ExtensionPointListener<Annotator>() {
@Override
public void extensionAdded(@NotNull Annotator extension, @Nullable PluginDescriptor pluginDescriptor) {
cachedAnnotators.clear();
}
@Override
public void extensionRemoved(@NotNull Annotator extension, @Nullable PluginDescriptor pluginDescriptor) {
cachedAnnotators.clear();
}
});
}
private void runAnnotators(PsiElement element) {
List<Annotator> annotators = cachedAnnotators.get(element.getLanguage());
if (annotators.isEmpty()) return;
final boolean dumb = myDumbService.isDumb();
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < annotators.size(); i++) {
Annotator annotator = annotators.get(i);
if (dumb && !DumbService.isDumbAware(annotator)) {
continue;
}
ProgressManager.checkCanceled();
annotator.annotate(element, myAnnotationHolder);
}
}
private void visitErrorElement(final PsiErrorElement element) {
for(HighlightErrorFilter errorFilter: myErrorFilters) {
if (!errorFilter.shouldHighlightErrorElement(element)) {
return;
}
}
HighlightInfo info = createErrorElementInfo(element);
myHolder.add(info);
}
private static HighlightInfo createErrorElementInfo(@NotNull PsiErrorElement element) {
TextRange range = element.getTextRange();
String errorDescription = element.getErrorDescription();
if (!range.isEmpty()) {
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range);
if (errorDescription != null) {
builder.descriptionAndTooltip(errorDescription);
}
final HighlightInfo info = builder.create();
if (info != null) {
for(ErrorQuickFixProvider provider: Extensions.getExtensions(ErrorQuickFixProvider.EP_NAME)) {
provider.registerErrorQuickFix(element, info);
}
}
return info;
}
int offset = range.getStartOffset();
PsiFile containingFile = element.getContainingFile();
int fileLength = containingFile.getTextLength();
FileViewProvider viewProvider = containingFile.getViewProvider();
PsiElement elementAtOffset = viewProvider.findElementAt(offset, viewProvider.getBaseLanguage());
String text = elementAtOffset == null ? null : elementAtOffset.getText();
HighlightInfo info;
if (offset < fileLength && text != null && !StringUtil.startsWithChar(text, '\n') && !StringUtil.startsWithChar(text, '\r')) {
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(offset, offset + 1);
if (errorDescription != null) {
builder.descriptionAndTooltip(errorDescription);
}
info = builder.create();
}
else {
int start;
int end;
if (offset > 0) {
start = offset/* - 1*/;
end = offset;
}
else {
start = offset;
end = offset < fileLength ? offset + 1 : offset;
}
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element, start, end);
if (errorDescription != null) {
builder.descriptionAndTooltip(errorDescription);
}
builder.endOfLine();
info = builder.create();
}
return info;
}
}