call applyInformation for all EditorBoundPass for each editor

This commit is contained in:
Alexey Kudravtsev
2014-06-05 16:30:32 +04:00
parent 47336a6e0a
commit dc3bd73115
5 changed files with 288 additions and 228 deletions
@@ -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;
}
}
@@ -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<Boolean> 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<Boolean> 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();
@@ -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);
}
}
@@ -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<List<LineMarkerInfo>> 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<HighlightInfo> 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<HighlightInfo> runPasses(@NotNull PsiFile file,
@NotNull Document document,
@NotNull List<TextEditor> 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<TextEditorHighlightingPass> passes = highlighter.getPasses(toIgnore);
HighlightingPass[] array = passes.toArray(new HighlightingPass[passes.size()]);
assert array.length != 0 : "Highlighting is disabled for the file " + file;
Map<FileEditor, HighlightingPass[]> map = new HashMap<FileEditor, HighlightingPass[]>();
for (TextEditor textEditor : textEditors) {
TextEditorBackgroundHighlighter highlighter = (TextEditorBackgroundHighlighter)textEditor.getBackgroundHighlighter();
final List<TextEditorHighlightingPass> 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<HighlightInfo> 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);
}
};
@@ -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<ScheduledPass, Job<Void>> mySubmittedPasses = new ConcurrentHashMap<ScheduledPass, Job<Void>>();
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<FileEditor, HighlightingPass[]> passesMap, @NotNull DaemonProgressIndicator updateProgress, final int jobPriority) {
public void submitPasses(@NotNull Map<FileEditor, HighlightingPass[]> passesMap, @NotNull DaemonProgressIndicator updateProgress) {
if (isDisposed()) return;
int id = 1;
// (doc, passId) -> created pass
Map<Pair<Document, Integer>, ScheduledPass> toBeSubmitted = new THashMap<Pair<Document, Integer>, ScheduledPass>(passesMap.size());
// null keys are ok
Map<Document, List<FileEditor>> documentToEditors = new HashMap<Document, List<FileEditor>>();
Map<FileEditor, List<TextEditorHighlightingPass>> textPasses = new HashMap<FileEditor, List<TextEditorHighlightingPass>>(passesMap.size());
MultiMap<Document, FileEditor> documentToEditors = MultiMap.createSet();
MultiMap<FileEditor, TextEditorHighlightingPass> documentBoundPasses = MultiMap.createSmartList();
MultiMap<FileEditor, EditorBoundHighlightingPass> editorBoundPasses = MultiMap.createSmartList();
for (Map.Entry<FileEditor, HighlightingPass[]> 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<TextEditorHighlightingPass> textPassesForDocument = textPasses.get(fileEditor);
if (textPassesForDocument == null) {
textPassesForDocument = new SmartList<TextEditorHighlightingPass>();
textPasses.put(fileEditor, textPassesForDocument);
}
textPassesForDocument.add(textEditorHighlightingPass);
List<FileEditor> editors = documentToEditors.get(document);
if (editors == null) {
editors = new SmartList<FileEditor>();
documentToEditors.put(document, editors);
}
if (!editors.contains(fileEditor)) editors.add(fileEditor);
}
}
List<ScheduledPass> freePasses = new ArrayList<ScheduledPass>(documentToEditors.size()*5);
List<ScheduledPass> dependentPasses = new ArrayList<ScheduledPass>(documentToEditors.size()*10);
// (fileEditor, passId) -> created pass
Map<Pair<FileEditor, Integer>, ScheduledPass> toBeSubmitted = new THashMap<Pair<FileEditor, Integer>, ScheduledPass>(passesMap.size());
final AtomicInteger threadsToStartCountdown = new AtomicInteger(0);
for (Map.Entry<Document, List<FileEditor>> entry : documentToEditors.entrySet()) {
final List<FileEditor> fileEditors = entry.getValue();
List<TextEditorHighlightingPass> passes = textPasses.get(getPreferredFileEditor(entry.getKey(), fileEditors));
if (passes == null) {
for (Map.Entry<Document, Collection<FileEditor>> entry : documentToEditors.entrySet()) {
Collection<FileEditor> fileEditors = entry.getValue();
Document document = entry.getKey();
FileEditor preferredFileEditor = getPreferredFileEditor(document, fileEditors);
List<TextEditorHighlightingPass> passes = (List<TextEditorHighlightingPass>)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<TextEditorHighlightingPass>() {
@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<FileEditor, Collection<EditorBoundHighlightingPass>> entry : editorBoundPasses.entrySet()) {
FileEditor fileEditor = entry.getKey();
Collection<EditorBoundHighlightingPass> createdEditorBoundPasses = entry.getValue();
List<TextEditorHighlightingPass> createdDocumentBoundPasses = (List<TextEditorHighlightingPass>)documentBoundPasses.get(fileEditor);
List<TextEditorHighlightingPass> allCreatedPasses = new ArrayList<TextEditorHighlightingPass>(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<FileEditor> fileEditors) {
private void assertConsistency(List<ScheduledPass> freePasses,
Map<Pair<FileEditor, Integer>, ScheduledPass> toBeSubmitted,
AtomicInteger threadsToStartCountdown) {
assert threadsToStartCountdown.get() == toBeSubmitted.size();
TIntObjectHashMap<Pair<ScheduledPass, Integer>> id2Visits = new TIntObjectHashMap<Pair<ScheduledPass, Integer>>();
for (ScheduledPass freePass : freePasses) {
id2Visits.put(freePass.myPass.getId(), Pair.create(freePass, 0));
checkConsistency(freePass, id2Visits);
}
id2Visits.forEachEntry(new TIntObjectProcedure<Pair<ScheduledPass,Integer>>() {
@Override
public boolean execute(int id, Pair<ScheduledPass, Integer> pair) {
int count = pair.second;
assert count == 0 : id;
return true;
}
});
assert id2Visits.size() == threadsToStartCountdown.get();
}
private void checkConsistency(ScheduledPass pass, TIntObjectHashMap<Pair<ScheduledPass, Integer>> id2Visits) {
for (ScheduledPass succ : ContainerUtil.concat(pass.mySuccessorsOnCompletion, pass.mySuccessorsOnSubmit)) {
int succId = succ.myPass.getId();
Pair<ScheduledPass, Integer> 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<FileEditor> 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<FileEditor> fileEditors,
private ScheduledPass createScheduledPass(@NotNull FileEditor fileEditor,
@NotNull TextEditorHighlightingPass pass,
@NotNull Map<Pair<Document, Integer>, ScheduledPass> toBeSubmitted,
@NotNull Map<Pair<FileEditor, Integer>, ScheduledPass> toBeSubmitted,
@NotNull List<TextEditorHighlightingPass> textEditorHighlightingPasses,
@NotNull List<ScheduledPass> freePasses,
@NotNull List<ScheduledPass> dependentPasses,
@NotNull DaemonProgressIndicator updateProgress,
@NotNull AtomicInteger threadsToStartCountdown,
int jobPriority) {
@NotNull AtomicInteger threadsToStartCountdown) {
int passId = pass.getId();
Document document = pass.getDocument();
Pair<Document, Integer> key = Pair.create(document, passId);
Pair<FileEditor, Integer> 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<FileEditor> fileEditors,
Document document,
@NotNull Map<Pair<Document, Integer>, ScheduledPass> toBeSubmitted,
private ScheduledPass findOrCreatePredecessorPass(@NotNull FileEditor fileEditor,
@NotNull Map<Pair<FileEditor, Integer>, ScheduledPass> toBeSubmitted,
@NotNull List<TextEditorHighlightingPass> textEditorHighlightingPasses,
@NotNull List<ScheduledPass> freePasses,
@NotNull List<ScheduledPass> dependentPasses,
@NotNull DaemonProgressIndicator updateProgress,
@NotNull AtomicInteger myThreadsToStartCountdown,
final int jobPriority,
final int predecessorId) {
Pair<Document, Integer> predKey = Pair.create(document, predecessorId);
Pair<FileEditor, Integer> 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<TextEditorHighlightingPass> textEditorHighlightingPasses) {
TextEditorHighlightingPass textEditorPass = null;
for (TextEditorHighlightingPass found : textEditorHighlightingPasses) {
if (found.getId() == id) {
textEditorPass = found;
break;
return ContainerUtil.find(textEditorHighlightingPasses, new Condition<TextEditorHighlightingPass>() {
@Override
public boolean value(TextEditorHighlightingPass pass) {
return pass.getId() == id;
}
}
return textEditorPass;
});
}
private void submit(@NotNull ScheduledPass pass) {
if (!pass.myUpdateProgress.isCanceled()) {
Job<Void> job = JobLauncher.getInstance().submitToJobThread(pass.myJobPriority, pass, new Consumer<Future>() {
Job<Void> job = JobLauncher.getInstance().submitToJobThread(Job.DEFAULT_PRIORITY, pass, new Consumer<Future>() {
@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<FileEditor> 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<ScheduledPass> mySuccessorsOnCompletion = new ArrayList<ScheduledPass>();
private final Collection<ScheduledPass> mySuccessorsOnSubmit = new ArrayList<ScheduledPass>();
private final DaemonProgressIndicator myUpdateProgress;
private ScheduledPass(@NotNull List<FileEditor> 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<FileEditor> 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<FileEditor> 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<TextEditorHighlightingPass> getAllSubmittedPasses() {
List<TextEditorHighlightingPass> result = new ArrayList<TextEditorHighlightingPass>(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<TextEditorHighlightingPass> result) {
ContainerUtil.quickSort(result, new Comparator<TextEditorHighlightingPass>() {
@Override
public int compare(TextEditorHighlightingPass o1, TextEditorHighlightingPass o2) {
return o1.getId() - o2.getId();
}
});
return result;
}
private static final ConcurrentHashMap<Thread, Integer> threads = new ConcurrentHashMap<Thread, Integer>();
@@ -537,11 +564,11 @@ public abstract class PassExecutorService implements Disposable {
}
private static final Key<Throwable> 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);
}