fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / impl / PsiDocumentManagerImpl.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2
3 package com.intellij.psi.impl;
4
5 import com.intellij.AppTopics;
6 import com.intellij.injected.editor.DocumentWindow;
7 import com.intellij.lang.ASTNode;
8 import com.intellij.lang.injection.InjectedLanguageManager;
9 import com.intellij.openapi.application.ApplicationManager;
10 import com.intellij.openapi.application.ReadAction;
11 import com.intellij.openapi.editor.Document;
12 import com.intellij.openapi.editor.EditorFactory;
13 import com.intellij.openapi.editor.event.DocumentEvent;
14 import com.intellij.openapi.editor.impl.event.EditorEventMulticasterImpl;
15 import com.intellij.openapi.fileEditor.FileDocumentManager;
16 import com.intellij.openapi.fileEditor.FileDocumentManagerListener;
17 import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
18 import com.intellij.openapi.progress.ProgressIndicator;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.project.ProjectLocator;
21 import com.intellij.openapi.project.impl.ProjectImpl;
22 import com.intellij.openapi.util.Disposer;
23 import com.intellij.openapi.util.Segment;
24 import com.intellij.openapi.util.TextRange;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.pom.core.impl.PomModelImpl;
28 import com.intellij.psi.FileViewProvider;
29 import com.intellij.psi.PsiFile;
30 import com.intellij.psi.impl.source.PostprocessReformattingAspect;
31 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl;
32 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
33 import com.intellij.util.ArrayUtil;
34 import com.intellij.util.FileContentUtil;
35 import com.intellij.util.containers.ContainerUtil;
36 import com.intellij.util.messages.MessageBusConnection;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40 import org.jetbrains.annotations.TestOnly;
41
42 import java.util.*;
43
44 //todo listen & notifyListeners readonly events?
45 public final class PsiDocumentManagerImpl extends PsiDocumentManagerBase {
46   private final boolean myUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
47
48   public PsiDocumentManagerImpl(@NotNull Project project) {
49     super(project);
50
51     EditorFactory.getInstance().getEventMulticaster().addDocumentListener(this, this);
52     ((EditorEventMulticasterImpl)EditorFactory.getInstance().getEventMulticaster()).addPrioritizedDocumentListener(new PriorityEventCollector(), this);
53     MessageBusConnection connection = project.getMessageBus().connect(this);
54     connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerListener() {
55       @Override
56       public void fileContentLoaded(@NotNull final VirtualFile virtualFile, @NotNull Document document) {
57         PsiFile psiFile = ReadAction.compute(() -> myProject.isDisposed() || !virtualFile.isValid() ? null : getCachedPsiFile(virtualFile));
58         fireDocumentCreated(document, psiFile);
59       }
60     });
61     Disposer.register(this, () -> ((DocumentCommitThread)myDocumentCommitProcessor).cancelTasksOnProjectDispose(project));
62   }
63
64   @Nullable
65   @Override
66   public PsiFile getPsiFile(@NotNull Document document) {
67     final PsiFile psiFile = super.getPsiFile(document);
68     if (myUnitTestMode) {
69       final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
70       if (virtualFile != null && virtualFile.isValid()) {
71         Collection<Project> projects = ProjectLocator.getInstance().getProjectsForFile(virtualFile);
72         if (!projects.isEmpty() && !projects.contains(myProject)) {
73           LOG.error("Trying to get PSI for an alien project. VirtualFile=" + virtualFile +
74                     ";\n myProject=" + myProject +
75                     ";\n projects returned: " + projects);
76         }
77       }
78     }
79     return psiFile;
80   }
81
82   @Override
83   public void documentChanged(@NotNull DocumentEvent event) {
84     super.documentChanged(event);
85     // optimisation: avoid documents piling up during batch processing
86     if (isUncommited(event.getDocument()) && FileDocumentManagerImpl.areTooManyDocumentsInTheQueue(myUncommittedDocuments)) {
87       if (myUnitTestMode) {
88         myStopTrackingDocuments = true;
89         try {
90           LOG.error("Too many uncommitted documents for " + myProject + "(" +myUncommittedDocuments.size()+")"+
91                     ":\n" + StringUtil.join(myUncommittedDocuments, "\n") +
92                     (myProject instanceof ProjectImpl ? "\n\n Project creation trace: " + ((ProjectImpl)myProject).getCreationTrace() : ""));
93         }
94         finally {
95           //noinspection TestOnlyProblems
96           clearUncommittedDocuments();
97         }
98       }
99       // must not commit during document save
100       if (PomModelImpl.isAllowPsiModification()
101           // it can happen that document(forUseInNonAWTThread=true) outside write action caused this
102           && ApplicationManager.getApplication().isWriteAccessAllowed()) {
103         // commit one document to avoid OOME
104         for (Document document : myUncommittedDocuments) {
105           if (document != event.getDocument()) {
106             doCommitWithoutReparse(document);
107             break;
108           }
109         }
110       }
111     }
112   }
113
114   @Override
115   protected void beforeDocumentChangeOnUnlockedDocument(@NotNull final FileViewProvider viewProvider) {
116     PostprocessReformattingAspect.getInstance(myProject).assertDocumentChangeIsAllowed(viewProvider);
117     super.beforeDocumentChangeOnUnlockedDocument(viewProvider);
118   }
119
120
121   @Override
122   protected boolean finishCommitInWriteAction(@NotNull Document document,
123                                               @NotNull List<? extends BooleanRunnable> finishProcessors,
124                                               @NotNull List<? extends BooleanRunnable> reparseInjectedProcessors,
125                                               boolean synchronously,
126                                               boolean forceNoPsiCommit) {
127     if (ApplicationManager.getApplication().isWriteAccessAllowed()) { // can be false for non-physical PSI
128       InjectedLanguageManagerImpl.disposeInvalidEditors();
129     }
130     return super.finishCommitInWriteAction(document, finishProcessors, reparseInjectedProcessors, synchronously, forceNoPsiCommit);
131   }
132
133   @Override
134   public boolean isDocumentBlockedByPsi(@NotNull Document doc) {
135     final FileViewProvider viewProvider = getCachedViewProvider(doc);
136     return viewProvider != null && PostprocessReformattingAspect.getInstance(myProject).isViewProviderLocked(viewProvider);
137   }
138
139   @Override
140   public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) {
141     if (doc instanceof DocumentWindow) doc = ((DocumentWindow)doc).getDelegate();
142     final PostprocessReformattingAspect component = myProject.getComponent(PostprocessReformattingAspect.class);
143     final FileViewProvider viewProvider = getCachedViewProvider(doc);
144     if (viewProvider != null && component != null) component.doPostponedFormatting(viewProvider);
145   }
146
147   @Override
148   @TestOnly
149   public void clearUncommittedDocuments() {
150     super.clearUncommittedDocuments();
151     ((DocumentCommitThread)myDocumentCommitProcessor).clearQueue();
152   }
153
154   @NotNull
155   @Override
156   List<BooleanRunnable> reparseChangedInjectedFragments(@NotNull Document hostDocument,
157                                                         @NotNull PsiFile hostPsiFile,
158                                                         @NotNull TextRange hostChangedRange,
159                                                         @NotNull ProgressIndicator indicator,
160                                                         @NotNull ASTNode oldRoot,
161                                                         @NotNull ASTNode newRoot) {
162     List<DocumentWindow> changedInjected = InjectedLanguageManager.getInstance(myProject).getCachedInjectedDocumentsInRange(hostPsiFile, hostChangedRange);
163     if (changedInjected.isEmpty()) return Collections.emptyList();
164     FileViewProvider hostViewProvider = hostPsiFile.getViewProvider();
165     List<DocumentWindow> fromLast = new ArrayList<>(changedInjected);
166     // make sure modifications do not ruin all document offsets after
167     fromLast.sort(Collections.reverseOrder(Comparator.comparingInt(doc -> ArrayUtil.getLastElement(doc.getHostRanges()).getEndOffset())));
168     List<BooleanRunnable> result = new ArrayList<>(changedInjected.size());
169     for (DocumentWindow document : fromLast) {
170       Segment[] ranges = document.getHostRanges();
171       if (ranges.length != 0) {
172         // host document change has left something valid in this document window place. Try to reparse.
173         PsiFile injectedPsiFile = getCachedPsiFile(document);
174         if (injectedPsiFile  == null || !injectedPsiFile.isValid()) continue;
175
176         BooleanRunnable runnable = InjectedLanguageUtil.reparse(injectedPsiFile, document, hostPsiFile, hostDocument, hostViewProvider, indicator, oldRoot, newRoot, this);
177         ContainerUtil.addIfNotNull(result, runnable);
178       }
179     }
180
181     return result;
182   }
183
184   @NonNls
185   @Override
186   public String toString() {
187     return super.toString() + " for the project " + myProject + ".";
188   }
189
190   @Override
191   public void reparseFiles(@NotNull Collection<? extends VirtualFile> files, boolean includeOpenFiles) {
192     FileContentUtil.reparseFiles(myProject, files, includeOpenFiles);
193   }
194
195   @NotNull
196   @Override
197   protected DocumentWindow freezeWindow(@NotNull DocumentWindow document) {
198     return InjectedLanguageManager.getInstance(myProject).freezeWindow(document);
199   }
200
201   @Override
202   public void associatePsi(@NotNull Document document, @Nullable PsiFile file) {
203     if (file != null) {
204       VirtualFile vFile = file.getViewProvider().getVirtualFile();
205       Document cachedDocument = FileDocumentManager.getInstance().getCachedDocument(vFile);
206       if (cachedDocument != null && cachedDocument != document) {
207         throw new IllegalStateException("Can't replace existing document");
208       }
209
210       FileDocumentManagerImpl.registerDocument(document, vFile);
211     }
212   }
213 }