diff --git a/platform/analysis-impl/src/com/intellij/codeHighlighting/EditorBoundHighlightingPass.java b/platform/analysis-impl/src/com/intellij/codeHighlighting/EditorBoundHighlightingPass.java new file mode 100644 index 000000000000..86120b597839 --- /dev/null +++ b/platform/analysis-impl/src/com/intellij/codeHighlighting/EditorBoundHighlightingPass.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2014 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.codeHighlighting; + +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +/** + * The pass which should be applied to every editor, even if there are many for this document. + * + * Ordinary {@link TextEditorHighlightingPass} is document-bound, + * i.e. after the pass finishes the markup is stored in the document. + * For example, there is no point to recalculate syntax errors for each splitted editor of the same document. + * This pass however is for editor-specific markup, e.g. code folding. + */ +public abstract class EditorBoundHighlightingPass extends TextEditorHighlightingPass { + @NotNull protected final Editor myEditor; + @NotNull protected final PsiFile myFile; + + protected EditorBoundHighlightingPass(@NotNull Editor editor, + @NotNull PsiFile psiFile, + boolean runIntentionPassAfter) { + super(psiFile.getProject(), editor.getDocument(), runIntentionPassAfter); + myEditor = editor; + myFile = psiFile; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CodeFoldingPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CodeFoldingPass.java index 6e1c9a461dbe..8c8fd2c87482 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CodeFoldingPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CodeFoldingPass.java @@ -16,7 +16,7 @@ package com.intellij.codeInsight.daemon.impl; -import com.intellij.codeHighlighting.TextEditorHighlightingPass; +import com.intellij.codeHighlighting.EditorBoundHighlightingPass; import com.intellij.codeInsight.folding.CodeFoldingManager; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.editor.Editor; @@ -24,30 +24,22 @@ import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.PossiblyDumbAware; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; -class CodeFoldingPass extends TextEditorHighlightingPass implements PossiblyDumbAware { +class CodeFoldingPass extends EditorBoundHighlightingPass implements PossiblyDumbAware { private static final Key THE_FIRST_TIME = Key.create("FirstFoldingPass"); - private Runnable myRunnable; - private final Editor myEditor; - private final PsiFile myFile; + private volatile Runnable myRunnable; - CodeFoldingPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { - super(project, editor.getDocument(), false); - myEditor = editor; - myFile = file; + CodeFoldingPass(@NotNull Editor editor, @NotNull PsiFile file) { + super(editor, file, false); } @Override public void doCollectInformation(@NotNull ProgressIndicator progress) { final boolean firstTime = isFirstTime(myFile, myEditor, THE_FIRST_TIME); - Runnable runnable = CodeFoldingManager.getInstance(myProject).updateFoldRegionsAsync(myEditor, firstTime); - synchronized (this) { - myRunnable = runnable; - } + myRunnable = CodeFoldingManager.getInstance(myProject).updateFoldRegionsAsync(myEditor, firstTime); } static boolean isFirstTime(PsiFile file, Editor editor, Key key) { @@ -61,10 +53,7 @@ class CodeFoldingPass extends TextEditorHighlightingPass implements PossiblyDumb @Override public void doApplyInformationToEditor() { - Runnable runnable; - synchronized (this) { - runnable = myRunnable; - } + Runnable runnable = myRunnable; if (runnable != null){ try { runnable.run(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CodeFoldingPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CodeFoldingPassFactory.java index c86df2e038ed..5d9f85441136 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CodeFoldingPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CodeFoldingPassFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -46,6 +46,6 @@ public class CodeFoldingPassFactory extends AbstractProjectComponent implements @Override @NotNull public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull final Editor editor) { - return new CodeFoldingPass(myProject, editor, file); + return new CodeFoldingPass(editor, file); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java index aff59a8406ee..65d302e8ec21 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java @@ -27,7 +27,6 @@ import com.intellij.codeInsight.daemon.ReferenceImporter; import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.intention.impl.FileLevelIntentionComponent; import com.intellij.codeInsight.intention.impl.IntentionHintComponent; -import com.intellij.concurrency.Job; import com.intellij.ide.PowerSaveMode; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.Disposable; @@ -81,7 +80,7 @@ import java.util.*; /** * This class also controls the auto-reparse and auto-hints. */ -public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOMExternalizable, NamedComponent { +public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOMExternalizable, NamedComponent, Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl"); private static final Key> MARKERS_IN_EDITOR_DOCUMENT_KEY = Key.create("MARKERS_IN_EDITOR_DOCUMENT"); @@ -115,41 +114,27 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM public DaemonCodeAnalyzerImpl(@NotNull Project project, @NotNull DaemonCodeAnalyzerSettings daemonCodeAnalyzerSettings, @NotNull EditorTracker editorTracker, - @NotNull final NamedScopeManager namedScopeManager, - @NotNull final DependencyValidationManager dependencyValidationManager) { + @SuppressWarnings("UnusedParameters") @NotNull final NamedScopeManager namedScopeManager, + @SuppressWarnings("UnusedParameters") @NotNull final DependencyValidationManager dependencyValidationManager) { myProject = project; mySettings = daemonCodeAnalyzerSettings; myEditorTracker = editorTracker; myLastSettings = ((DaemonCodeAnalyzerSettingsImpl)daemonCodeAnalyzerSettings).clone(); - myFileStatusMap = new FileStatusMap(myProject); - myPassExecutorService = new PassExecutorService(myProject) { - @Override - protected void afterApplyInformationToEditor(final TextEditorHighlightingPass pass, - @NotNull final FileEditor fileEditor, - final ProgressIndicator updateProgress) { - if (fileEditor instanceof TextEditor) { - log(updateProgress, pass, "Apply "); - } - } - - @Override - protected boolean isDisposed() { - return myDisposed || super.isDisposed(); - } - }; - Disposer.register(project, myPassExecutorService); - Disposer.register(project, myFileStatusMap); + myFileStatusMap = new FileStatusMap(project); + myPassExecutorService = new PassExecutorService(project); + Disposer.register(this, myPassExecutorService); + Disposer.register(this, myFileStatusMap); DaemonProgressIndicator.setDebug(LOG.isDebugEnabled()); assert !myInitialized : "Double Initializing"; - Disposer.register(myProject, new StatusBarUpdater(myProject)); + Disposer.register(this, new StatusBarUpdater(project)); myInitialized = true; myDisposed = false; myFileStatusMap.markAllFilesDirty(); - Disposer.register(project, new Disposable() { + Disposer.register(this, new Disposable() { @Override public void dispose() { assert myInitialized : "Disposing not initialized component"; @@ -163,6 +148,11 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM }); } + @Override + public void dispose() { + + } + @NotNull @TestOnly public static List getHighlights(@NotNull Document document, HighlightSeverity minSeverity, @NotNull Project project) { @@ -270,6 +260,17 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM @NotNull int[] toIgnore, boolean canChangeDocument, @Nullable Runnable callbackWhileWaiting) throws ProcessCanceledException { + return runPasses(file, document, Collections.singletonList(textEditor), toIgnore, canChangeDocument, callbackWhileWaiting); + } + + @NotNull + @TestOnly + public List runPasses(@NotNull PsiFile file, + @NotNull Document document, + @NotNull List textEditors, + @NotNull int[] toIgnore, + boolean canChangeDocument, + @Nullable Runnable callbackWhileWaiting) throws ProcessCanceledException { assert myInitialized; assert !myDisposed; Application application = ApplicationManager.getApplication(); @@ -291,13 +292,16 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM } fileStatusMap.allowDirt(canChangeDocument); - TextEditorBackgroundHighlighter highlighter = (TextEditorBackgroundHighlighter)textEditor.getBackgroundHighlighter(); - final List passes = highlighter.getPasses(toIgnore); - HighlightingPass[] array = passes.toArray(new HighlightingPass[passes.size()]); - assert array.length != 0 : "Highlighting is disabled for the file " + file; - + Map map = new HashMap(); + for (TextEditor textEditor : textEditors) { + TextEditorBackgroundHighlighter highlighter = (TextEditorBackgroundHighlighter)textEditor.getBackgroundHighlighter(); + final List passes = highlighter.getPasses(toIgnore); + HighlightingPass[] array = passes.toArray(new HighlightingPass[passes.size()]); + assert array.length != 0 : "Highlighting is disabled for the file " + file; + map.put(textEditor, array); + } final DaemonProgressIndicator progress = createUpdateProgress(); - myPassExecutorService.submitPasses(Collections.singletonMap((FileEditor)textEditor, array), progress, Job.DEFAULT_PRIORITY); + myPassExecutorService.submitPasses(map, progress); try { while (progress.isRunning()) { try { @@ -323,8 +327,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM UIUtil.dispatchAllInvocationEvents(); UIUtil.dispatchAllInvocationEvents(); - List highlights = getHighlights(document, null, project); - return highlights; + return getHighlights(document, null, project); } finally { fileStatusMap.allowDirt(true); @@ -738,7 +741,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements JDOM cancelUpdateProgress(true, "Cancel by alarm"); myAlarm.cancelAllRequests(); DaemonProgressIndicator progress = createUpdateProgress(); - myPassExecutorService.submitPasses(passes, progress, Job.DEFAULT_PRIORITY); + myPassExecutorService.submitPasses(passes, progress); } }; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java index 3b40edb59869..12f04344aa09 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java @@ -16,6 +16,7 @@ package com.intellij.codeInsight.daemon.impl; +import com.intellij.codeHighlighting.EditorBoundHighlightingPass; import com.intellij.codeHighlighting.HighlightingPass; import com.intellij.codeHighlighting.TextEditorHighlightingPass; import com.intellij.concurrency.Job; @@ -38,18 +39,18 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ConcurrencyUtil; import com.intellij.util.Consumer; -import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; -import gnu.trove.THashMap; +import com.intellij.util.containers.MultiMap; +import gnu.trove.*; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.util.*; @@ -59,15 +60,16 @@ import java.util.concurrent.atomic.AtomicInteger; /** * @author cdr */ -public abstract class PassExecutorService implements Disposable { +public class PassExecutorService implements Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PassExecutorService"); + private static final boolean CHECK_CONSISTENCY = ApplicationManager.getApplication().isUnitTestMode(); private final Map> mySubmittedPasses = new ConcurrentHashMap>(); private final Project myProject; - private volatile boolean isDisposed; + protected volatile boolean isDisposed; private final AtomicInteger nextPassId = new AtomicInteger(100); - public PassExecutorService(Project project) { + public PassExecutorService(@NotNull Project project) { myProject = project; } @@ -97,15 +99,13 @@ public abstract class PassExecutorService implements Disposable { mySubmittedPasses.clear(); } - public void submitPasses(@NotNull Map passesMap, @NotNull DaemonProgressIndicator updateProgress, final int jobPriority) { + public void submitPasses(@NotNull Map passesMap, @NotNull DaemonProgressIndicator updateProgress) { if (isDisposed()) return; - int id = 1; - // (doc, passId) -> created pass - Map, ScheduledPass> toBeSubmitted = new THashMap, ScheduledPass>(passesMap.size()); // null keys are ok - Map> documentToEditors = new HashMap>(); - Map> textPasses = new HashMap>(passesMap.size()); + MultiMap documentToEditors = MultiMap.createSet(); + MultiMap documentBoundPasses = MultiMap.createSmartList(); + MultiMap editorBoundPasses = MultiMap.createSmartList(); for (Map.Entry entry : passesMap.entrySet()) { FileEditor fileEditor = entry.getKey(); HighlightingPass[] passes = entry.getValue(); @@ -116,83 +116,59 @@ public abstract class PassExecutorService implements Disposable { document = editor.getDocument(); } - for (int i = 0; i < passes.length; i++) { - final HighlightingPass pass = passes[i]; - - TextEditorHighlightingPass textEditorHighlightingPass; - if (pass instanceof TextEditorHighlightingPass) { - textEditorHighlightingPass = (TextEditorHighlightingPass)pass; + int prevId = 0; + for (final HighlightingPass pass : passes) { + if (pass instanceof EditorBoundHighlightingPass) { + EditorBoundHighlightingPass editorPass = (EditorBoundHighlightingPass)pass; + editorPass.setId(nextPassId.incrementAndGet()); // have to make ids unique for this document + editorBoundPasses.putValue(fileEditor, editorPass); } else { - // run all passes in sequence - textEditorHighlightingPass = new TextEditorHighlightingPass(myProject, document, true) { - @Override - public void doCollectInformation(@NotNull ProgressIndicator progress) { - pass.collectInformation(progress); - } - - @Override - public void doApplyInformationToEditor() { - pass.applyInformationToEditor(); - } - }; - textEditorHighlightingPass.setId(id++); - if (i > 0) { - textEditorHighlightingPass.setCompletionPredecessorIds(new int[]{i - 1}); - } + TextEditorHighlightingPass textEditorHighlightingPass = convertToTextHighlightingPass(pass, document, nextPassId, prevId); + document = textEditorHighlightingPass.getDocument(); + documentBoundPasses.putValue(fileEditor, textEditorHighlightingPass); + documentToEditors.putValue(document, fileEditor); + prevId = textEditorHighlightingPass.getId(); } - document = textEditorHighlightingPass.getDocument(); - - List textPassesForDocument = textPasses.get(fileEditor); - if (textPassesForDocument == null) { - textPassesForDocument = new SmartList(); - textPasses.put(fileEditor, textPassesForDocument); - } - textPassesForDocument.add(textEditorHighlightingPass); - - List editors = documentToEditors.get(document); - if (editors == null) { - editors = new SmartList(); - documentToEditors.put(document, editors); - } - if (!editors.contains(fileEditor)) editors.add(fileEditor); } } List freePasses = new ArrayList(documentToEditors.size()*5); List dependentPasses = new ArrayList(documentToEditors.size()*10); + // (fileEditor, passId) -> created pass + Map, ScheduledPass> toBeSubmitted = new THashMap, ScheduledPass>(passesMap.size()); + final AtomicInteger threadsToStartCountdown = new AtomicInteger(0); - for (Map.Entry> entry : documentToEditors.entrySet()) { - final List fileEditors = entry.getValue(); - List passes = textPasses.get(getPreferredFileEditor(entry.getKey(), fileEditors)); - if (passes == null) { + for (Map.Entry> entry : documentToEditors.entrySet()) { + Collection fileEditors = entry.getValue(); + Document document = entry.getKey(); + FileEditor preferredFileEditor = getPreferredFileEditor(document, fileEditors); + List passes = (List)documentBoundPasses.get(preferredFileEditor); + if (passes.isEmpty()) { continue; } - threadsToStartCountdown.addAndGet(passes.size()); - - // create one scheduled pass per unique id (possibly for multiple file editors. they all will be applied at the pass finish) - ContainerUtil.quickSort(passes, new Comparator() { - @Override - public int compare(final TextEditorHighlightingPass o1, final TextEditorHighlightingPass o2) { - return o1.getId() - o2.getId(); - } - }); - int passId = -1; - TextEditorHighlightingPass currentPass = null; - for (int i = 0; i <= passes.size(); i++) { - int newId = -1; - if (i < passes.size()) { - currentPass = passes.get(i); - newId = currentPass.getId(); - } - if (newId != passId) { - createScheduledPass(fileEditors, currentPass, toBeSubmitted, passes, freePasses, dependentPasses, updateProgress, threadsToStartCountdown, - jobPriority); - passId = newId; - } + sortById(passes); + for (TextEditorHighlightingPass currentPass : passes) { + createScheduledPass(preferredFileEditor, currentPass, toBeSubmitted, passes, freePasses, dependentPasses, updateProgress, threadsToStartCountdown); } } + for (Map.Entry> entry : editorBoundPasses.entrySet()) { + FileEditor fileEditor = entry.getKey(); + Collection createdEditorBoundPasses = entry.getValue(); + List createdDocumentBoundPasses = (List)documentBoundPasses.get(fileEditor); + List allCreatedPasses = new ArrayList(createdDocumentBoundPasses); + allCreatedPasses.addAll(createdEditorBoundPasses); + + for (EditorBoundHighlightingPass pass : createdEditorBoundPasses) { + createScheduledPass(fileEditor, pass, toBeSubmitted, allCreatedPasses, freePasses, dependentPasses, updateProgress, threadsToStartCountdown); + } + } + + if (CHECK_CONSISTENCY) { + assertConsistency(freePasses, toBeSubmitted, threadsToStartCountdown); + } + log(updateProgress, null, "---------------------starting------------------------ " + threadsToStartCountdown.get(), freePasses); for (ScheduledPass dependentPass : dependentPasses) { @@ -203,54 +179,117 @@ public abstract class PassExecutorService implements Disposable { } } - @Nullable - private FileEditor getPreferredFileEditor(Document document, @NotNull List fileEditors) { + private void assertConsistency(List freePasses, + Map, ScheduledPass> toBeSubmitted, + AtomicInteger threadsToStartCountdown) { + assert threadsToStartCountdown.get() == toBeSubmitted.size(); + TIntObjectHashMap> id2Visits = new TIntObjectHashMap>(); + for (ScheduledPass freePass : freePasses) { + id2Visits.put(freePass.myPass.getId(), Pair.create(freePass, 0)); + checkConsistency(freePass, id2Visits); + } + id2Visits.forEachEntry(new TIntObjectProcedure>() { + @Override + public boolean execute(int id, Pair pair) { + int count = pair.second; + assert count == 0 : id; + return true; + } + }); + assert id2Visits.size() == threadsToStartCountdown.get(); + } + + private void checkConsistency(ScheduledPass pass, TIntObjectHashMap> id2Visits) { + for (ScheduledPass succ : ContainerUtil.concat(pass.mySuccessorsOnCompletion, pass.mySuccessorsOnSubmit)) { + int succId = succ.myPass.getId(); + Pair succPair = id2Visits.get(succId); + if (succPair == null) { + succPair = Pair.create(succ, succ.myRunningPredecessorsCount.get()); + id2Visits.put(succId, succPair); + } + int newPred = succPair.second - 1; + id2Visits.put(succId, Pair.create(succ, newPred)); + assert newPred >= 0; + if (newPred == 0) { + checkConsistency((succ), id2Visits); + } + } + } + + @NotNull + private TextEditorHighlightingPass convertToTextHighlightingPass(@NotNull final HighlightingPass pass, + final Document document, + @NotNull AtomicInteger id, + int previousPassId) { + TextEditorHighlightingPass textEditorHighlightingPass; + if (pass instanceof TextEditorHighlightingPass) { + textEditorHighlightingPass = (TextEditorHighlightingPass)pass; + } + else { + // run all passes in sequence + textEditorHighlightingPass = new TextEditorHighlightingPass(myProject, document, true) { + @Override + public void doCollectInformation(@NotNull ProgressIndicator progress) { + pass.collectInformation(progress); + } + + @Override + public void doApplyInformationToEditor() { + pass.applyInformationToEditor(); + } + }; + textEditorHighlightingPass.setId(id.incrementAndGet()); + if (previousPassId != 0) { + textEditorHighlightingPass.setCompletionPredecessorIds(new int[]{previousPassId}); + } + } + return textEditorHighlightingPass; + } + + @NotNull + private FileEditor getPreferredFileEditor(Document document, @NotNull Collection fileEditors) { + assert !fileEditors.isEmpty(); if (document != null) { final VirtualFile file = FileDocumentManager.getInstance().getFile(document); if (file != null) { final FileEditor selected = FileEditorManager.getInstance(myProject).getSelectedEditor(file); - if (fileEditors.contains(selected)) { + if (selected != null && fileEditors.contains(selected)) { return selected; } } } - if (!fileEditors.isEmpty()) { - return fileEditors.get(0); - } - return null; + return fileEditors.iterator().next(); } @NotNull - private ScheduledPass createScheduledPass(@NotNull List fileEditors, + private ScheduledPass createScheduledPass(@NotNull FileEditor fileEditor, @NotNull TextEditorHighlightingPass pass, - @NotNull Map, ScheduledPass> toBeSubmitted, + @NotNull Map, ScheduledPass> toBeSubmitted, @NotNull List textEditorHighlightingPasses, @NotNull List freePasses, @NotNull List dependentPasses, @NotNull DaemonProgressIndicator updateProgress, - @NotNull AtomicInteger threadsToStartCountdown, - int jobPriority) { + @NotNull AtomicInteger threadsToStartCountdown) { int passId = pass.getId(); - Document document = pass.getDocument(); - Pair key = Pair.create(document, passId); + Pair key = Pair.create(fileEditor, passId); ScheduledPass scheduledPass = toBeSubmitted.get(key); if (scheduledPass != null) return scheduledPass; - scheduledPass = new ScheduledPass(fileEditors, pass, updateProgress, threadsToStartCountdown, jobPriority); + scheduledPass = new ScheduledPass(fileEditor, pass, updateProgress, threadsToStartCountdown); + threadsToStartCountdown.incrementAndGet(); toBeSubmitted.put(key, scheduledPass); for (int predecessorId : pass.getCompletionPredecessorIds()) { - ScheduledPass predecessor = findOrCreatePredecessorPass(fileEditors, document, toBeSubmitted, textEditorHighlightingPasses, freePasses, dependentPasses, - updateProgress, threadsToStartCountdown, jobPriority, predecessorId); + ScheduledPass predecessor = findOrCreatePredecessorPass(fileEditor, toBeSubmitted, textEditorHighlightingPasses, freePasses, dependentPasses, + updateProgress, threadsToStartCountdown, predecessorId); if (predecessor != null) { - predecessor.mySuccessorsOnCompletion.add(scheduledPass); - scheduledPass.myRunningPredecessorsCount.incrementAndGet(); + predecessor.addSuccessorOnCompletion(scheduledPass); } } for (int predecessorId : pass.getStartingPredecessorIds()) { - ScheduledPass predecessor = findOrCreatePredecessorPass(fileEditors, document, toBeSubmitted, textEditorHighlightingPasses, freePasses, - dependentPasses, updateProgress, threadsToStartCountdown, jobPriority, predecessorId); + ScheduledPass predecessor = findOrCreatePredecessorPass(fileEditor, toBeSubmitted, textEditorHighlightingPasses, freePasses, + dependentPasses, updateProgress, threadsToStartCountdown, + predecessorId); if (predecessor != null) { - predecessor.mySuccessorsOnSubmit.add(scheduledPass); - scheduledPass.myRunningPredecessorsCount.incrementAndGet(); + predecessor.addSuccessorOnSubmit(scheduledPass); } } if (scheduledPass.myRunningPredecessorsCount.get() == 0 && !freePasses.contains(scheduledPass)) { @@ -259,43 +298,49 @@ public abstract class PassExecutorService implements Disposable { else if (!dependentPasses.contains(scheduledPass)) { dependentPasses.add(scheduledPass); } + + if (pass.isRunIntentionPassAfter() && fileEditor instanceof TextEditor) { + Editor editor = ((TextEditor)fileEditor).getEditor(); + ShowIntentionsPass ip = new ShowIntentionsPass(myProject, editor, -1); + ip.setId(nextPassId.incrementAndGet()); + ip.setCompletionPredecessorIds(new int[]{scheduledPass.myPass.getId()}); + + createScheduledPass(fileEditor, ip, toBeSubmitted, textEditorHighlightingPasses, freePasses, dependentPasses, updateProgress, threadsToStartCountdown); + } + return scheduledPass; } - private ScheduledPass findOrCreatePredecessorPass(@NotNull List fileEditors, - Document document, - @NotNull Map, ScheduledPass> toBeSubmitted, + private ScheduledPass findOrCreatePredecessorPass(@NotNull FileEditor fileEditor, + @NotNull Map, ScheduledPass> toBeSubmitted, @NotNull List textEditorHighlightingPasses, @NotNull List freePasses, @NotNull List dependentPasses, @NotNull DaemonProgressIndicator updateProgress, @NotNull AtomicInteger myThreadsToStartCountdown, - final int jobPriority, final int predecessorId) { - Pair predKey = Pair.create(document, predecessorId); + Pair predKey = Pair.create(fileEditor, predecessorId); ScheduledPass predecessor = toBeSubmitted.get(predKey); if (predecessor == null) { TextEditorHighlightingPass textEditorPass = findPassById(predecessorId, textEditorHighlightingPasses); - predecessor = textEditorPass == null ? null : createScheduledPass(fileEditors, textEditorPass, toBeSubmitted, textEditorHighlightingPasses, freePasses, - dependentPasses, updateProgress, myThreadsToStartCountdown, jobPriority); + predecessor = textEditorPass == null ? null : createScheduledPass(fileEditor, textEditorPass, toBeSubmitted, textEditorHighlightingPasses, freePasses, + dependentPasses, updateProgress, myThreadsToStartCountdown); } return predecessor; } private static TextEditorHighlightingPass findPassById(final int id, @NotNull List textEditorHighlightingPasses) { - TextEditorHighlightingPass textEditorPass = null; - for (TextEditorHighlightingPass found : textEditorHighlightingPasses) { - if (found.getId() == id) { - textEditorPass = found; - break; + return ContainerUtil.find(textEditorHighlightingPasses, new Condition() { + @Override + public boolean value(TextEditorHighlightingPass pass) { + return pass.getId() == id; } - } - return textEditorPass; + }); } private void submit(@NotNull ScheduledPass pass) { if (!pass.myUpdateProgress.isCanceled()) { - Job job = JobLauncher.getInstance().submitToJobThread(pass.myJobPriority, pass, new Consumer() { + Job job = JobLauncher.getInstance().submitToJobThread(Job.DEFAULT_PRIORITY, pass, new Consumer() { @Override public void consume(Future future) { try { @@ -317,25 +362,21 @@ public abstract class PassExecutorService implements Disposable { } private class ScheduledPass implements Runnable { - private final List myFileEditors; + private final FileEditor myFileEditor; private final TextEditorHighlightingPass myPass; private final AtomicInteger myThreadsToStartCountdown; - private final int myJobPriority; - private final AtomicInteger myRunningPredecessorsCount; + private final AtomicInteger myRunningPredecessorsCount = new AtomicInteger(0); private final Collection mySuccessorsOnCompletion = new ArrayList(); private final Collection mySuccessorsOnSubmit = new ArrayList(); private final DaemonProgressIndicator myUpdateProgress; - private ScheduledPass(@NotNull List fileEditors, + private ScheduledPass(@NotNull FileEditor fileEditor, @NotNull TextEditorHighlightingPass pass, @NotNull DaemonProgressIndicator progressIndicator, - @NotNull AtomicInteger threadsToStartCountdown, - int jobPriority) { - myFileEditors = fileEditors; + @NotNull AtomicInteger threadsToStartCountdown) { + myFileEditor = fileEditor; myPass = pass; myThreadsToStartCountdown = threadsToStartCountdown; - myJobPriority = jobPriority; - myRunningPredecessorsCount = new AtomicInteger(0); myUpdateProgress = progressIndicator; } @@ -410,7 +451,7 @@ public abstract class PassExecutorService implements Disposable { log(myUpdateProgress, myPass, "Finished. "); if (!myUpdateProgress.isCanceled()) { - applyInformationToEditorsLater(myFileEditors, myPass, myUpdateProgress, myThreadsToStartCountdown); + applyInformationToEditorsLater(myFileEditor, myPass, myUpdateProgress, myThreadsToStartCountdown); for (ScheduledPass successor : mySuccessorsOnCompletion) { int predecessorsToRun = successor.myRunningPredecessorsCount.decrementAndGet(); if (predecessorsToRun == 0) { @@ -425,75 +466,57 @@ public abstract class PassExecutorService implements Disposable { public String toString() { return "SP: " + myPass; } + + private void addSuccessorOnCompletion(@NotNull ScheduledPass successor) { + mySuccessorsOnCompletion.add(successor); + successor.myRunningPredecessorsCount.incrementAndGet(); + } + + private void addSuccessorOnSubmit(@NotNull ScheduledPass successor) { + mySuccessorsOnSubmit.add(successor); + successor.myRunningPredecessorsCount.incrementAndGet(); + } } - private void applyInformationToEditorsLater(@NotNull final List fileEditors, + private void applyInformationToEditorsLater(@NotNull final FileEditor fileEditor, @NotNull final TextEditorHighlightingPass pass, @NotNull final DaemonProgressIndicator updateProgress, @NotNull final AtomicInteger threadsToStartCountdown) { - final boolean testMode = ApplicationManager.getApplication().isUnitTestMode(); ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { @Override public void run() { - doApplyInformationToEditors(updateProgress, pass, fileEditors, threadsToStartCountdown, testMode); - } - }, ModalityState.stateForComponent(fileEditors.get(0).getComponent())); - } - - private void doApplyInformationToEditors(@NotNull DaemonProgressIndicator updateProgress, - @NotNull TextEditorHighlightingPass pass, - @NotNull List fileEditors, - @NotNull AtomicInteger threadsToStartCountdown, - boolean testMode) { - ApplicationManager.getApplication().assertIsDispatchThread(); - if (isDisposed() || myProject.isDisposed()) { - updateProgress.cancel(); - } - if (updateProgress.isCanceled()) { - log(updateProgress, pass, " is canceled during apply, sorry"); - return; - } - boolean applied = false; - for (final FileEditor fileEditor : fileEditors) { - LOG.assertTrue(fileEditor != null); - try { - if (testMode || fileEditor.getComponent().isDisplayable()) { - if (!applied) { - applied = true; + if (isDisposed() || myProject.isDisposed()) { + updateProgress.cancel(); + } + if (updateProgress.isCanceled()) { + log(updateProgress, pass, " is canceled during apply, sorry"); + return; + } + try { + if (fileEditor.getComponent().isDisplayable() || ApplicationManager.getApplication().isUnitTestMode()) { log(updateProgress, pass, " Applied"); pass.applyInformationToEditor(); } - afterApplyInformationToEditor(pass, fileEditor, updateProgress); - - if (pass.isRunIntentionPassAfter() && fileEditor instanceof TextEditor && !updateProgress.isCanceled()) { - Editor editor = ((TextEditor)fileEditor).getEditor(); - ShowIntentionsPass ip = new ShowIntentionsPass(myProject, editor, -1); - ip.setId(nextPassId.incrementAndGet()); - threadsToStartCountdown.incrementAndGet(); - submit(new ScheduledPass(fileEditors, ip, updateProgress, threadsToStartCountdown, Job.DEFAULT_PRIORITY)); - } + } + catch (RuntimeException e) { + log(updateProgress, pass, "Error " + e); + throw e; + } + if (threadsToStartCountdown.decrementAndGet() == 0) { + log(updateProgress, pass, "Stopping "); + updateProgress.stopIfRunning(); + } + else { + log(updateProgress, pass, "Finished but there are passes in the queue: " + threadsToStartCountdown.get()); } } - catch (RuntimeException e) { - log(updateProgress, pass, "Error " + e); - throw e; - } - } - if (threadsToStartCountdown.decrementAndGet() == 0) { - log(updateProgress, pass, "Stopping "); - updateProgress.stopIfRunning(); - } - else { - log(updateProgress, pass, "Finished but there are passes in the queue: "+threadsToStartCountdown.get()); - } + }, ModalityState.stateForComponent(fileEditor.getComponent())); } protected boolean isDisposed() { return isDisposed; } - protected abstract void afterApplyInformationToEditor(TextEditorHighlightingPass pass, FileEditor fileEditor, ProgressIndicator updateProgress); - @NotNull public List getAllSubmittedPasses() { List result = new ArrayList(mySubmittedPasses.size()); @@ -502,13 +525,17 @@ public abstract class PassExecutorService implements Disposable { result.add(scheduledPass.myPass); } } + sortById(result); + return result; + } + + private void sortById(@NotNull List result) { ContainerUtil.quickSort(result, new Comparator() { @Override public int compare(TextEditorHighlightingPass o1, TextEditorHighlightingPass o2) { return o1.getId() - o2.getId(); } }); - return result; } private static final ConcurrentHashMap threads = new ConcurrentHashMap(); @@ -537,11 +564,11 @@ public abstract class PassExecutorService implements Disposable { } private static final Key THROWABLE_KEY = Key.create("THROWABLE_KEY"); - private static void saveException(Throwable e, DaemonProgressIndicator indicator) { + private static void saveException(@NotNull Throwable e, @NotNull DaemonProgressIndicator indicator) { indicator.putUserDataIfAbsent(THROWABLE_KEY, e); } @TestOnly - public static Throwable getSavedException(DaemonProgressIndicator indicator) { + public static Throwable getSavedException(@NotNull DaemonProgressIndicator indicator) { return indicator.getUserData(THROWABLE_KEY); }