/* * Copyright 2000-2016 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.psi.impl; import com.intellij.AppTopics; import com.intellij.injected.editor.DocumentWindow; import com.intellij.injected.editor.DocumentWindowImpl; import com.intellij.injected.editor.EditorWindowImpl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.components.SettingsSavingComponent; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectLocator; import com.intellij.openapi.project.impl.ProjectImpl; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.core.impl.PomModelImpl; import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.impl.source.PostprocessReformattingAspect; import com.intellij.psi.impl.source.tree.injected.MultiHostRegistrarImpl; import com.intellij.util.FileContentUtil; import com.intellij.util.Processor; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBusConnection; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.util.Collection; import java.util.List; //todo listen & notifyListeners readonly events? public class PsiDocumentManagerImpl extends PsiDocumentManagerBase implements SettingsSavingComponent { private final DocumentCommitProcessor myDocumentCommitThread; private final boolean myUnitTestMode = ApplicationManager.getApplication().isUnitTestMode(); public PsiDocumentManagerImpl(@NotNull final Project project, @NotNull PsiManager psiManager, @NotNull EditorFactory editorFactory, @NotNull MessageBus bus, @NonNls @NotNull final DocumentCommitProcessor documentCommitThread) { super(project, psiManager, bus, documentCommitThread); myDocumentCommitThread = documentCommitThread; editorFactory.getEventMulticaster().addDocumentListener(this, project); MessageBusConnection connection = bus.connect(); connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() { @Override public void fileContentLoaded(@NotNull final VirtualFile virtualFile, @NotNull Document document) { PsiFile psiFile = ReadAction.compute(() -> myProject.isDisposed() || !virtualFile.isValid() ? null : getCachedPsiFile(virtualFile)); fireDocumentCreated(document, psiFile); } }); connection.subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() { @Override public void updateFinished(@NotNull Document doc) { documentCommitThread.commitAsynchronously(project, doc, "Bulk update finished", TransactionGuard.getInstance().getContextTransaction()); } }); Disposer.register(project, () -> ((DocumentCommitThread)myDocumentCommitThread).cancelTasksOnProjectDispose(project)); } @Nullable @Override public PsiFile getPsiFile(@NotNull Document document) { final PsiFile psiFile = super.getPsiFile(document); if (myUnitTestMode) { final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile != null && virtualFile.isValid()) { Collection<Project> projects = ProjectLocator.getInstance().getProjectsForFile(virtualFile); if (!projects.isEmpty() && !projects.contains(myProject)) { LOG.error("Trying to get PSI for an alien project. VirtualFile=" + virtualFile + ";\n myProject=" + myProject + ";\n projects returned: " + projects); } } } return psiFile; } @Override public void documentChanged(DocumentEvent event) { super.documentChanged(event); // optimisation: avoid documents piling up during batch processing if (isUncommited(event.getDocument()) && FileDocumentManagerImpl.areTooManyDocumentsInTheQueue(myUncommittedDocuments)) { if (myUnitTestMode) { myStopTrackingDocuments = true; try { LOG.error("Too many uncommitted documents for " + myProject + "(" +myUncommittedDocuments.size()+")"+ ":\n" + StringUtil.join(myUncommittedDocuments, "\n") + "\n\n Project creation trace: " + myProject.getUserData(ProjectImpl.CREATION_TRACE)); } finally { //noinspection TestOnlyProblems clearUncommittedDocuments(); } } // must not commit during document save if (PomModelImpl.isAllowPsiModification() // it can happen that document(forUseInNonAWTThread=true) outside write action caused this && ApplicationManager.getApplication().isWriteAccessAllowed()) { commitAllDocuments(); } } } @Override protected void beforeDocumentChangeOnUnlockedDocument(@NotNull final FileViewProvider viewProvider) { PostprocessReformattingAspect.getInstance(myProject).beforeDocumentChanged(viewProvider); super.beforeDocumentChangeOnUnlockedDocument(viewProvider); } @Override protected boolean finishCommitInWriteAction(@NotNull Document document, @NotNull List<Processor<Document>> finishProcessors, boolean synchronously) { if (ApplicationManager.getApplication().isWriteAccessAllowed()) { // can be false for non-physical PSI EditorWindowImpl.disposeInvalidEditors(); } return super.finishCommitInWriteAction(document, finishProcessors, synchronously); } @Override public boolean isDocumentBlockedByPsi(@NotNull Document doc) { final FileViewProvider viewProvider = getCachedViewProvider(doc); return viewProvider != null && PostprocessReformattingAspect.getInstance(myProject).isViewProviderLocked(viewProvider); } @Override public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) { if (doc instanceof DocumentWindow) doc = ((DocumentWindow)doc).getDelegate(); final PostprocessReformattingAspect component = myProject.getComponent(PostprocessReformattingAspect.class); final FileViewProvider viewProvider = getCachedViewProvider(doc); if (viewProvider != null && component != null) component.doPostponedFormatting(viewProvider); } @Override public void save() { // Ensure all documents are committed on save so file content dependent indices, that use PSI to build have consistent content. try { commitAllDocuments(); } catch (Exception e) { LOG.error(e); } } @Override @TestOnly public void clearUncommittedDocuments() { super.clearUncommittedDocuments(); ((DocumentCommitThread)myDocumentCommitThread).clearQueue(); } @NonNls @Override public String toString() { return super.toString() + " for the project " + myProject + "."; } @Override public void reparseFiles(@NotNull Collection<VirtualFile> files, boolean includeOpenFiles) { FileContentUtil.reparseFiles(myProject, files, includeOpenFiles); } @NotNull @Override protected DocumentWindow freezeWindow(@NotNull DocumentWindow document) { return MultiHostRegistrarImpl.freezeWindow((DocumentWindowImpl)document); } }