diff --git a/java/java-tests/testSrc/com/intellij/index/IndexTest.java b/java/java-tests/testSrc/com/intellij/index/IndexTest.java index 140d8a4fcee7..c5caa1b275f4 100644 --- a/java/java-tests/testSrc/com/intellij/index/IndexTest.java +++ b/java/java-tests/testSrc/com/intellij/index/IndexTest.java @@ -22,9 +22,7 @@ import com.intellij.openapi.util.Factory; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; +import com.intellij.psi.*; import com.intellij.psi.impl.PsiManagerEx; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.testFramework.IdeaTestCase; @@ -221,12 +219,19 @@ public class IndexTest extends IdeaTestCase { PlatformTestUtil.tryGcSoftlyReachableObjects(); assertNull(((PsiManagerEx)PsiManager.getInstance(getProject())).getFileManager().getCachedPsiFile(vFile)); + PsiClass foo = facade.findClass("Foo", scope); + assertNotNull(foo); + assertTrue(foo.isValid()); + assertEquals("class Foo {}", foo.getText()); + assertTrue(foo.isValid()); + + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); assertNull(facade.findClass("Foo", scope)); } }); } - public void _testSavedUncommittedDocument() throws IOException { + public void testSavedUncommittedDocument() throws IOException { VirtualFile dir = getVirtualFile(createTempDirectory()); PsiTestUtil.addSourceContentToRoots(myModule, dir); @@ -247,11 +252,15 @@ public class IndexTest extends IdeaTestCase { Document document = FileDocumentManager.getInstance().getDocument(vFile); document.insertString(0, "class Foo {}"); FileDocumentManager.getInstance().saveDocument(document); - // if Foo exists now, mod count should be different - //assertTrue(count != PsiManager.getInstance(myProject).getModificationTracker().getModificationCount()); - + + assertTrue(count == PsiManager.getInstance(myProject).getModificationTracker().getModificationCount()); + assertNull(facade.findClass("Foo", scope)); + + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); assertNotNull(facade.findClass("Foo", scope)); assertNotNull(facade.findClass("Foo", scope).getText()); + // if Foo exists now, mod count should be different + assertTrue(count != PsiManager.getInstance(myProject).getModificationTracker().getModificationCount()); } }); } diff --git a/java/java-tests/testSrc/com/intellij/psi/PsiModificationTrackerTest.java b/java/java-tests/testSrc/com/intellij/psi/PsiModificationTrackerTest.java index de9aa0981b46..bd9641f576b0 100644 --- a/java/java-tests/testSrc/com/intellij/psi/PsiModificationTrackerTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/PsiModificationTrackerTest.java @@ -14,6 +14,7 @@ import com.intellij.testFramework.IdeaTestCase; import com.intellij.testFramework.PlatformTestUtil; import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; import com.intellij.util.Processor; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; import java.io.IOException; @@ -149,39 +150,79 @@ public class PsiModificationTrackerTest extends LightPlatformCodeInsightFixtureT assertEquals(count + 1, modificationTracker.getJavaStructureModificationCount()); } - public void _testClassShouldNotAppearWithoutEvents() throws IOException { - VirtualFile file = myFixture.getTempDirFixture().createFile("Foo.java", ""); + public void testClassShouldNotAppearWithoutEvents_WithPsi() throws IOException { + final VirtualFile file = myFixture.getTempDirFixture().createFile("Foo.java", ""); final Document document = FileDocumentManager.getInstance().getDocument(file); assertNotNull(document); new WriteCommandAction.Simple(getProject()) { @Override protected void run() throws Throwable { assertNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject()))); - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiManager psiManager = PsiManager.getInstance(getProject()); + PsiModificationTracker tracker = psiManager.getModificationTracker(); long count1 = tracker.getJavaStructureModificationCount(); + PsiJavaFile psiFile = (PsiJavaFile)psiManager.findFile(file); document.insertString(0, "class Foo {}"); assertEquals(count1, tracker.getJavaStructureModificationCount()); // no PSI changes yet //so the class should not exist assertNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject()))); - + assertSize(0, psiFile.getClasses()); + assertEquals("", psiManager.findFile(file).getText()); + PlatformTestUtil.tryGcSoftlyReachableObjects(); + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); assertFalse(count1 == tracker.getJavaStructureModificationCount()); assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject()))); + assertEquals("class Foo {}", psiManager.findFile(file).getText()); + assertEquals("class Foo {}", psiManager.findFile(file).getNode().getText()); + assertSize(1, psiFile.getClasses()); + } + }.execute(); + } + + public void testClassShouldNotAppearWithoutEvents_WithoutPsi() throws IOException { + final GlobalSearchScope allScope = GlobalSearchScope.allScope(getProject()); + final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject()); + final PsiManager psiManager = PsiManager.getInstance(getProject()); + final PsiModificationTracker tracker = psiManager.getModificationTracker(); + + final VirtualFile file = myFixture.getTempDirFixture().createFile("Foo.java", ""); + final Document document = FileDocumentManager.getInstance().getDocument(file); + assertNotNull(document); + new WriteCommandAction.Simple(getProject()) { + @Override + protected void run() throws Throwable { + assertNull(facade.findClass("Foo", allScope)); + long count1 = tracker.getJavaStructureModificationCount(); + + PlatformTestUtil.tryGcSoftlyReachableObjects(); + assertNull(PsiDocumentManager.getInstance(getProject()).getCachedPsiFile(document)); + + document.insertString(0, "class Foo {}"); + + assertFalse(count1 == tracker.getJavaStructureModificationCount()); + assertTrue(PsiDocumentManager.getInstance(getProject()).isCommitted(document)); + assertNotNull(facade.findClass("Foo", allScope)); + + PsiJavaFile psiFile = (PsiJavaFile)psiManager.findFile(file); + assertSize(1, psiFile.getClasses()); + assertEquals("class Foo {}", psiFile.getText()); + assertEquals("class Foo {}", psiFile.getNode().getText()); } }.execute(); } - public void _testClassShouldNotDisappearWithoutEvents() throws IOException { + public void testClassShouldNotDisappearWithoutEvents() throws IOException { new WriteCommandAction.Simple(getProject()) { @Override protected void run() throws Throwable { PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); long count0 = tracker.getJavaStructureModificationCount(); - VirtualFile file = myFixture.addFileToProject("Foo.java", "class Foo {}").getVirtualFile(); + final VirtualFile file = myFixture.addFileToProject("Foo.java", "class Foo {}").getVirtualFile(); final Document document = FileDocumentManager.getInstance().getDocument(file); assertNotNull(document); @@ -191,13 +232,20 @@ public class PsiModificationTrackerTest extends LightPlatformCodeInsightFixtureT document.deleteString(0, document.getTextLength()); + // some plugins (e.g. Copyright) hold file reference in an invokeLater runnable, let them pass + UIUtil.dispatchAllInvocationEvents(); + // gc softly-referenced file and AST PlatformTestUtil.tryGcSoftlyReachableObjects(); - assertNull(((PsiManagerEx)PsiManager.getInstance(getProject())).getFileManager().getCachedPsiFile(file)); + final PsiManagerEx psiManager = (PsiManagerEx)PsiManager.getInstance(getProject()); + assertNull(psiManager.getFileManager().getCachedPsiFile(file)); assertEquals(count1, tracker.getJavaStructureModificationCount()); // no PSI changes yet - //so the class should exist + //so the class should still be there assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject()))); + assertSize(1, ((PsiJavaFile)psiManager.findFile(file)).getClasses()); + assertEquals("class Foo {}", psiManager.findFile(file).getText()); + PlatformTestUtil.tryGcSoftlyReachableObjects(); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); diff --git a/platform/core-api/src/com/intellij/psi/PsiDocumentManager.java b/platform/core-api/src/com/intellij/psi/PsiDocumentManager.java index 7ab0445d2ba4..874a423c06ba 100644 --- a/platform/core-api/src/com/intellij/psi/PsiDocumentManager.java +++ b/platform/core-api/src/com/intellij/psi/PsiDocumentManager.java @@ -19,6 +19,7 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.text.ImmutableCharSequence; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -100,6 +101,13 @@ public abstract class PsiDocumentManager { */ public abstract void commitDocument(@NotNull Document document); + /** + * @param document + * @return the document text that PSI should be based upon. For changed documents, it's their old text until the document is committed + */ + @NotNull + public abstract ImmutableCharSequence getLastCommittedText(@NotNull Document document); + /** * Returns the list of documents which have been modified but not committed. * diff --git a/platform/core-impl/src/com/intellij/psi/SingleRootFileViewProvider.java b/platform/core-impl/src/com/intellij/psi/SingleRootFileViewProvider.java index 862c30448d1a..742483a8d5ab 100644 --- a/platform/core-impl/src/com/intellij/psi/SingleRootFileViewProvider.java +++ b/platform/core-impl/src/com/intellij/psi/SingleRootFileViewProvider.java @@ -492,7 +492,7 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi final VirtualFile virtualFile = getVirtualFile(); if (virtualFile instanceof LightVirtualFile) { Document doc = getCachedDocument(); - if (doc != null) return doc.getCharsSequence(); + if (doc != null) return getLastCommittedText(doc); return ((LightVirtualFile)virtualFile).getContent(); } @@ -501,7 +501,7 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi return LoadTextUtil.loadText(virtualFile); } else { - return document.getCharsSequence(); + return getLastCommittedText(document); } } @@ -517,6 +517,10 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi } } + private CharSequence getLastCommittedText(Document document) { + return PsiDocumentManager.getInstance(myManager.getProject()).getLastCommittedText(document); + } + private class DocumentContent implements Content { @NonNls @Override @@ -530,7 +534,7 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi public CharSequence getText() { final Document document = getDocument(); assert document != null; - return document.getCharsSequence(); + return getLastCommittedText(document); } @Override diff --git a/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitProcessor.java b/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitProcessor.java index 0d96409c2c5d..fc5252623b0c 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitProcessor.java +++ b/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitProcessor.java @@ -76,7 +76,7 @@ public abstract class DocumentCommitProcessor { @Override public String toString() { return "Project: " + project.getName() - + ", Doc: "+ document +" ("+ StringUtil.first(document.getText(), 12, true).replaceAll("\n"," ")+")" + + ", Doc: "+ document +" ("+ StringUtil.first(document.getImmutableCharSequence(), 12, true).toString().replaceAll("\n", " ")+")" +(indicator.isCanceled() ? " (Canceled)" : "") + (removed ? "Removed" : ""); } @@ -103,10 +103,9 @@ public abstract class DocumentCommitProcessor { @NotNull final PsiFile file, final boolean synchronously) { Document document = task.document; - if (PsiDocumentManager.getInstance(task.project).isCommitted(document)) return null; final long startDocModificationTimeStamp = document.getModificationStamp(); final FileElement myTreeElementBeingReparsedSoItWontBeCollected = ((PsiFileImpl)file).calcTreeElement(); - final CharSequence chars = document.getCharsSequence(); + final CharSequence chars = document.getImmutableCharSequence(); final Boolean data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY); if (data != null) { document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null); @@ -130,7 +129,8 @@ public abstract class DocumentCommitProcessor { public boolean process(Document document) { ApplicationManager.getApplication().assertWriteAccessAllowed(); log("Finishing", task, synchronously, document.getModificationStamp(), startDocModificationTimeStamp); - if (document.getModificationStamp() != startDocModificationTimeStamp) { + if (document.getModificationStamp() != startDocModificationTimeStamp || + ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(file.getProject())).getCachedViewProvider(document) != file.getViewProvider()) { return false; // optimistic locking failed } @@ -228,7 +228,7 @@ public abstract class DocumentCommitProcessor { } } - public void log(@NonNls String msg, CommitTask task, boolean synchronously, @NonNls Object... args) { + public void log(@NonNls String msg, @Nullable CommitTask task, boolean synchronously, @NonNls Object... args) { } @NotNull diff --git a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java index f48c6836dff2..865e5875d599 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java @@ -30,6 +30,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.FileIndexFacade; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; @@ -37,15 +38,17 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl; import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.text.BlockSupportImpl; import com.intellij.psi.text.BlockSupport; import com.intellij.util.FileContentUtilCore; import com.intellij.util.Processor; import com.intellij.util.SmartList; import com.intellij.util.SystemProperties; import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.containers.ConcurrentHashSet; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; -import gnu.trove.THashSet; +import com.intellij.util.text.ImmutableCharSequence; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -56,19 +59,20 @@ import java.util.*; public abstract class PsiDocumentManagerBase extends PsiDocumentManager implements ProjectComponent, DocumentListener { protected static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl"); - protected static final Key HARD_REF_TO_PSI = new Key("HARD_REFERENCE_TO_PSI"); - protected static final Key> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT"); + private static final Key HARD_REF_TO_PSI = new Key("HARD_REFERENCE_TO_PSI"); + private static final Key> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT"); protected final Project myProject; - protected final PsiManager myPsiManager; - protected final DocumentCommitProcessor myDocumentCommitProcessor; - protected final Set myUncommittedDocuments = Collections.synchronizedSet(new THashSet()); + private final PsiManager myPsiManager; + private final DocumentCommitProcessor myDocumentCommitProcessor; + protected final Set myUncommittedDocuments = new ConcurrentHashSet(); + private final Map myLastCommittedTexts = ContainerUtil.newConcurrentMap(); - protected volatile boolean myIsCommitInProgress; - protected final PsiToDocumentSynchronizer mySynchronizer; + private volatile boolean myIsCommitInProgress; + private final PsiToDocumentSynchronizer mySynchronizer; - protected final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); - protected final SmartPointerManagerImpl mySmartPointerManager; + private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + private final SmartPointerManagerImpl mySmartPointerManager; public PsiDocumentManagerBase(@NotNull final Project project, @NotNull PsiManager psiManager, @@ -152,7 +156,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen } @Nullable - protected PsiFile getPsiFile(VirtualFile virtualFile) { + private PsiFile getPsiFile(VirtualFile virtualFile) { return ((PsiManagerEx)myPsiManager).getFileManager().findFile(virtualFile); } @@ -319,7 +323,10 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen break; } } + myLastCommittedTexts.remove(document); viewProvider.contentsSynchronized(); + } else { + handleCommitWithoutPsi(document); } } finally { @@ -541,6 +548,13 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen } } + @Override + @NotNull + public ImmutableCharSequence getLastCommittedText(@NotNull Document document) { + ImmutableCharSequence text = myLastCommittedTexts.get(document); + return text != null ? text : document.getImmutableCharSequence(); + } + @Override @NotNull public Document[] getUncommittedDocuments() { @@ -573,6 +587,9 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen @Override public void beforeDocumentChange(DocumentEvent event) { final Document document = event.getDocument(); + if (!(document instanceof DocumentWindow) && !myLastCommittedTexts.containsKey(document)) { + myLastCommittedTexts.put(document, document.getImmutableCharSequence()); + } final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider == null) return; @@ -608,8 +625,14 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen public void documentChanged(DocumentEvent event) { final Document document = event.getDocument(); final FileViewProvider viewProvider = getCachedViewProvider(document); - if (viewProvider == null) return; - if (!isRelevant(viewProvider)) return; + if (viewProvider == null) { + handleCommitWithoutPsi(document); + return; + } + if (!isRelevant(viewProvider)) { + myLastCommittedTexts.remove(document); + return; + } ApplicationManager.getApplication().assertWriteAccessAllowed(); final List files = viewProvider.getAllFiles(); @@ -647,15 +670,56 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen if (commitNecessary) { assert !(document instanceof DocumentWindow); myUncommittedDocuments.add(document); + myDocumentCommitProcessor.log("added uncommitted doc", null, false, myProject, document, ((DocumentEx)document).isInBulkUpdate()); if (forceCommit) { commitDocument(document); } else if (!((DocumentEx)document).isInBulkUpdate()) { myDocumentCommitProcessor.commitAsynchronously(myProject, document, event); } + } else { + myLastCommittedTexts.remove(document); } } + public void handleCommitWithoutPsi(final Document document) { + final ImmutableCharSequence prevText = myLastCommittedTexts.remove(document); + if (prevText == null) { + return; + } + + if (!myProject.isInitialized() || myProject.isDisposed()) { + return; + } + + VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); + if (virtualFile == null || !FileIndexFacade.getInstance(myProject).isInContent(virtualFile)) { + return; + } + + final PsiFileImpl psiFile = (PsiFileImpl)getPsiFile(document); + if (psiFile == null) { + return; + } + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + psiFile.getViewProvider().beforeContentsSynchronized(); + synchronized (PsiLock.LOCK) { + final int oldLength = prevText.length(); + PsiManagerImpl manager = (PsiManagerImpl)psiFile.getManager(); + BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, true); + BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, false); + psiFile.onContentReload(); + BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, false); + BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, true); + } + psiFile.getViewProvider().contentsSynchronized(); + } + }); + + } + private boolean isRelevant(@NotNull FileViewProvider viewProvider) { VirtualFile virtualFile = viewProvider.getVirtualFile(); return !virtualFile.getFileType().isBinary() && @@ -729,6 +793,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen @TestOnly public void clearUncommittedDocuments() { + myLastCommittedTexts.clear(); myUncommittedDocuments.clear(); mySynchronizer.cleanupForNextTest(); } diff --git a/platform/core-impl/src/com/intellij/psi/stubs/StubTreeBuilder.java b/platform/core-impl/src/com/intellij/psi/stubs/StubTreeBuilder.java index 2af2eff663e7..5ef0038eeb7e 100644 --- a/platform/core-impl/src/com/intellij/psi/stubs/StubTreeBuilder.java +++ b/platform/core-impl/src/com/intellij/psi/stubs/StubTreeBuilder.java @@ -17,13 +17,17 @@ package com.intellij.psi.stubs; import com.intellij.lang.Language; import com.intellij.lang.LanguageParserDefinitions; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IFileElementType; import com.intellij.psi.tree.IStubFileElementType; import com.intellij.util.indexing.FileContent; +import com.intellij.util.indexing.FileContentImpl; import com.intellij.util.indexing.IndexingDataKeys; import com.intellij.util.indexing.SubstitutedFileType; import org.jetbrains.annotations.Nullable; @@ -55,9 +59,21 @@ public class StubTreeBuilder { Language l = languageFileType.getLanguage(); final IFileElementType type = LanguageParserDefinitions.INSTANCE.forLanguage(l).getFileNodeType(); - PsiFile psi = inputData.getPsiFile(); + PsiFile psi = null; + CharSequence contentAsText = null; + Document document = FileDocumentManager.getInstance().getCachedDocument(inputData.getFile()); + if (document != null) { + PsiFile existingPsi = PsiDocumentManager.getInstance(inputData.getProject()).getPsiFile(document); + if (existingPsi != null) { + contentAsText = existingPsi.getText(); + psi = ((FileContentImpl) inputData).createFileFromText(contentAsText); + } + } + if (contentAsText == null) { + contentAsText = inputData.getContentAsText(); + psi = inputData.getPsiFile(); + } psi = psi.getViewProvider().getStubBindingRoot(); - CharSequence contentAsText = inputData.getContentAsText(); psi.putUserData(IndexingDataKeys.FILE_TEXT_CONTENT_KEY, contentAsText); try { diff --git a/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java b/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java index a29935a0c049..f320f856f541 100644 --- a/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java +++ b/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java @@ -66,20 +66,23 @@ public final class FileContentImpl extends UserDataHolderBase implements FileCon } if (psi == null) { - Project project = getProject(); - if (project == null) { - project = DefaultProjectFactory.getInstance().getDefaultProject(); - } - final Language language = ((LanguageFileType)getFileTypeWithoutSubstitution()).getLanguage(); - final Language substitutedLanguage = LanguageSubstitutors.INSTANCE.substituteLanguage(language, getFile(), project); - psi = PsiFileFactory.getInstance(project).createFileFromText(getFileName(), substitutedLanguage, getContentAsText(), false, false, true); - + psi = createFileFromText(getContentAsText()); psi.putUserData(IndexingDataKeys.VIRTUAL_FILE, getFile()); putUserData(CACHED_PSI, psi); } return psi; } + public PsiFile createFileFromText(CharSequence text) { + Project project = getProject(); + if (project == null) { + project = DefaultProjectFactory.getInstance().getDefaultProject(); + } + final Language language = ((LanguageFileType)getFileTypeWithoutSubstitution()).getLanguage(); + final Language substitutedLanguage = LanguageSubstitutors.INSTANCE.substituteLanguage(language, getFile(), project); + return PsiFileFactory.getInstance(project).createFileFromText(getFileName(), substitutedLanguage, text, false, false, true); + } + public static class IllegalDataException extends RuntimeException { public IllegalDataException(final String message) { super(message); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java b/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java index 174903be0b0d..5168d3573902 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/DocumentCommitThread.java @@ -45,7 +45,9 @@ import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; public class DocumentCommitThread extends DocumentCommitProcessor implements Runnable, Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.DocumentCommitThread"); @@ -143,7 +145,7 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run private final StringBuilder log = new StringBuilder(); @Override - public void log(@NonNls String msg, CommitTask task, boolean synchronously, @NonNls Object... args) { + public void log(@NonNls String msg, @Nullable CommitTask task, boolean synchronously, @NonNls Object... args) { if (true) return; String indent = new SimpleDateFormat("mm:ss:SSSS").format(new Date()) + @@ -400,9 +402,16 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run public void run() { ApplicationManager.getApplication().assertReadAccessAllowed(); if (project.isDisposed()) return; + final PsiDocumentManagerImpl documentManager = (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(project); + if (documentManager.isCommitted(document)) return; + FileViewProvider viewProvider = documentManager.getCachedViewProvider(document); - if (viewProvider == null) return; + if (viewProvider == null) { + finishProcessors.add(handleCommitWithoutPsi(documentManager, document, task, synchronously)); + return; + } + List psiFiles = viewProvider.getAllFiles(); for (PsiFile file : psiFiles) { if (file.isValid()) { @@ -482,6 +491,25 @@ public class DocumentCommitThread extends DocumentCommitProcessor implements Run return finishRunnable; } + private Processor handleCommitWithoutPsi(final PsiDocumentManagerImpl documentManager, + Document document, + final CommitTask task, final boolean synchronously) { + final long startDocModificationTimeStamp = document.getModificationStamp(); + return new Processor() { + @Override + public boolean process(Document document) { + log("Finishing without PSI", task, synchronously, document.getModificationStamp(), startDocModificationTimeStamp); + if (document.getModificationStamp() != startDocModificationTimeStamp || + documentManager.getCachedViewProvider(document) != null) { + return false; + } + + documentManager.handleCommitWithoutPsi(document); + return true; + } + }; + } + private boolean processAll(final Processor processor) { final boolean[] result = {true}; synchronized (documentsToCommit) { diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java index ea22194d2f2c..964227144fb9 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java @@ -109,11 +109,12 @@ public class StubTreeLoaderImpl extends StubTreeLoader { SerializedStubTree stubTree = datas.get(0); if (!stubTree.contentLengthMatches(vFile.getLength(), getCurrentTextContentLength(project, vFile, document))) { - return processError(vFile, - "Outdated stub in index: " + StubUpdatingIndex.getIndexingStampInfo(vFile) + - ", docSaved=" + saved + - ", queried at " + vFile.getTimeStamp(), - null); + //todo find another way of early stub-ast mismatch prevention + //return processError(vFile, + // "Outdated stub in index: " + StubUpdatingIndex.getIndexingStampInfo(vFile) + + // ", docSaved=" + saved + + // ", queried at " + vFile.getTimeStamp(), + // null); } Stub stub; diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubUpdatingIndex.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubUpdatingIndex.java index 4e5ca5f0377c..ac8d040b0d2d 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubUpdatingIndex.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubUpdatingIndex.java @@ -43,7 +43,7 @@ import java.util.concurrent.Callable; /* * @author max */ -public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtension { +public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtension implements PsiDependentIndex { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubs.StubUpdatingIndex"); // todo remove once we don't need this for stub-ast mismatch debug info diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java index eb4a51899942..7e66423cbdf9 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -56,6 +56,9 @@ import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon; import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; import com.intellij.psi.*; import com.intellij.psi.impl.PsiDocumentTransactionListener; +import com.intellij.psi.impl.PsiManagerImpl; +import com.intellij.psi.impl.PsiTreeChangeEventImpl; +import com.intellij.psi.impl.PsiTreeChangePreprocessor; import com.intellij.psi.impl.cache.impl.id.PlatformIdTableBuilding; import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.search.EverythingGlobalScope; @@ -106,6 +109,7 @@ public class FileBasedIndexImpl extends FileBasedIndex { private final TObjectIntHashMap> myIndexIdToVersionMap = new TObjectIntHashMap>(); private final Set> myNotRequiringContentIndices = new THashSet>(); private final Set> myRequiringContentIndices = new THashSet>(); + private final Set> myPsiDependentIndices = new THashSet>(); private final Set myNoLimitCheckTypes = new THashSet(); private final PerIndexDocumentVersionMap myLastIndexedDocStamps = new PerIndexDocumentVersionMap(); @@ -442,6 +446,7 @@ public class FileBasedIndexImpl extends FileBasedIndex { else { myRequiringContentIndices.add(name); } + if (extension instanceof PsiDependentIndex) myPsiDependentIndices.add(name); myNoLimitCheckTypes.addAll(extension.getFileTypesWithSizeLimitNotApplicable()); break; } @@ -1392,12 +1397,15 @@ public class FileBasedIndexImpl extends FileBasedIndex { } @NotNull - private Set getUnsavedOrTransactedDocuments() { - final Set docs = new THashSet(Arrays.asList(myFileDocumentManager.getUnsavedDocuments())); + private Set getUnsavedDocuments() { + return new THashSet(Arrays.asList(myFileDocumentManager.getUnsavedDocuments())); + } + + @NotNull + private Set getTransactedDocuments() { synchronized (myTransactionMap) { - docs.addAll(myTransactionMap.keySet()); + return new THashSet(myTransactionMap.keySet()); } - return docs; } private void indexUnsavedDocuments(@NotNull ID indexId, @@ -1408,7 +1416,8 @@ public class FileBasedIndexImpl extends FileBasedIndex { return; // no need to index unsaved docs } - final Set documents = getUnsavedOrTransactedDocuments(); + Set documents = myPsiDependentIndices.contains(indexId) ? getTransactedDocuments() : getUnsavedDocuments(); + if (!documents.isEmpty()) { // now index unsaved data final StorageGuard.Holder guard = setDataBufferingEnabled(true); @@ -1419,11 +1428,9 @@ public class FileBasedIndexImpl extends FileBasedIndex { semaphore.down(); boolean allDocsProcessed = true; - boolean hasUncommittedDocuments = project == null; try { for (Document document : documents) { allDocsProcessed &= indexUnsavedDocument(document, indexId, project, filter, restrictedFile); - if (!hasUncommittedDocuments) hasUncommittedDocuments = PsiDocumentManager.getInstance(project).isUncommited(document); ProgressManager.checkCanceled(); } } @@ -1443,7 +1450,6 @@ public class FileBasedIndexImpl extends FileBasedIndex { // if we have uncommitted documents in unsaved documents, we may index old psi with new uncommitted doc, // to properly reindex with new psi / new doc we don't mark index up to date in this case (IDEA-111448) - if (!hasUncommittedDocuments) myUpToDateIndices.add(indexId); } } } @@ -2439,6 +2445,27 @@ public class FileBasedIndexImpl extends FileBasedIndex { public void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project) { myIndexableSets.add(set); myIndexableSetToProjectMap.put(set, project); + if (project != null) { + ((PsiManagerImpl)PsiManager.getInstance(project)).addTreeChangePreprocessor(new PsiTreeChangePreprocessor() { + @Override + public void treeChanged(@NotNull PsiTreeChangeEventImpl event) { + if (event.isGenericChange() && + event.getCode() == PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED) { + PsiFile file = event.getFile(); + if (file != null) { + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile instanceof VirtualFileWithId) { + for(ID psiBackedIndex:myPsiDependentIndices) { + IndexingStamp.update(virtualFile, psiBackedIndex, IndexInfrastructure.INVALID_STAMP2); + } + myChangedFilesCollector.scheduleForUpdate(virtualFile); + IndexingStamp.flushCache(virtualFile); + } + } + } + } + }); + } } @Override diff --git a/platform/lang-impl/src/com/intellij/util/indexing/MapReduceIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/MapReduceIndex.java index 5a9fb41ba121..fb90b75f5351 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/MapReduceIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/MapReduceIndex.java @@ -24,6 +24,7 @@ import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.Processor; +import com.intellij.util.SmartList; import com.intellij.util.io.PersistentHashMap; import gnu.trove.THashMap; import gnu.trove.TObjectObjectProcedure; @@ -204,7 +205,6 @@ public class MapReduceIndex implements UpdatableIndex update(final int inputId, @Nullable final Input content) { - assert myInputsIndex != null; final Map data = content != null ? myIndexer.map(content) : Collections.emptyMap(); @@ -222,6 +222,9 @@ public class MapReduceIndex implements UpdatableIndex>() { @Override public Collection call() throws Exception { + if (myInputsIndex == null) { + return new SmartList((Key)(Integer)inputId); + } final Collection oldKeys = myInputsIndex.get(inputId); return oldKeys == null? Collections.emptyList() : oldKeys; } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/PsiDependentIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/PsiDependentIndex.java new file mode 100644 index 000000000000..991c75b82ba2 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/util/indexing/PsiDependentIndex.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2014 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.util.indexing; + +/** + * Created by Maxim.Mossienko on 1/4/14. + */ +public interface PsiDependentIndex { +} diff --git a/platform/testFramework/src/com/intellij/mock/MockPsiDocumentManager.java b/platform/testFramework/src/com/intellij/mock/MockPsiDocumentManager.java index 05e865ab3a53..15bcf670df01 100644 --- a/platform/testFramework/src/com/intellij/mock/MockPsiDocumentManager.java +++ b/platform/testFramework/src/com/intellij/mock/MockPsiDocumentManager.java @@ -9,6 +9,7 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; +import com.intellij.util.text.ImmutableCharSequence; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -56,6 +57,12 @@ public class MockPsiDocumentManager extends PsiDocumentManager { public void commitDocument(@NotNull Document document) { } + @NotNull + @Override + public ImmutableCharSequence getLastCommittedText(@NotNull Document document) { + return document.getImmutableCharSequence(); + } + @Override @NotNull public Document[] getUncommittedDocuments() {