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