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
;
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
,
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() {
84 public String
getComponentName() {
85 return "PsiDocumentManager";
88 public void initComponent() { }
90 public void disposeComponent() {
91 EditorFactory
.getInstance().getEventMulticaster().removeDocumentListener(this);
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
);
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
);
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
);
131 protected PsiFile
getCachedPsiFile(VirtualFile virtualFile
) {
132 return ((PsiManagerEx
)myPsiManager
).getFileManager().getCachedPsiFile(virtualFile
);
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
);
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
);
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);
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
);
195 public void addRunOnCommit(Document document
, Runnable action
) {
196 synchronized (ACTION_AFTER_COMMIT
) {
197 List
<Runnable
> list
= document
.getUserData(ACTION_AFTER_COMMIT
);
199 document
.putUserData(ACTION_AFTER_COMMIT
, list
= new SmartList
<Runnable
>());
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
) {
216 if (isCommittingDocument(document
)) return;
217 document
.putUserData(KEY_COMMITING
, Boolean
.TRUE
);
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
);
233 InjectedLanguageUtil
.commitAllInjectedDocuments(document
, myProject
);
237 document
.putUserData(KEY_COMMITING
, null);
243 synchronized (ACTION_AFTER_COMMIT
) {
244 list
= document
.getUserData(ACTION_AFTER_COMMIT
);
246 list
= new ArrayList
<Runnable
>(list
);
247 document
.putUserData(ACTION_AFTER_COMMIT
, null);
251 for (final Runnable runnable
: list
) {
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() {
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() {
272 ref
.set(computation
.compute());
278 public void commitAndRunReadAction(@NotNull final Runnable runnable
) {
279 final Application application
= ApplicationManager
.getApplication();
280 if (SwingUtilities
.isEventDispatchThread()){
281 commitAllDocuments();
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(
294 if (myUncommittedDocuments
.isEmpty()){
301 final Runnable commitRunnable
= new Runnable() {
303 commitAllDocuments();
308 final ProgressIndicator progressIndicator
= ProgressManager
.getInstance().getProgressIndicator();
309 if (progressIndicator
== null) {
310 ApplicationManager
.getApplication().invokeLater(commitRunnable
);
313 ApplicationManager
.getApplication().invokeLater(commitRunnable
, progressIndicator
.getModalityState());
322 application
.runReadAction(
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;
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
403 final CharSequence chars
= document
.getCharsSequence();
404 final Boolean data
= document
.getUserData(BlockSupport
.DO_NOT_REPARSE_INCREMENTALLY
);
406 document
.putUserData(BlockSupport
.DO_NOT_REPARSE_INCREMENTALLY
, null);
407 file
.putUserData(BlockSupport
.DO_NOT_REPARSE_INCREMENTALLY
, data
);
413 if (file
.getViewProvider().supportsIncrementalReparse(file
.getLanguage())) {
414 startOffset
= textBlock
.getStartOffset();
415 int psiEndOffset
= textBlock
.getPsiEndOffset();
416 endOffset
= psiEndOffset
;
417 lengthShift
= textBlock
.getTextEndOffset() - psiEndOffset
;
421 endOffset
= document
.getTextLength();
422 lengthShift
= document
.getTextLength() - file
.getTextLength();
424 myBlockSupport
.reparseRange(file
, startOffset
, endOffset
, lengthShift
, chars
);
430 myTreeElementBeingReparsedSoItWontBeCollected
= null;
431 myIsCommitInProgress
= false;
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;
469 if (file
instanceof PsiFileImpl
){
470 myIsCommitInProgress
= true;
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());
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
);
532 public static boolean checkConsistency(PsiFile psiFile
, Document document
) {
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
);
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";
547 for(; i
< documentLength
; i
++){
548 if (i
>= fileText
.length
){
549 error
+= "editorText.length > psiText.length i=" + i
+ "\n";
552 if (i
>= editorText
.length()){
553 error
+= "editorText.length > psiText.length i=" + i
+ "\n";
556 if (editorText
.charAt(i
) != fileText
[i
]){
557 error
+= "first unequal char i=" + i
+ "\n";
561 //error += "*********************************************" + "\n";
563 // error += "Equal part:" + editorText.subSequence(0, i) + "\n";
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";
591 //document.replaceString(0, documentLength, psiFile.getText());
595 public void contentsLoaded(PsiFileImpl file
) {
596 final Document document
= getCachedDocument(file
);
597 if (document
!= null) getTextBlock(document
, file
).clear();
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
;
614 // Ensure all documents are commited on save so file content dependent indicies, that use PSI to build have consistent content.
615 commitAllDocuments();