blob: 87852f2f9ca7175b3f2ef476939c8b322aa6952a [file] [log] [blame]
/*
* Copyright 2000-2009 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.codeHighlighting.*;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiCompiledElement;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.ArrayUtil;
import gnu.trove.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* User: anna
* Date: 19-Apr-2006
*/
public class TextEditorHighlightingPassRegistrarImpl extends TextEditorHighlightingPassRegistrarEx {
private final TIntObjectHashMap<PassConfig> myRegisteredPassFactories = new TIntObjectHashMap<PassConfig>();
private final List<DirtyScopeTrackingHighlightingPassFactory> myDirtyScopeTrackingFactories = new ArrayList<DirtyScopeTrackingHighlightingPassFactory>();
private int nextAvailableId = Pass.LAST_PASS + 1;
private boolean checkedForCycles;
private final Project myProject;
public TextEditorHighlightingPassRegistrarImpl(Project project) {
myProject = project;
}
private static class PassConfig {
private final TextEditorHighlightingPassFactory passFactory;
private final int[] startingPredecessorIds;
private final int[] completionPredecessorIds;
private PassConfig(@NotNull TextEditorHighlightingPassFactory passFactory, @NotNull int[] completionPredecessorIds, @NotNull int[] startingPredecessorIds) {
this.completionPredecessorIds = completionPredecessorIds;
this.startingPredecessorIds = startingPredecessorIds;
this.passFactory = passFactory;
}
}
@Override
public void registerTextEditorHighlightingPass(TextEditorHighlightingPassFactory factory, int anchor, int anchorPass) {
Anchor anc = Anchor.FIRST;
switch (anchor) {
case FIRST : anc = Anchor.FIRST;
break;
case LAST : anc = Anchor.LAST;
break;
case BEFORE : anc = Anchor.BEFORE;
break;
case AFTER : anc = Anchor.AFTER;
break;
}
registerTextEditorHighlightingPass(factory, anc, anchorPass, true, true);
}
@Override
public synchronized int registerTextEditorHighlightingPass(@NotNull TextEditorHighlightingPassFactory factory,
@Nullable int[] runAfterCompletionOf,
@Nullable int[] runAfterOfStartingOf,
boolean runIntentionsPassAfter,
int forcedPassId) {
assert !checkedForCycles;
PassConfig info = new PassConfig(factory,
runAfterCompletionOf == null || runAfterCompletionOf.length == 0 ? ArrayUtil.EMPTY_INT_ARRAY : runAfterCompletionOf,
runAfterOfStartingOf == null || runAfterOfStartingOf.length == 0 ? ArrayUtil.EMPTY_INT_ARRAY : runAfterOfStartingOf);
int passId = forcedPassId == -1 ? nextAvailableId++ : forcedPassId;
PassConfig registered = myRegisteredPassFactories.get(passId);
assert registered == null: "Pass id "+passId +" has already been registered in: "+ registered.passFactory;
myRegisteredPassFactories.put(passId, info);
if (factory instanceof DirtyScopeTrackingHighlightingPassFactory) {
myDirtyScopeTrackingFactories.add((DirtyScopeTrackingHighlightingPassFactory) factory);
}
return passId;
}
@Override
@NotNull
public List<TextEditorHighlightingPass> instantiatePasses(@NotNull final PsiFile psiFile, @NotNull final Editor editor, @NotNull final int[] passesToIgnore) {
synchronized (this) {
if (!checkedForCycles) {
checkedForCycles = true;
checkForCycles();
}
}
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
final Document document = editor.getDocument();
PsiFile fileFromDoc = documentManager.getPsiFile(document);
if (!(fileFromDoc instanceof PsiCompiledElement)) {
assert fileFromDoc == psiFile : "Files are different: " + psiFile + ";" + fileFromDoc;
Document documentFromFile = documentManager.getDocument(psiFile);
assert documentFromFile == document : "Documents are different. Doc: " + document + "; Doc from file: " + documentFromFile +"; File: "+psiFile +"; Virtual file: "+
PsiUtilCore.getVirtualFile(psiFile);
}
final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass = new TIntObjectHashMap<TextEditorHighlightingPass>();
final TIntArrayList passesRefusedToCreate = new TIntArrayList();
myRegisteredPassFactories.forEachKey(new TIntProcedure() {
@Override
public boolean execute(int passId) {
if (ArrayUtil.find(passesToIgnore, passId) != -1) {
return true;
}
PassConfig passConfig = myRegisteredPassFactories.get(passId);
TextEditorHighlightingPassFactory factory = passConfig.passFactory;
final TextEditorHighlightingPass pass = factory.createHighlightingPass(psiFile, editor);
if (pass == null) {
passesRefusedToCreate.add(passId);
}
else {
// init with editor's colors scheme
pass.setColorsScheme(editor.getColorsScheme());
TIntArrayList ids = new TIntArrayList(passConfig.completionPredecessorIds.length);
for (int id : passConfig.completionPredecessorIds) {
if (myRegisteredPassFactories.containsKey(id)) ids.add(id);
}
pass.setCompletionPredecessorIds(ids.isEmpty() ? ArrayUtil.EMPTY_INT_ARRAY : ids.toNativeArray());
ids = new TIntArrayList(passConfig.startingPredecessorIds.length);
for (int id : passConfig.startingPredecessorIds) {
if (myRegisteredPassFactories.containsKey(id)) ids.add(id);
}
pass.setStartingPredecessorIds(ids.isEmpty() ? ArrayUtil.EMPTY_INT_ARRAY : ids.toNativeArray());
pass.setId(passId);
id2Pass.put(passId, pass);
}
return true;
}
});
DaemonCodeAnalyzerEx daemonCodeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(myProject);
final FileStatusMap statusMap = daemonCodeAnalyzer.getFileStatusMap();
passesRefusedToCreate.forEach(new TIntProcedure() {
@Override
public boolean execute(int passId) {
statusMap.markFileUpToDate(document, passId);
return true;
}
});
return (List)Arrays.asList(id2Pass.getValues());
}
@NotNull
@Override
public List<TextEditorHighlightingPass> instantiateMainPasses(@NotNull final PsiFile psiFile,
@NotNull final Document document,
@NotNull final HighlightInfoProcessor highlightInfoProcessor) {
final THashSet<TextEditorHighlightingPass> ids = new THashSet<TextEditorHighlightingPass>();
myRegisteredPassFactories.forEachKey(new TIntProcedure() {
@Override
public boolean execute(int passId) {
PassConfig passConfig = myRegisteredPassFactories.get(passId);
TextEditorHighlightingPassFactory factory = passConfig.passFactory;
if (factory instanceof MainHighlightingPassFactory) {
final TextEditorHighlightingPass pass = ((MainHighlightingPassFactory)factory).createMainHighlightingPass(psiFile, document, highlightInfoProcessor);
if (pass != null) {
ids.add(pass);
pass.setId(passId);
}
}
return true;
}
});
return new ArrayList<TextEditorHighlightingPass>(ids);
}
private void checkForCycles() {
final TIntObjectHashMap<TIntHashSet> transitivePredecessors = new TIntObjectHashMap<TIntHashSet>();
myRegisteredPassFactories.forEachEntry(new TIntObjectProcedure<PassConfig>() {
@Override
public boolean execute(int passId, PassConfig config) {
TIntHashSet allPredecessors = new TIntHashSet(config.completionPredecessorIds);
allPredecessors.addAll(config.startingPredecessorIds);
transitivePredecessors.put(passId, allPredecessors);
allPredecessors.forEach(new TIntProcedure() {
@Override
public boolean execute(int predecessorId) {
PassConfig predecessor = myRegisteredPassFactories.get(predecessorId);
if (predecessor == null) return true;
TIntHashSet transitives = transitivePredecessors.get(predecessorId);
if (transitives == null) {
transitives = new TIntHashSet();
transitivePredecessors.put(predecessorId, transitives);
}
transitives.addAll(predecessor.completionPredecessorIds);
transitives.addAll(predecessor.startingPredecessorIds);
return true;
}
});
return true;
}
});
transitivePredecessors.forEachKey(new TIntProcedure() {
@Override
public boolean execute(int passId) {
if (transitivePredecessors.get(passId).contains(passId)) {
throw new IllegalArgumentException("There is a cycle introduced involving pass " + myRegisteredPassFactories.get(passId).passFactory);
}
return true;
}
});
}
@NotNull
@Override
public List<DirtyScopeTrackingHighlightingPassFactory> getDirtyScopeTrackingFactories() {
return myDirtyScopeTrackingFactories;
}
}