/* * 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; public class TextEditorHighlightingPassRegistrarImpl extends TextEditorHighlightingPassRegistrarEx { private final TIntObjectHashMap<PassConfig> myRegisteredPassFactories = new TIntObjectHashMap<>(); private final List<DirtyScopeTrackingHighlightingPassFactory> myDirtyScopeTrackingFactories = new ArrayList<>(); 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<>(); final TIntArrayList passesRefusedToCreate = new TIntArrayList(); myRegisteredPassFactories.forEachKey(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(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<>(); myRegisteredPassFactories.forEachKey(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<>(ids); } private void checkForCycles() { final TIntObjectHashMap<TIntHashSet> transitivePredecessors = new TIntObjectHashMap<>(); myRegisteredPassFactories.forEachEntry((passId, config) -> { TIntHashSet allPredecessors = new TIntHashSet(config.completionPredecessorIds); allPredecessors.addAll(config.startingPredecessorIds); transitivePredecessors.put(passId, allPredecessors); allPredecessors.forEach(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(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; } }