blob: 0d02b32baed4c31f20f9cdc5028b15a1a399f3ea [file] [log] [blame]
/*
* 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.codeInsight.daemon.impl;
import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
import com.intellij.codeHighlighting.HighlightingPass;
import com.intellij.codeHighlighting.Pass;
import com.intellij.codeHighlighting.TextEditorHighlightingPass;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettingsImpl;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
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.ide.PowerSaveMode;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.components.NamedComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.ex.RangeHighlighterEx;
import com.intellij.openapi.editor.impl.DocumentMarkupModel;
import com.intellij.openapi.editor.markup.MarkupModel;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.newvfs.RefreshQueueImpl;
import com.intellij.packageDependencies.DependencyValidationManager;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiCompiledElement;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.PsiDocumentManagerImpl;
import com.intellij.psi.search.scope.packageSet.NamedScopeManager;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.Alarm;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.SmartList;
import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
/**
* This class also controls the auto-reparse and auto-hints.
*/
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");
private static final Key<List<HighlightInfo>> FILE_LEVEL_HIGHLIGHTS = Key.create("FILE_LEVEL_HIGHLIGHTS");
private final Project myProject;
private final DaemonCodeAnalyzerSettings mySettings;
@NotNull private final EditorTracker myEditorTracker;
private DaemonProgressIndicator myUpdateProgress = new DaemonProgressIndicator(); //guarded by this
private final Runnable myUpdateRunnable = createUpdateRunnable();
private final Alarm myAlarm = new Alarm();
private boolean myUpdateByTimerEnabled = true;
private final Collection<VirtualFile> myDisabledHintsFiles = new THashSet<VirtualFile>();
private final Collection<VirtualFile> myDisabledHighlightingFiles = new THashSet<VirtualFile>();
private final FileStatusMap myFileStatusMap;
private DaemonCodeAnalyzerSettings myLastSettings;
private volatile IntentionHintComponent myLastIntentionHint;
private volatile boolean myDisposed; // the only possible transition: false -> true
private volatile boolean myInitialized; // the only possible transition: false -> true
@NonNls private static final String DISABLE_HINTS_TAG = "disable_hints";
@NonNls private static final String FILE_TAG = "file";
@NonNls private static final String URL_ATT = "url";
private final PassExecutorService myPassExecutorService;
private volatile boolean allowToInterrupt = true;
public DaemonCodeAnalyzerImpl(@NotNull Project project,
@NotNull DaemonCodeAnalyzerSettings daemonCodeAnalyzerSettings,
@NotNull EditorTracker editorTracker,
@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(project);
myPassExecutorService = new PassExecutorService(project);
Disposer.register(this, myPassExecutorService);
Disposer.register(this, myFileStatusMap);
DaemonProgressIndicator.setDebug(LOG.isDebugEnabled());
assert !myInitialized : "Double Initializing";
Disposer.register(this, new StatusBarUpdater(project));
myInitialized = true;
myDisposed = false;
myFileStatusMap.markAllFilesDirty();
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
assert myInitialized : "Disposing not initialized component";
assert !myDisposed : "Double dispose";
stopProcess(false, "Dispose");
myDisposed = true;
myLastSettings = null;
}
});
}
@Override
public void dispose() {
}
@NotNull
@TestOnly
public static List<HighlightInfo> getHighlights(@NotNull Document document, HighlightSeverity minSeverity, @NotNull Project project) {
List<HighlightInfo> infos = new ArrayList<HighlightInfo>();
processHighlights(document, project, minSeverity, 0, document.getTextLength(),
new CommonProcessors.CollectProcessor<HighlightInfo>(infos));
return infos;
}
@Override
@NotNull
@TestOnly
public List<HighlightInfo> getFileLevelHighlights(@NotNull Project project, @NotNull PsiFile file) {
VirtualFile vFile = file.getViewProvider().getVirtualFile();
final FileEditorManager manager = FileEditorManager.getInstance(project);
List<HighlightInfo> result = new ArrayList<HighlightInfo>();
for (FileEditor fileEditor : manager.getEditors(vFile)) {
final List<HighlightInfo> infos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS);
if (infos == null) continue;
for (HighlightInfo info : infos) {
result.add(info);
}
}
return result;
}
@Override
public void cleanFileLevelHighlights(@NotNull Project project, final int group, PsiFile psiFile) {
if (psiFile == null) return;
FileViewProvider provider = psiFile.getViewProvider();
VirtualFile vFile = provider.getVirtualFile();
final FileEditorManager manager = FileEditorManager.getInstance(project);
for (FileEditor fileEditor : manager.getEditors(vFile)) {
final List<HighlightInfo> infos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS);
if (infos == null) continue;
List<HighlightInfo> infosToRemove = new ArrayList<HighlightInfo>();
for (HighlightInfo info : infos) {
if (info.getGroup() == group) {
manager.removeTopComponent(fileEditor, info.fileLevelComponent);
infosToRemove.add(info);
}
}
infos.removeAll(infosToRemove);
}
}
@Override
public void addFileLevelHighlight(@NotNull final Project project,
final int group,
@NotNull final HighlightInfo info,
@NotNull final PsiFile psiFile) {
VirtualFile vFile = psiFile.getViewProvider().getVirtualFile();
final FileEditorManager manager = FileEditorManager.getInstance(project);
for (FileEditor fileEditor : manager.getEditors(vFile)) {
if (fileEditor instanceof TextEditor) {
FileLevelIntentionComponent component = new FileLevelIntentionComponent(info.getDescription(), info.getSeverity(), info.quickFixActionRanges,
project, psiFile, ((TextEditor)fileEditor).getEditor());
manager.addTopComponent(fileEditor, component);
List<HighlightInfo> fileLevelInfos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS);
if (fileLevelInfos == null) {
fileLevelInfos = new ArrayList<HighlightInfo>();
fileEditor.putUserData(FILE_LEVEL_HIGHLIGHTS, fileLevelInfos);
}
info.fileLevelComponent = component;
info.setGroup(group);
fileLevelInfos.add(info);
}
}
}
@Override
@NotNull
public List<HighlightInfo> runMainPasses(@NotNull PsiFile psiFile,
@NotNull Document document,
@NotNull final ProgressIndicator progress) {
final List<HighlightInfo> result = new ArrayList<HighlightInfo>();
final VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile != null && !virtualFile.getFileType().isBinary()) {
List<TextEditorHighlightingPass> passes =
TextEditorHighlightingPassRegistrarEx.getInstanceEx(myProject).instantiateMainPasses(psiFile, document,
HighlightInfoProcessor.getEmpty());
Collections.sort(passes, new Comparator<TextEditorHighlightingPass>() {
@Override
public int compare(TextEditorHighlightingPass o1, TextEditorHighlightingPass o2) {
if (o1 instanceof GeneralHighlightingPass) return -1;
if (o2 instanceof GeneralHighlightingPass) return 1;
return 0;
}
});
for (TextEditorHighlightingPass pass : passes) {
pass.doCollectInformation(progress);
result.addAll(pass.getInfos());
}
}
return result;
}
@NotNull
@TestOnly
public List<HighlightInfo> runPasses(@NotNull PsiFile file,
@NotNull Document document,
@NotNull TextEditor textEditor,
@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;
ApplicationEx application = ApplicationManagerEx.getApplicationEx();
application.assertIsDispatchThread();
if (application.isWriteAccessAllowed()) {
throw new AssertionError("Must not start highlighting from within write action, or deadlock is imminent");
}
((FileTypeManagerImpl)FileTypeManager.getInstance()).drainReDetectQueue();
// pump first so that queued event do not interfere
UIUtil.dispatchAllInvocationEvents();
// refresh will fire write actions interfering with highlighting
while (RefreshQueueImpl.isRefreshInProgress() || HeavyProcessLatch.INSTANCE.isRunning()) {
UIUtil.dispatchAllInvocationEvents();
}
UIUtil.dispatchAllInvocationEvents();
Project project = file.getProject();
setUpdateByTimerEnabled(false);
FileStatusMap fileStatusMap = getFileStatusMap();
fileStatusMap.allowDirt(canChangeDocument);
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);
}
for (int ignoreId : toIgnore) {
fileStatusMap.markFileUpToDate(document, ignoreId);
}
final DaemonProgressIndicator progress = createUpdateProgress();
myPassExecutorService.submitPasses(map, progress);
try {
while (progress.isRunning()) {
try {
progress.checkCanceled();
if (callbackWhileWaiting != null) {
callbackWhileWaiting.run();
}
myPassExecutorService.waitFor(50);
UIUtil.dispatchAllInvocationEvents();
Throwable savedException = PassExecutorService.getSavedException(progress);
if (savedException != null) throw savedException;
}
catch (RuntimeException e) {
throw e;
}
catch (Error e) {
throw e;
}
catch (Throwable e) {
throw new RuntimeException(e);
}
}
UIUtil.dispatchAllInvocationEvents();
UIUtil.dispatchAllInvocationEvents();
return getHighlights(document, null, project);
}
finally {
fileStatusMap.allowDirt(true);
waitForTermination();
}
}
@TestOnly
public void prepareForTest() {
//if (!myInitialized) {
// projectOpened();
//}
setUpdateByTimerEnabled(false);
waitForTermination();
}
@TestOnly
public void cleanupAfterTest() {
if (!myProject.isOpen()) return;
setUpdateByTimerEnabled(false);
waitForTermination();
}
void waitForTermination() {
myPassExecutorService.cancelAll(true);
}
@Override
@NotNull
public String getComponentName() {
return "DaemonCodeAnalyzer";
}
@Override
public void settingsChanged() {
DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
if (settings.isCodeHighlightingChanged(myLastSettings)) {
restart();
}
myLastSettings = ((DaemonCodeAnalyzerSettingsImpl)settings).clone();
}
@Override
public void updateVisibleHighlighters(@NotNull Editor editor) {
ApplicationManager.getApplication().assertIsDispatchThread();
// no need, will not work anyway
}
@Override
public void setUpdateByTimerEnabled(boolean value) {
myUpdateByTimerEnabled = value;
stopProcess(value, "Update by timer change");
}
private int myDisableCount = 0;
@Override
public void disableUpdateByTimer(@NotNull Disposable parentDisposable) {
setUpdateByTimerEnabled(false);
myDisableCount++;
ApplicationManager.getApplication().assertIsDispatchThread();
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
myDisableCount--;
if (myDisableCount == 0) {
setUpdateByTimerEnabled(true);
}
}
});
}
public boolean isUpdateByTimerEnabled() {
return myUpdateByTimerEnabled;
}
@Override
public void setImportHintsEnabled(@NotNull PsiFile file, boolean value) {
VirtualFile vFile = file.getVirtualFile();
if (value) {
myDisabledHintsFiles.remove(vFile);
stopProcess(true, "Import hints change");
}
else {
myDisabledHintsFiles.add(vFile);
HintManager.getInstance().hideAllHints();
}
}
@Override
public void resetImportHintsEnabledForProject() {
myDisabledHintsFiles.clear();
}
@Override
public void setHighlightingEnabled(@NotNull PsiFile file, boolean value) {
VirtualFile virtualFile = PsiUtilCore.getVirtualFile(file);
if (value) {
myDisabledHighlightingFiles.remove(virtualFile);
}
else {
myDisabledHighlightingFiles.add(virtualFile);
}
}
@Override
public boolean isHighlightingAvailable(@Nullable PsiFile file) {
if (file == null || !file.isPhysical()) return false;
if (myDisabledHighlightingFiles.contains(PsiUtilCore.getVirtualFile(file))) return false;
if (file instanceof PsiCompiledElement) return false;
final FileType fileType = file.getFileType();
// To enable T.O.D.O. highlighting
return !fileType.isBinary();
}
@Override
public boolean isImportHintsEnabled(@NotNull PsiFile file) {
return isAutohintsAvailable(file) && !myDisabledHintsFiles.contains(file.getVirtualFile());
}
@Override
public boolean isAutohintsAvailable(PsiFile file) {
return isHighlightingAvailable(file) && !(file instanceof PsiCompiledElement);
}
@Override
public void restart() {
myFileStatusMap.markAllFilesDirty();
stopProcess(true, "Global restart");
}
@Override
public void restart(@NotNull PsiFile file) {
Document document = PsiDocumentManager.getInstance(myProject).getCachedDocument(file);
if (document == null) return;
myFileStatusMap.markFileScopeDirty(document, new TextRange(0, document.getTextLength()), file.getTextLength());
stopProcess(true, "Psi file restart");
}
@NotNull
List<TextEditorHighlightingPass> getPassesToShowProgressFor(Document document) {
List<TextEditorHighlightingPass> allPasses = myPassExecutorService.getAllSubmittedPasses();
List<TextEditorHighlightingPass> result = new ArrayList<TextEditorHighlightingPass>(allPasses.size());
for (TextEditorHighlightingPass pass : allPasses) {
if (pass.getDocument() == document || pass.getDocument() == null) {
result.add(pass);
}
}
return result;
}
boolean isAllAnalysisFinished(@NotNull PsiFile file) {
if (myDisposed) return false;
Document document = PsiDocumentManager.getInstance(myProject).getCachedDocument(file);
return document != null &&
document.getModificationStamp() == file.getViewProvider().getModificationStamp() &&
myFileStatusMap.allDirtyScopesAreNull(document);
}
@Override
public boolean isErrorAnalyzingFinished(@NotNull PsiFile file) {
if (myDisposed) return false;
Document document = PsiDocumentManager.getInstance(myProject).getCachedDocument(file);
return document != null &&
document.getModificationStamp() == file.getViewProvider().getModificationStamp() &&
myFileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL) == null;
}
@Override
@NotNull
public FileStatusMap getFileStatusMap() {
return myFileStatusMap;
}
synchronized boolean isRunning() {
return myUpdateProgress != null && !myUpdateProgress.isCanceled();
}
synchronized void stopProcess(boolean toRestartAlarm, @NonNls String reason) {
if (!allowToInterrupt) throw new RuntimeException("Cannot interrupt daemon");
cancelUpdateProgress(toRestartAlarm, reason);
myAlarm.cancelAllRequests();
boolean restart = toRestartAlarm && !myDisposed && myInitialized;
if (restart) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myAlarm.addRequest(myUpdateRunnable, mySettings.AUTOREPARSE_DELAY);
}
});
}
}
private synchronized void cancelUpdateProgress(final boolean start, @NonNls String reason) {
PassExecutorService.log(myUpdateProgress, null, "CancelX", reason, start);
if (myUpdateProgress != null) {
myUpdateProgress.cancel();
myPassExecutorService.cancelAll(false);
myUpdateProgress = null;
}
}
public static boolean processHighlightsNearOffset(@NotNull Document document,
@NotNull Project project,
@NotNull final HighlightSeverity minSeverity,
final int offset,
final boolean includeFixRange,
@NotNull final Processor<HighlightInfo> processor) {
return processHighlights(document, project, null, 0, document.getTextLength(), new Processor<HighlightInfo>() {
@Override
public boolean process(@NotNull HighlightInfo info) {
if (!isOffsetInsideHighlightInfo(offset, info, includeFixRange)) return true;
int compare = info.getSeverity().compareTo(minSeverity);
return compare < 0 || processor.process(info);
}
});
}
@Nullable
public HighlightInfo findHighlightByOffset(@NotNull Document document, final int offset, final boolean includeFixRange) {
return findHighlightByOffset(document, offset, includeFixRange, HighlightSeverity.INFORMATION);
}
@Nullable
public HighlightInfo findHighlightByOffset(@NotNull Document document,
final int offset,
final boolean includeFixRange,
@NotNull HighlightSeverity minSeverity) {
final List<HighlightInfo> foundInfoList = new SmartList<HighlightInfo>();
processHighlightsNearOffset(document, myProject, minSeverity, offset, includeFixRange,
new Processor<HighlightInfo>() {
@Override
public boolean process(@NotNull HighlightInfo info) {
if (info.getSeverity() == HighlightInfoType.ELEMENT_UNDER_CARET_SEVERITY) {
return true;
}
if (!foundInfoList.isEmpty()) {
HighlightInfo foundInfo = foundInfoList.get(0);
int compare = foundInfo.getSeverity().compareTo(info.getSeverity());
if (compare < 0) {
foundInfoList.clear();
}
else if (compare > 0) {
return true;
}
}
foundInfoList.add(info);
return true;
}
});
if (foundInfoList.isEmpty()) return null;
if (foundInfoList.size() == 1) return foundInfoList.get(0);
return new HighlightInfoComposite(foundInfoList);
}
private static boolean isOffsetInsideHighlightInfo(int offset, @NotNull HighlightInfo info, boolean includeFixRange) {
RangeHighlighterEx highlighter = info.highlighter;
if (highlighter == null || !highlighter.isValid()) return false;
int startOffset = highlighter.getStartOffset();
int endOffset = highlighter.getEndOffset();
if (startOffset <= offset && offset <= endOffset) {
return true;
}
if (!includeFixRange) return false;
RangeMarker fixMarker = info.fixMarker;
if (fixMarker != null) { // null means its range is the same as highlighter
if (!fixMarker.isValid()) return false;
startOffset = fixMarker.getStartOffset();
endOffset = fixMarker.getEndOffset();
return startOffset <= offset && offset <= endOffset;
}
return false;
}
@Nullable
public static List<LineMarkerInfo> getLineMarkers(@NotNull Document document, Project project) {
ApplicationManager.getApplication().assertIsDispatchThread();
MarkupModel markup = DocumentMarkupModel.forDocument(document, project, true);
return markup.getUserData(MARKERS_IN_EDITOR_DOCUMENT_KEY);
}
static void setLineMarkers(@NotNull Document document, List<LineMarkerInfo> lineMarkers, Project project) {
ApplicationManager.getApplication().assertIsDispatchThread();
MarkupModel markup = DocumentMarkupModel.forDocument(document, project, true);
markup.putUserData(MARKERS_IN_EDITOR_DOCUMENT_KEY, lineMarkers);
}
void setLastIntentionHint(@NotNull Project project,
@NotNull PsiFile file,
@NotNull Editor editor,
@NotNull ShowIntentionsPass.IntentionsInfo intentions,
boolean hasToRecreate) {
if (!editor.getSettings().isShowIntentionBulb()) {
return;
}
ApplicationManager.getApplication().assertIsDispatchThread();
hideLastIntentionHint();
IntentionHintComponent hintComponent = IntentionHintComponent.showIntentionHint(project, file, editor, intentions, false);
if (hasToRecreate) {
hintComponent.recreate();
}
myLastIntentionHint = hintComponent;
}
void hideLastIntentionHint() {
ApplicationManager.getApplication().assertIsDispatchThread();
IntentionHintComponent hint = myLastIntentionHint;
if (hint != null && hint.isVisible()) {
hint.hide();
myLastIntentionHint = null;
}
}
@Nullable
IntentionHintComponent getLastIntentionHint() {
return myLastIntentionHint;
}
@Override
public void writeExternal(@NotNull Element parentNode) throws WriteExternalException {
Element disableHintsElement = new Element(DISABLE_HINTS_TAG);
parentNode.addContent(disableHintsElement);
List<String> array = new ArrayList<String>();
for (VirtualFile file : myDisabledHintsFiles) {
if (file.isValid()) {
array.add(file.getUrl());
}
}
Collections.sort(array);
for (String url : array) {
Element fileElement = new Element(FILE_TAG);
fileElement.setAttribute(URL_ATT, url);
disableHintsElement.addContent(fileElement);
}
}
@Override
public void readExternal(@NotNull Element parentNode) throws InvalidDataException {
myDisabledHintsFiles.clear();
Element element = parentNode.getChild(DISABLE_HINTS_TAG);
if (element != null) {
for (Object o : element.getChildren(FILE_TAG)) {
Element e = (Element)o;
String url = e.getAttributeValue(URL_ATT);
if (url != null) {
VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url);
if (file != null) {
myDisabledHintsFiles.add(file);
}
}
}
}
}
@NotNull
private Runnable createUpdateRunnable() {
return new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().assertIsDispatchThread();
if (myDisposed || !myProject.isInitialized() || PowerSaveMode.isEnabled()) {
return;
}
if (HeavyProcessLatch.INSTANCE.isRunning()) {
if (myAlarm.isEmpty()) {
myAlarm.addRequest(myUpdateRunnable, mySettings.AUTOREPARSE_DELAY);
}
return;
}
Editor activeEditor = FileEditorManager.getInstance(myProject).getSelectedTextEditor();
final PsiDocumentManagerImpl documentManager = (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(myProject);
Runnable runnable = new Runnable() {
@Override
public void run() {
PassExecutorService.log(getUpdateProgress(), null, "Update Runnable. myUpdateByTimerEnabled:",
myUpdateByTimerEnabled, " something disposed:",
PowerSaveMode.isEnabled() || myDisposed || !myProject.isInitialized(), " activeEditors:",
myProject.isDisposed() ? null : getSelectedEditors());
if (!myUpdateByTimerEnabled) return;
if (myDisposed) return;
ApplicationManager.getApplication().assertIsDispatchThread();
final Collection<FileEditor> activeEditors = getSelectedEditors();
if (activeEditors.isEmpty()) return;
if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
// makes no sense to start from within write action, will cancel anyway
// we'll restart when write action finish
return;
}
if (documentManager.hasUncommitedDocuments()) {
documentManager.cancelAndRunWhenAllCommitted("restart daemon when all committed", this);
return;
}
Map<FileEditor, HighlightingPass[]> passes = new THashMap<FileEditor, HighlightingPass[]>(activeEditors.size());
for (FileEditor fileEditor : activeEditors) {
BackgroundEditorHighlighter highlighter = fileEditor.getBackgroundHighlighter();
if (highlighter != null) {
HighlightingPass[] highlightingPasses = highlighter.createPassesForEditor();
passes.put(fileEditor, highlightingPasses);
}
}
// cancel all after calling createPasses() since there are perverts {@link com.intellij.util.xml.ui.DomUIFactoryImpl} who are changing PSI there
cancelUpdateProgress(true, "Cancel by alarm");
myAlarm.cancelAllRequests();
DaemonProgressIndicator progress = createUpdateProgress();
myPassExecutorService.submitPasses(passes, progress);
}
};
if (activeEditor == null) {
runnable.run();
}
else {
documentManager.cancelAndRunWhenAllCommitted("start daemon when all committed", runnable);
}
}
};
}
@NotNull
private synchronized DaemonProgressIndicator createUpdateProgress() {
DaemonProgressIndicator progress = new DaemonProgressIndicator() {
@Override
public void stopIfRunning() {
super.stopIfRunning();
myProject.getMessageBus().syncPublisher(DAEMON_EVENT_TOPIC).daemonFinished();
}
};
progress.start();
myUpdateProgress = progress;
return progress;
}
@Override
public void autoImportReferenceAtCursor(@NotNull Editor editor, @NotNull PsiFile file) {
for (ReferenceImporter importer : Extensions.getExtensions(ReferenceImporter.EP_NAME)) {
if (importer.autoImportReferenceAtCursor(editor, file)) break;
}
}
synchronized DaemonProgressIndicator getUpdateProgress() {
return myUpdateProgress;
}
@TestOnly
public void allowToInterrupt(boolean can) {
allowToInterrupt = can;
}
@NotNull
private Collection<FileEditor> getSelectedEditors() {
// Editors in modal context
List<Editor> editors = getActiveEditors();
Collection<FileEditor> activeFileEditors = new THashSet<FileEditor>(editors.size());
for (Editor editor : editors) {
TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
activeFileEditors.add(textEditor);
}
if (ApplicationManager.getApplication().getCurrentModalityState() != ModalityState.NON_MODAL) {
return activeFileEditors;
}
// Editors in tabs.
Collection<FileEditor> result = new THashSet<FileEditor>();
Collection<Document> documents = new THashSet<Document>(activeFileEditors.size());
final FileEditor[] tabEditors = FileEditorManager.getInstance(myProject).getSelectedEditors();
for (FileEditor tabEditor : tabEditors) {
if (tabEditor instanceof TextEditor) {
documents.add(((TextEditor)tabEditor).getEditor().getDocument());
}
result.add(tabEditor);
}
// do not duplicate documents
for (FileEditor fileEditor : activeFileEditors) {
if (fileEditor instanceof TextEditor && documents.contains(((TextEditor)fileEditor).getEditor().getDocument())) continue;
result.add(fileEditor);
}
return result;
}
@NotNull
private List<Editor> getActiveEditors() {
return myEditorTracker.getActiveEditors();
}
}