| /* |
| * Copyright 2000-2011 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.openapi.vcs.contentAnnotation; |
| |
| import com.intellij.execution.filters.ExceptionInfoCache; |
| import com.intellij.execution.filters.ExceptionWorker; |
| import com.intellij.execution.filters.Filter; |
| import com.intellij.execution.filters.FilterMixin; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.diff.DiffColors; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.colors.CodeInsightColors; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.colors.EditorColorsScheme; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.localVcs.UpToDateLineNumberProvider; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.Trinity; |
| import com.intellij.openapi.vcs.changes.ChangeListManager; |
| import com.intellij.openapi.vcs.history.VcsRevisionNumber; |
| import com.intellij.openapi.vcs.impl.UpToDateLineNumberProviderImpl; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.SmartList; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * Created by IntelliJ IDEA. |
| * User: Irina.Chernushina |
| * Date: 8/5/11 |
| * Time: 8:39 PM |
| */ |
| public class VcsContentAnnotationExceptionFilter implements Filter, FilterMixin { |
| private final Project myProject; |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.contentAnnotation.VcsContentAnnotationExceptionFilter"); |
| private final VcsContentAnnotationSettings mySettings; |
| private final Map<VirtualFile,VcsRevisionNumber> myRevNumbersCache; |
| private final ExceptionInfoCache myCache; |
| |
| public VcsContentAnnotationExceptionFilter(@NotNull GlobalSearchScope scope) { |
| myProject = scope.getProject(); |
| mySettings = VcsContentAnnotationSettings.getInstance(myProject); |
| myRevNumbersCache = new HashMap<VirtualFile, VcsRevisionNumber>(); |
| myCache = new ExceptionInfoCache(scope); |
| } |
| |
| private static class MyAdditionalHighlight extends AdditionalHighlight { |
| private MyAdditionalHighlight(int start, int end) { |
| super(start, end); |
| } |
| |
| @Override |
| public TextAttributes getTextAttributes(@Nullable TextAttributes source) { |
| EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme(); |
| final TextAttributes changedColor = globalScheme.getAttributes(DiffColors.DIFF_MODIFIED); |
| if (source == null) { |
| TextAttributes attrs = |
| globalScheme.getAttributes(CodeInsightColors.CLASS_NAME_ATTRIBUTES).clone(); |
| attrs.setBackgroundColor(changedColor.getBackgroundColor()); |
| return attrs; |
| } |
| TextAttributes clone = source.clone(); |
| clone.setBackgroundColor(changedColor.getBackgroundColor()); |
| return clone; |
| } |
| } |
| |
| @Override |
| public boolean shouldRunHeavy() { |
| return mySettings.isShow(); |
| } |
| |
| @Override |
| public void applyHeavyFilter(final Document copiedFragment, |
| int startOffset, |
| int startLineNumber, |
| Consumer<AdditionalHighlight> consumer) { |
| VcsContentAnnotation vcsContentAnnotation = VcsContentAnnotationImpl.getInstance(myProject); |
| final LocalChangesCorrector localChangesCorrector = new LocalChangesCorrector(myProject); |
| Trinity<PsiClass, PsiFile, String> previousLineResult = null; |
| |
| for (int i = 0; i < copiedFragment.getLineCount(); i++) { |
| final int lineStartOffset = copiedFragment.getLineStartOffset(i); |
| final int lineEndOffset = copiedFragment.getLineEndOffset(i); |
| final ExceptionWorker worker = new ExceptionWorker(myCache); |
| final String[] lineText = new String[1]; |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| lineText[0] = copiedFragment.getText(new TextRange(lineStartOffset, lineEndOffset)); |
| worker.execute(lineText[0], lineEndOffset); |
| } |
| }); |
| if (worker.getResult() != null) { |
| VirtualFile vf = worker.getFile().getVirtualFile(); |
| if (vf.getFileSystem().isReadOnly()) continue; |
| |
| VcsRevisionNumber recentChangeRevision = myRevNumbersCache.get(vf); |
| if (recentChangeRevision == null) { |
| recentChangeRevision = vcsContentAnnotation.fileRecentlyChanged(vf); |
| if (recentChangeRevision == null) { |
| myRevNumbersCache.put(vf, VcsRevisionNumber.NULL); |
| } else { |
| myRevNumbersCache.put(vf, recentChangeRevision); |
| } |
| } |
| if (VcsRevisionNumber.NULL.equals(recentChangeRevision)) { |
| recentChangeRevision = null; |
| } |
| if (localChangesCorrector.isFileAlreadyIdentifiedAsChanged(vf) || ChangeListManager.isFileChanged(myProject, vf) || |
| recentChangeRevision != null) { |
| final Document document = getDocumentForFile(worker); |
| if (document == null) return; |
| |
| int startFileOffset = worker.getInfo().getThird().getStartOffset(); |
| int idx = lineText[0].indexOf(':', startFileOffset); |
| int endIdx = idx == -1 ? worker.getInfo().getThird().getEndOffset() : idx; |
| consumer.consume(new MyAdditionalHighlight(startOffset + lineStartOffset + startFileOffset + 1, startOffset + lineStartOffset + endIdx)); |
| |
| if (worker.getPsiClass() != null) { |
| // also check method |
| final List<TextRange> ranges = findMethodRange(worker, document, previousLineResult); |
| if (ranges != null) { |
| boolean methodChanged = false; |
| for (TextRange range : ranges) { |
| if (localChangesCorrector.isRangeChangedLocally(vf, document, range)) { |
| methodChanged = true; |
| break; |
| } |
| final TextRange correctedRange = localChangesCorrector.getCorrectedRange(vf, document, range); |
| if (vcsContentAnnotation.intervalRecentlyChanged(vf, correctedRange, recentChangeRevision)) { |
| methodChanged = true; |
| break; |
| } |
| } |
| if (methodChanged) { |
| consumer.consume(new MyAdditionalHighlight(startOffset + lineStartOffset + worker.getInfo().getSecond().getStartOffset(), |
| startOffset + lineStartOffset + worker.getInfo().getSecond().getEndOffset())); |
| } |
| } |
| } |
| } |
| } |
| previousLineResult = worker.getResult() == null ? null : |
| new Trinity<PsiClass, PsiFile, String>(worker.getPsiClass(), worker.getFile(), worker.getMethod()); |
| } |
| } |
| |
| @Override |
| public String getUpdateMessage() { |
| return "Checking recent changes..."; |
| } |
| |
| private static class LocalChangesCorrector { |
| private final Map<VirtualFile, UpToDateLineNumberProvider> myRecentlyChanged; |
| private final Project myProject; |
| |
| private LocalChangesCorrector(final Project project) { |
| myProject = project; |
| myRecentlyChanged = new HashMap<VirtualFile, UpToDateLineNumberProvider>(); |
| } |
| |
| public boolean isFileAlreadyIdentifiedAsChanged(final VirtualFile vf) { |
| return myRecentlyChanged.containsKey(vf); |
| } |
| |
| public boolean isRangeChangedLocally(final VirtualFile vf, final Document document, final TextRange range) { |
| final UpToDateLineNumberProvider provider = getProvider(vf, document); |
| return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() { |
| @Override |
| public Boolean compute() { |
| return provider.isRangeChanged(range.getStartOffset(), range.getEndOffset()); |
| } |
| }); |
| } |
| |
| public TextRange getCorrectedRange(final VirtualFile vf, final Document document, final TextRange range) { |
| final UpToDateLineNumberProvider provider = getProvider(vf, document); |
| if (provider == null) return range; |
| return ApplicationManager.getApplication().runReadAction(new Computable<TextRange>() { |
| @Override |
| public TextRange compute() { |
| return new TextRange(provider.getLineNumber(range.getStartOffset()), provider.getLineNumber(range.getEndOffset())); |
| } |
| }); |
| } |
| |
| private UpToDateLineNumberProvider getProvider(VirtualFile vf, Document document) { |
| UpToDateLineNumberProvider provider = myRecentlyChanged.get(vf); |
| if (provider == null) { |
| provider = new UpToDateLineNumberProviderImpl(document, myProject); |
| myRecentlyChanged.put(vf, provider); |
| } |
| return provider; |
| } |
| } |
| |
| private static Document getDocumentForFile(final ExceptionWorker worker) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<Document>() { |
| @Override |
| public Document compute() { |
| final Document document = FileDocumentManager.getInstance().getDocument(worker.getFile().getVirtualFile()); |
| if (document == null) { |
| LOG.info("can not get document for file: " + worker.getFile().getVirtualFile()); |
| return null; |
| } |
| return document; |
| } |
| }); |
| } |
| |
| // line numbers |
| private static List<TextRange> findMethodRange(final ExceptionWorker worker, |
| final Document document, |
| final Trinity<PsiClass, PsiFile, String> previousLineResult) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<List<TextRange>>() { |
| @Override |
| public List<TextRange> compute() { |
| List<TextRange> ranges = getTextRangeForMethod(worker, previousLineResult); |
| if (ranges == null) return null; |
| final List<TextRange> result = new ArrayList<TextRange>(); |
| for (TextRange range : ranges) { |
| result.add(new TextRange(document.getLineNumber(range.getStartOffset()), |
| document.getLineNumber(range.getEndOffset()))); |
| } |
| return result; |
| } |
| }); |
| } |
| |
| // null - check all |
| @Nullable |
| private static List<PsiMethod> selectMethod(final PsiMethod[] methods, final Trinity<PsiClass, PsiFile, String> previousLineResult) { |
| if (previousLineResult == null || previousLineResult.getThird() == null) return null; |
| |
| final List<PsiMethod> result = new SmartList<PsiMethod>(); |
| for (final PsiMethod method : methods) { |
| method.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitCallExpression(PsiCallExpression callExpression) { |
| final PsiMethod resolved = callExpression.resolveMethod(); |
| if (resolved != null) { |
| if (resolved.getName().equals(previousLineResult.getThird())) { |
| result.add(method); |
| } |
| } |
| } |
| }); |
| } |
| |
| return result; |
| } |
| |
| private static List<TextRange> getTextRangeForMethod(final ExceptionWorker worker, Trinity<PsiClass, PsiFile, String> previousLineResult) { |
| String method = worker.getMethod(); |
| PsiClass psiClass = worker.getPsiClass(); |
| PsiMethod[] methods; |
| if (method.contains("<init>")) { |
| // constructor |
| methods = psiClass.getConstructors(); |
| } else if (method.contains("$")) { |
| // access$100 |
| return null; |
| } else { |
| methods = psiClass.findMethodsByName(method, false); |
| } |
| if (methods.length > 0) { |
| if (methods.length == 1) { |
| final TextRange range = methods[0].getTextRange(); |
| return Collections.singletonList(range); |
| } else { |
| List<PsiMethod> selectedMethods = selectMethod(methods, previousLineResult); |
| final List<PsiMethod> toIterate = selectedMethods == null ? Arrays.asList(methods) : selectedMethods; |
| final List<TextRange> result = new ArrayList<TextRange>(); |
| for (PsiMethod psiMethod : toIterate) { |
| result.add(psiMethod.getTextRange()); |
| } |
| return result; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Result applyFilter(String line, int entireLength) { |
| return null; |
| } |
| } |