assertion on multiple projects loaded
[fedora-idea.git] / lang-impl / src / com / intellij / psi / impl / PsiDocumentManagerImpl.java
blob181914cd43335a4d1f50047a6d2a013d69cb2aa9
1 package com.intellij.psi.impl;
3 import com.intellij.injected.editor.DocumentWindow;
4 import com.intellij.lang.ASTNode;
5 import com.intellij.openapi.application.Application;
6 import com.intellij.openapi.application.ApplicationManager;
7 import com.intellij.openapi.components.ProjectComponent;
8 import com.intellij.openapi.components.SettingsSavingComponent;
9 import com.intellij.openapi.diagnostic.Logger;
10 import com.intellij.openapi.editor.Document;
11 import com.intellij.openapi.editor.EditorFactory;
12 import com.intellij.openapi.editor.event.DocumentEvent;
13 import com.intellij.openapi.editor.event.DocumentListener;
14 import com.intellij.openapi.editor.ex.DocumentEx;
15 import com.intellij.openapi.fileEditor.FileDocumentManager;
16 import com.intellij.openapi.progress.ProgressIndicator;
17 import com.intellij.openapi.progress.ProgressManager;
18 import com.intellij.openapi.project.Project;
19 import com.intellij.openapi.util.Computable;
20 import com.intellij.openapi.util.Key;
21 import com.intellij.openapi.util.Ref;
22 import com.intellij.openapi.vfs.VirtualFile;
23 import com.intellij.psi.*;
24 import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl;
25 import com.intellij.psi.impl.source.PostprocessReformattingAspect;
26 import com.intellij.psi.impl.source.PsiFileImpl;
27 import com.intellij.psi.impl.source.text.BlockSupportImpl;
28 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
29 import com.intellij.psi.text.BlockSupport;
30 import com.intellij.util.SmartList;
31 import com.intellij.util.concurrency.Semaphore;
32 import com.intellij.util.messages.MessageBus;
33 import org.jetbrains.annotations.NonNls;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36 import org.jetbrains.annotations.TestOnly;
38 import javax.swing.*;
39 import java.util.*;
41 //todo listen & notifyListeners readonly events?
43 public class PsiDocumentManagerImpl extends PsiDocumentManager implements ProjectComponent, DocumentListener, SettingsSavingComponent {
44 private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl");
45 private static final Key<PsiFile> HARD_REF_TO_PSI = new Key<PsiFile>("HARD_REFERENCE_TO_PSI");
46 private static final Key<Boolean> KEY_COMMITING = new Key<Boolean>("Commiting");
47 private static final Key<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT");
49 private final Project myProject;
50 private final PsiManager myPsiManager;
51 private final Key<TextBlock> KEY_TEXT_BLOCK = Key.create("KEY_TEXT_BLOCK");
52 private final Set<Document> myUncommittedDocuments = Collections.synchronizedSet(new HashSet<Document>());
54 private final BlockSupportImpl myBlockSupport;
55 private volatile boolean myIsCommitInProgress;
56 private final PsiToDocumentSynchronizer mySynchronizer;
58 private final List<Listener> myListeners = new ArrayList<Listener>();
59 private Listener[] myCachedListeners = null; //guarded by mylisteners
60 private final SmartPointerManagerImpl mySmartPointerManager;
62 public PsiDocumentManagerImpl(Project project,
63 PsiManager psiManager,
64 SmartPointerManager smartPointerManager,
65 BlockSupport blockSupport,
66 EditorFactory editorFactory,
67 MessageBus bus) {
68 myProject = project;
69 myPsiManager = psiManager;
70 mySmartPointerManager = (SmartPointerManagerImpl)smartPointerManager;
71 myBlockSupport = (BlockSupportImpl)blockSupport;
72 mySynchronizer = new PsiToDocumentSynchronizer(this, bus);
73 myPsiManager.addPsiTreeChangeListener(mySynchronizer);
74 editorFactory.getEventMulticaster().addDocumentListener(this);
77 public void projectOpened() {
80 public void projectClosed() {
83 @NotNull
84 public String getComponentName() {
85 return "PsiDocumentManager";
88 public void initComponent() { }
90 public void disposeComponent() {
91 EditorFactory.getInstance().getEventMulticaster().removeDocumentListener(this);
94 @Nullable
95 public PsiFile getPsiFile(@NotNull Document document) {
96 final PsiFile userData = document.getUserData(HARD_REF_TO_PSI);
97 if(userData != null) return userData;
99 PsiFile psiFile = getCachedPsiFile(document);
100 if (psiFile == null){
101 final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
102 if (virtualFile == null || !virtualFile.isValid()) return null;
103 psiFile = getPsiFile(virtualFile);
104 if (psiFile == null) return null;
106 //psiFile.setModificationStamp(document.getModificationStamp());
107 fireFileCreated(document, psiFile);
110 return psiFile;
114 public PsiFile getCachedPsiFile(@NotNull Document document) {
115 final PsiFile userData = document.getUserData(HARD_REF_TO_PSI);
116 if(userData != null) return userData;
118 final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
119 if (virtualFile == null || !virtualFile.isValid()) return null;
120 return getCachedPsiFile(virtualFile);
123 @Nullable
124 public FileViewProvider getCachedViewProvider(Document document) {
125 final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
126 if (virtualFile == null || !virtualFile.isValid()) return null;
127 return ((PsiManagerEx)myPsiManager).getFileManager().findCachedViewProvider(virtualFile);
130 @Nullable
131 protected PsiFile getCachedPsiFile(VirtualFile virtualFile) {
132 return ((PsiManagerEx)myPsiManager).getFileManager().getCachedPsiFile(virtualFile);
135 @Nullable
136 protected PsiFile getPsiFile(VirtualFile virtualFile) {
137 return ((PsiManagerEx)myPsiManager).getFileManager().findFile(virtualFile);
140 public Document getDocument(@NotNull PsiFile file) {
141 if (file instanceof PsiBinaryFile) return null;
143 Document document = getCachedDocument(file);
144 if (document != null) {
145 if (!file.getViewProvider().isPhysical() &&
146 document.getUserData(HARD_REF_TO_PSI) == null) {
147 document.putUserData(HARD_REF_TO_PSI, file);
149 return document;
152 if (!file.getViewProvider().isEventSystemEnabled()) return null;
153 document = FileDocumentManager.getInstance().getDocument(file.getViewProvider().getVirtualFile());
155 if (!file.getViewProvider().isPhysical()) {
156 document.putUserData(HARD_REF_TO_PSI, file);
159 fireDocumentCreated(document, file);
161 return document;
164 public Document getCachedDocument(@NotNull PsiFile file) {
165 if(!file.isPhysical()) return null;
166 VirtualFile vFile = file.getViewProvider().getVirtualFile();
167 return FileDocumentManager.getInstance().getCachedDocument(vFile);
170 public void commitAllDocuments() {
171 if (myUncommittedDocuments.isEmpty()) return;
173 //long time1 = System.currentTimeMillis();
175 final Document[] documents = getUncommittedDocuments();
176 for (Document document : documents) {
177 commitDocument(document);
180 //long time2 = System.currentTimeMillis();
181 //Statistics.commitTime += (time2 - time1);
184 @Override
185 public void performForCommittedDocument(@NotNull final Document doc, @NotNull final Runnable action) {
186 final Document document = doc instanceof DocumentWindow ? ((DocumentWindow)doc).getDelegate() : doc;
187 if (isUncommited(document)) {
188 addRunOnCommit(document, action);
190 else {
191 action.run();
195 public void addRunOnCommit(Document document, Runnable action) {
196 synchronized (ACTION_AFTER_COMMIT) {
197 List<Runnable> list = document.getUserData(ACTION_AFTER_COMMIT);
198 if (list == null) {
199 document.putUserData(ACTION_AFTER_COMMIT, list = new SmartList<Runnable>());
201 list.add(action);
205 public void commitDocument(final Document doc) {
206 final Document document = doc instanceof DocumentWindow ? ((DocumentWindow)doc).getDelegate() : doc;
207 if (isUncommited(document)) {
208 doCommit(document, null);
212 private void doCommit(final Document document, final PsiFile excludeFile) {
213 assert !(document instanceof DocumentWindow);
214 ApplicationManager.getApplication().runWriteAction(new CommitToPsiFileAction(document,myProject) {
215 public void run() {
216 if (isCommittingDocument(document)) return;
217 document.putUserData(KEY_COMMITING, Boolean.TRUE);
219 try {
220 boolean hasCommits = false;
221 final FileViewProvider viewProvider = getCachedViewProvider(document);
222 if (viewProvider != null) {
223 final List<PsiFile> psiFiles = viewProvider.getAllFiles();
224 for (PsiFile file : psiFiles) {
225 if (file.isValid() && file != excludeFile) {
226 hasCommits |= commit(document, file);
229 viewProvider.contentsSynchronized();
231 myUncommittedDocuments.remove(document);
232 if (hasCommits) {
233 InjectedLanguageUtil.commitAllInjectedDocuments(document, myProject);
236 finally {
237 document.putUserData(KEY_COMMITING, null);
242 List<Runnable> list;
243 synchronized (ACTION_AFTER_COMMIT) {
244 list = document.getUserData(ACTION_AFTER_COMMIT);
245 if (list != null) {
246 list = new ArrayList<Runnable>(list);
247 document.putUserData(ACTION_AFTER_COMMIT, null);
250 if (list != null) {
251 for (final Runnable runnable : list) {
252 runnable.run();
257 public void commitOtherFilesAssociatedWithDocument(final Document document, final PsiFile psiFile) {
258 final FileViewProvider viewProvider = getCachedViewProvider(document);
259 if (viewProvider != null && viewProvider.getAllFiles().size() > 1) {
260 PostprocessReformattingAspect.getInstance(myProject).disablePostprocessFormattingInside(new Runnable() {
261 public void run() {
262 doCommit(document, psiFile);
268 public <T> T commitAndRunReadAction(@NotNull final Computable<T> computation) {
269 final Ref<T> ref = Ref.create(null);
270 commitAndRunReadAction(new Runnable() {
271 public void run() {
272 ref.set(computation.compute());
275 return ref.get();
278 public void commitAndRunReadAction(@NotNull final Runnable runnable) {
279 final Application application = ApplicationManager.getApplication();
280 if (SwingUtilities.isEventDispatchThread()){
281 commitAllDocuments();
282 runnable.run();
284 else{
285 LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed(), "Don't call commitAndRunReadAction inside ReadAction it may cause a deadlock.");
287 final Semaphore s1 = new Semaphore();
288 final Semaphore s2 = new Semaphore();
289 final boolean[] committed = {false};
291 application.runReadAction(
292 new Runnable() {
293 public void run() {
294 if (myUncommittedDocuments.isEmpty()){
295 runnable.run();
296 committed[0] = true;
298 else{
299 s1.down();
300 s2.down();
301 final Runnable commitRunnable = new Runnable() {
302 public void run() {
303 commitAllDocuments();
304 s1.up();
305 s2.waitFor();
308 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
309 if (progressIndicator == null) {
310 ApplicationManager.getApplication().invokeLater(commitRunnable);
312 else {
313 ApplicationManager.getApplication().invokeLater(commitRunnable, progressIndicator.getModalityState());
320 if (!committed[0]){
321 s1.waitFor();
322 application.runReadAction(
323 new Runnable() {
324 public void run() {
325 s2.up();
326 runnable.run();
334 private Listener[] getCachedListeners() {
335 synchronized (myListeners) {
336 if (myCachedListeners == null) {
337 myCachedListeners = myListeners.toArray(new Listener[myListeners.size()]);
339 return myCachedListeners;
343 public void addListener(@NotNull Listener listener) {
344 synchronized (myListeners) {
345 myListeners.add(listener);
346 myCachedListeners = null;
350 public void removeListener(@NotNull Listener listener) {
351 synchronized (myListeners) {
352 myListeners.remove(listener);
353 myCachedListeners = null;
357 public boolean isDocumentBlockedByPsi(@NotNull Document doc) {
358 final FileViewProvider viewProvider = getCachedViewProvider(doc);
359 return viewProvider != null && viewProvider.isLockedByPsiOperations();
362 public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) {
363 if (doc instanceof DocumentWindow) doc = ((DocumentWindow)doc).getDelegate();
364 final PostprocessReformattingAspect component = myProject.getComponent(PostprocessReformattingAspect.class);
365 final FileViewProvider viewProvider = getCachedViewProvider(doc);
366 if(viewProvider != null) component.doPostponedFormatting(viewProvider);
369 private void fireDocumentCreated(Document document, PsiFile file) {
370 Listener[] listeners = getCachedListeners();
371 for (Listener listener : listeners) {
372 listener.documentCreated(document, file);
376 private void fireFileCreated(Document document, PsiFile file) {
377 Listener[] listeners = getCachedListeners();
378 for (Listener listener : listeners) {
379 listener.fileCreated(file, document);
383 @SuppressWarnings({"ALL"})
384 private ASTNode myTreeElementBeingReparsedSoItWontBeCollected;
386 protected boolean commit(final Document document, final PsiFile file) {
387 document.putUserData(TEMP_TREE_IN_DOCUMENT_KEY, null);
389 TextBlock textBlock = getTextBlock(document, file);
390 if (textBlock.isEmpty()) return false;
392 myIsCommitInProgress = true;
393 try{
394 if (mySmartPointerManager != null) { // mock tests
395 SmartPointerManagerImpl.synchronizePointers(file);
398 myTreeElementBeingReparsedSoItWontBeCollected = ((PsiFileImpl)file).calcTreeElement();
400 if (textBlock.isEmpty()) return false ; // if tree was just loaded above textBlock will be cleared by contentsLoaded
402 textBlock.lock();
403 final CharSequence chars = document.getCharsSequence();
404 final Boolean data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY);
405 if (data != null) {
406 document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
407 file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, data);
410 int startOffset;
411 int endOffset;
412 int lengthShift;
413 if (file.getViewProvider().supportsIncrementalReparse(file.getLanguage())) {
414 startOffset = textBlock.getStartOffset();
415 int psiEndOffset = textBlock.getPsiEndOffset();
416 endOffset = psiEndOffset;
417 lengthShift = textBlock.getTextEndOffset() - psiEndOffset;
419 else {
420 startOffset = 0;
421 endOffset = document.getTextLength();
422 lengthShift = document.getTextLength() - file.getTextLength();
424 myBlockSupport.reparseRange(file, startOffset, endOffset, lengthShift, chars);
426 textBlock.unlock();
427 textBlock.clear();
429 finally {
430 myTreeElementBeingReparsedSoItWontBeCollected = null;
431 myIsCommitInProgress = false;
433 return true;
436 public Document[] getUncommittedDocuments() {
437 return myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]);
440 public boolean isUncommited(Document document) {
441 if(getSynchronizer().isInSynchronization(document)) return false;
442 return ((DocumentEx)document).isInEventsHandling() || myUncommittedDocuments.contains(document);
445 public boolean hasUncommitedDocuments() {
446 return !myIsCommitInProgress && !myUncommittedDocuments.isEmpty();
449 private final Key<ASTNode> TEMP_TREE_IN_DOCUMENT_KEY = Key.create("TEMP_TREE_IN_DOCUMENT_KEY");
451 public void beforeDocumentChange(DocumentEvent event) {
452 final Document document = event.getDocument();
454 final FileViewProvider provider = getCachedViewProvider(document);
455 if (provider == null) return;
457 if (provider.getVirtualFile().getFileType().isBinary()) return;
459 final List<PsiFile> files = provider.getAllFiles();
460 boolean hasLockedBlocks = false;
461 for (PsiFile file : files) {
462 if (file == null) continue;
463 final TextBlock textBlock = getTextBlock(document, file);
464 if (textBlock.isLocked()) {
465 hasLockedBlocks = true;
466 continue;
469 if (file instanceof PsiFileImpl){
470 myIsCommitInProgress = true;
471 try{
472 PsiFileImpl psiFile = (PsiFileImpl)file;
473 // tree should be initialized and be kept until commit
474 document.putUserData(TEMP_TREE_IN_DOCUMENT_KEY, psiFile.calcTreeElement());
476 finally{
477 myIsCommitInProgress = false;
481 if (file.isPhysical()) {
482 if (mySmartPointerManager != null) { // mock tests
483 SmartPointerManagerImpl.fastenBelts(file);
488 if (!hasLockedBlocks) {
489 ((SingleRootFileViewProvider)provider).beforeDocumentChanged();
493 public void documentChanged(DocumentEvent event) {
494 final Document document = event.getDocument();
495 final FileViewProvider viewProvider = getCachedViewProvider(document);
496 if (viewProvider != null) {
497 if (viewProvider.getVirtualFile().getFileType().isBinary()) return;
499 final List<PsiFile> files = viewProvider.getAllFiles();
500 boolean commitNecessary = false;
501 for (PsiFile file : files) {
502 if (file == null || file instanceof PsiFileImpl && ((PsiFileImpl)file).getTreeElement() == null) continue;
503 final TextBlock textBlock = getTextBlock(document, file);
504 if (textBlock.isLocked()) continue;
506 if (mySmartPointerManager != null) { // mock tests
507 SmartPointerManagerImpl.unfastenBelts(file);
510 textBlock.documentChanged(event);
511 assert file instanceof PsiFileImpl : event + "; file="+file+"; allFiles="+files+"; viewProvider="+viewProvider;
512 myUncommittedDocuments.add(document);
513 commitNecessary = true;
516 if (commitNecessary && ApplicationManager.getApplication().getCurrentWriteAction(ExternalChangeAction.class) != null){
517 commitDocument(document);
522 public TextBlock getTextBlock(Document document, PsiFile file) {
523 TextBlock textBlock = file.getUserData(KEY_TEXT_BLOCK);
524 if (textBlock == null){
525 textBlock = new TextBlock();
526 file.putUserData(KEY_TEXT_BLOCK, textBlock);
529 return textBlock;
532 public static boolean checkConsistency(PsiFile psiFile, Document document) {
533 //todo hack
534 if (psiFile.getVirtualFile() == null) return true;
536 CharSequence editorText = document.getCharsSequence();
537 int documentLength = document.getTextLength();
538 if (psiFile.textMatches(editorText)) {
539 LOG.assertTrue(psiFile.getTextLength() == documentLength);
540 return true;
543 char[] fileText = psiFile.textToCharArray();
544 @NonNls String error = "File '" + psiFile.getName() + "' text mismatch after reparse. " +
545 "File length=" + fileText.length + "; Doc length=" + documentLength + "\n";
546 int i = 0;
547 for(; i < documentLength; i++){
548 if (i >= fileText.length){
549 error += "editorText.length > psiText.length i=" + i + "\n";
550 break;
552 if (i >= editorText.length()){
553 error += "editorText.length > psiText.length i=" + i + "\n";
554 break;
556 if (editorText.charAt(i) != fileText[i]){
557 error += "first unequal char i=" + i + "\n";
558 break;
561 //error += "*********************************************" + "\n";
562 //if (i <= 500){
563 // error += "Equal part:" + editorText.subSequence(0, i) + "\n";
565 //else{
566 // error += "Equal part start:\n" + editorText.subSequence(0, 200) + "\n";
567 // error += "................................................" + "\n";
568 // error += "................................................" + "\n";
569 // error += "................................................" + "\n";
570 // error += "Equal part end:\n" + editorText.subSequence(i - 200, i) + "\n";
572 error += "*********************************************" + "\n";
573 error += "Editor Text tail:(" + (documentLength - i) + ")\n";// + editorText.subSequence(i, Math.min(i + 300, documentLength)) + "\n";
574 error += "*********************************************" + "\n";
575 error += "Psi Text tail:(" + (fileText.length - i) + ")\n";// + new String(fileText, i, Math.min(i + 300, fileText.length) - i) + "\n";
576 error += "*********************************************" + "\n";
578 if (document instanceof DocumentWindow) {
579 error += "doc: '" + document.getText() + "'\n";
580 error += "psi: '" + psiFile.getText() + "'\n";
581 error += "ast: '" + psiFile.getNode().getText() + "'\n";
582 error += psiFile.getLanguage()+"\n";
583 PsiElement context = psiFile.getContext();
584 if (context != null) {
585 error += "context: " + context +"; text: '" + context.getText() + "'\n";
586 error += "context file: " + context.getContainingFile() + "\n";
588 error += "document window ranges: " + Arrays.asList(((DocumentWindow)document).getHostRanges())+"\n";
590 LOG.error(error);
591 //document.replaceString(0, documentLength, psiFile.getText());
592 return false;
595 public void contentsLoaded(PsiFileImpl file) {
596 final Document document = getCachedDocument(file);
597 if (document != null) getTextBlock(document, file).clear();
600 @TestOnly
601 public void clearUncommitedDocuments() {
602 myUncommittedDocuments.clear();
605 public PsiToDocumentSynchronizer getSynchronizer() {
606 return mySynchronizer;
609 public boolean isCommittingDocument(final Document doc) {
610 return doc.getUserData(KEY_COMMITING) == Boolean.TRUE;
613 public void save() {
614 // Ensure all documents are commited on save so file content dependent indicies, that use PSI to build have consistent content.
615 commitAllDocuments();