mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
when file content is reloaded externally, clear all roots in the view provider (EA-69225 - assert: SingleRootFileViewProvider.setContent)
This commit is contained in:
+14
-2
@@ -29,6 +29,7 @@ import com.intellij.psi.impl.source.PsiFileImpl;
|
||||
import com.intellij.psi.impl.source.tree.FileElement;
|
||||
import com.intellij.psi.templateLanguages.OuterLanguageElement;
|
||||
import com.intellij.util.ConcurrencyUtil;
|
||||
import com.intellij.util.NullableFunction;
|
||||
import com.intellij.util.ReflectionUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -107,9 +108,20 @@ public abstract class MultiplePsiFilesPerDocumentFileViewProvider extends Single
|
||||
return myRoots.get(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PsiFile> getCachedPsiFiles() {
|
||||
return ContainerUtil.mapNotNull(myRoots.keySet(), new NullableFunction<Language, PsiFile>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiFile fun(Language language) {
|
||||
return getCachedPsi(language);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public FileElement[] getKnownTreeRoots() {
|
||||
public List<FileElement> getKnownTreeRoots() {
|
||||
List<FileElement> files = new ArrayList<FileElement>(myRoots.size());
|
||||
for (PsiFile file : myRoots.values()) {
|
||||
final FileElement treeElement = ((PsiFileImpl)file).getTreeElement();
|
||||
@@ -118,7 +130,7 @@ public abstract class MultiplePsiFilesPerDocumentFileViewProvider extends Single
|
||||
}
|
||||
}
|
||||
|
||||
return files.toArray(new FileElement[files.size()]);
|
||||
return files;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
|
||||
@@ -39,8 +39,7 @@ import com.intellij.openapi.vfs.NonPhysicalFileSystem;
|
||||
import com.intellij.openapi.vfs.PersistentFSConstants;
|
||||
import com.intellij.openapi.vfs.VFileProperty;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.impl.PsiManagerEx;
|
||||
import com.intellij.psi.impl.PsiManagerImpl;
|
||||
import com.intellij.psi.impl.*;
|
||||
import com.intellij.psi.impl.file.PsiBinaryFileImpl;
|
||||
import com.intellij.psi.impl.file.PsiLargeFileImpl;
|
||||
import com.intellij.psi.impl.file.impl.FileManager;
|
||||
@@ -198,17 +197,66 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi
|
||||
|
||||
@Override
|
||||
public void contentsSynchronized() {
|
||||
if (!(myContent instanceof PsiFileContent)) return;
|
||||
setContent(new VirtualFileContent());
|
||||
if (myContent instanceof PsiFileContent) {
|
||||
setContent(new VirtualFileContent());
|
||||
}
|
||||
checkLengthConsistency();
|
||||
}
|
||||
|
||||
public void beforeDocumentChanged(@Nullable PsiFile psiCause) {
|
||||
PsiFile psiFile = psiCause != null ? psiCause : getPsi(getBaseLanguage());
|
||||
if (psiFile instanceof PsiFileImpl) {
|
||||
setContent(new PsiFileContent((PsiFileImpl)psiFile, psiCause == null ? getModificationStamp() : LocalTimeCounter.currentTime()));
|
||||
checkLengthConsistency();
|
||||
}
|
||||
}
|
||||
|
||||
public final void onContentReload() {
|
||||
List<PsiFile> files = getCachedPsiFiles();
|
||||
List<PsiTreeChangeEventImpl> events = ContainerUtil.newArrayList();
|
||||
List<PsiTreeChangeEventImpl> genericEvents = ContainerUtil.newArrayList();
|
||||
for (PsiFile file : files) {
|
||||
genericEvents.add(createChildrenChangeEvent(file, true));
|
||||
events.add(createChildrenChangeEvent(file, false));
|
||||
}
|
||||
|
||||
beforeContentsSynchronized();
|
||||
|
||||
for (PsiTreeChangeEventImpl event : genericEvents) {
|
||||
((PsiManagerImpl)getManager()).beforeChildrenChange(event);
|
||||
}
|
||||
for (PsiTreeChangeEventImpl event : events) {
|
||||
((PsiManagerImpl)getManager()).beforeChildrenChange(event);
|
||||
}
|
||||
|
||||
for (PsiFile psiFile : files) {
|
||||
if (psiFile instanceof PsiFileEx) {
|
||||
((PsiFileEx)psiFile).onContentReload();
|
||||
}
|
||||
}
|
||||
|
||||
for (PsiTreeChangeEventImpl event : events) {
|
||||
((PsiManagerImpl)getManager()).childrenChanged(event);
|
||||
}
|
||||
for (PsiTreeChangeEventImpl event : genericEvents) {
|
||||
((PsiManagerImpl)getManager()).childrenChanged(event);
|
||||
}
|
||||
|
||||
contentsSynchronized();
|
||||
}
|
||||
|
||||
private PsiTreeChangeEventImpl createChildrenChangeEvent(PsiFile file, boolean generic) {
|
||||
PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
|
||||
event.setParent(file);
|
||||
event.setFile(file);
|
||||
event.setGenericChange(generic);
|
||||
if (file instanceof PsiFileImpl && ((PsiFileImpl)file).isContentsLoaded()) {
|
||||
event.setOffset(0);
|
||||
event.setOldLength(file.getTextLength());
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rootChanged(@NotNull PsiFile psiFile) {
|
||||
if (psiFile instanceof PsiFileImpl && ((PsiFileImpl)psiFile).isContentsLoaded()) {
|
||||
@@ -242,12 +290,16 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi
|
||||
return file == PsiUtilCore.NULL_PSI_FILE ? null : file;
|
||||
}
|
||||
|
||||
public List<PsiFile> getCachedPsiFiles() {
|
||||
return ContainerUtil.createMaybeSingletonList(getCachedPsi(myBaseLanguage));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public FileElement[] getKnownTreeRoots() {
|
||||
public List<FileElement> getKnownTreeRoots() {
|
||||
PsiFile psiFile = getCachedPsi(myBaseLanguage);
|
||||
if (!(psiFile instanceof PsiFileImpl)) return FileElement.EMPTY_ARRAY;
|
||||
if (((PsiFileImpl)psiFile).getTreeElement() == null) return FileElement.EMPTY_ARRAY;
|
||||
return new FileElement[]{(FileElement)psiFile.getNode()};
|
||||
if (!(psiFile instanceof PsiFileImpl)) return Collections.emptyList();
|
||||
FileElement element = ((PsiFileImpl)psiFile).getTreeElement();
|
||||
return ContainerUtil.createMaybeSingletonList(element);
|
||||
}
|
||||
|
||||
private PsiFile createFile() {
|
||||
@@ -485,18 +537,27 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi
|
||||
}
|
||||
|
||||
private void setContent(@NotNull Content content) {
|
||||
Content prevContent = myContent;
|
||||
myContent = content;
|
||||
if (prevContent instanceof PsiFileContent && content instanceof VirtualFileContent) {
|
||||
int fileLength = content.getText().length();
|
||||
for (FileElement fileElement : getKnownTreeRoots()) {
|
||||
int nodeLength = fileElement.getTextLength();
|
||||
if (nodeLength != fileLength) {
|
||||
LOG.error("Inconsistent " + fileElement.getElementType() + " tree in " + this + "; nodeLength=" + nodeLength + "; fileLength=" + fileLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLengthConsistency() {
|
||||
Document document = getCachedDocument();
|
||||
if (document != null &&
|
||||
((PsiDocumentManagerBase)PsiDocumentManager.getInstance(myManager.getProject())).getSynchronizer().isInSynchronization(document)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<FileElement> knownTreeRoots = getKnownTreeRoots();
|
||||
if (knownTreeRoots.isEmpty()) return;
|
||||
|
||||
int fileLength = myContent.getText().length();
|
||||
for (FileElement fileElement : knownTreeRoots) {
|
||||
int nodeLength = fileElement.getTextLength();
|
||||
if (nodeLength != fileLength) {
|
||||
// exceptions here should be assigned to peter
|
||||
LOG.error("Inconsistent " + fileElement.getElementType() + " tree in " + this + "; nodeLength=" + nodeLength + "; fileLength=" + fileLength);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NonNls
|
||||
|
||||
@@ -750,19 +750,12 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
|
||||
ApplicationManager.getApplication().runWriteAction(new ExternalChangeAction() {
|
||||
@Override
|
||||
public void run() {
|
||||
psiFile.getViewProvider().beforeContentsSynchronized();
|
||||
synchronized (PsiLock.LOCK) {
|
||||
final int oldLength = prevInfo.myFrozen.getTextLength();
|
||||
PsiManagerImpl manager = (PsiManagerImpl)psiFile.getManager();
|
||||
BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, true);
|
||||
BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, false);
|
||||
if (psiFile instanceof PsiFileImpl) {
|
||||
((PsiFileImpl)psiFile).onContentReload();
|
||||
}
|
||||
BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, false);
|
||||
BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, true);
|
||||
FileViewProvider viewProvider = psiFile.getViewProvider();
|
||||
if (viewProvider instanceof SingleRootFileViewProvider) {
|
||||
((SingleRootFileViewProvider)viewProvider).onContentReload();
|
||||
} else {
|
||||
LOG.error("Invalid view provider: " + viewProvider + " of " + viewProvider.getClass());
|
||||
}
|
||||
psiFile.getViewProvider().contentsSynchronized();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -599,20 +599,12 @@ public class FileManagerImpl implements FileManager {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
|
||||
event.setParent(file);
|
||||
event.setFile(file);
|
||||
if (file instanceof PsiFileImpl && ((PsiFileImpl)file).isContentsLoaded()) {
|
||||
event.setOffset(0);
|
||||
event.setOldLength(file.getTextLength());
|
||||
FileViewProvider viewProvider = file.getViewProvider();
|
||||
if (viewProvider instanceof SingleRootFileViewProvider) {
|
||||
((SingleRootFileViewProvider)viewProvider).onContentReload();
|
||||
} else {
|
||||
LOG.error("Invalid view provider: " + viewProvider + " of " + viewProvider.getClass());
|
||||
}
|
||||
myManager.beforeChildrenChange(event);
|
||||
|
||||
if (file instanceof PsiFileEx) {
|
||||
((PsiFileEx)file).onContentReload();
|
||||
}
|
||||
|
||||
myManager.childrenChanged(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,28 +349,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
return treeElement;
|
||||
}
|
||||
|
||||
public void unloadContent() {
|
||||
ApplicationManager.getApplication().assertWriteAccessAllowed();
|
||||
clearCaches();
|
||||
myViewProvider.beforeContentsSynchronized();
|
||||
synchronized (PsiLock.LOCK) {
|
||||
FileElement treeElement = derefTreeElement();
|
||||
DebugUtil.startPsiModification("unloadContent");
|
||||
try {
|
||||
if (treeElement != null) {
|
||||
myTreeElementPointer = null;
|
||||
treeElement.detachFromFile();
|
||||
DebugUtil.onInvalidated(treeElement);
|
||||
}
|
||||
clearStub("unloadContent");
|
||||
}
|
||||
finally {
|
||||
DebugUtil.finishPsiModification();
|
||||
}
|
||||
}
|
||||
myViewProvider.contentsSynchronized();
|
||||
}
|
||||
|
||||
private void clearStub(@NotNull String reason) {
|
||||
StubTree stubHolder = SoftReference.dereference(myStub);
|
||||
if (stubHolder != null) {
|
||||
@@ -633,10 +611,22 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
|
||||
@Override
|
||||
public void onContentReload() {
|
||||
subtreeChanged(); // important! otherwise cached information is not released
|
||||
if (isContentsLoaded()) {
|
||||
unloadContent();
|
||||
ApplicationManager.getApplication().assertWriteAccessAllowed();
|
||||
|
||||
FileElement treeElement = derefTreeElement();
|
||||
DebugUtil.startPsiModification("onContentReload");
|
||||
try {
|
||||
if (treeElement != null) {
|
||||
myTreeElementPointer = null;
|
||||
treeElement.detachFromFile();
|
||||
DebugUtil.onInvalidated(treeElement);
|
||||
}
|
||||
clearStub("onContentReload");
|
||||
}
|
||||
finally {
|
||||
DebugUtil.finishPsiModification();
|
||||
}
|
||||
clearCaches();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -21,15 +21,13 @@ import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.DumbServiceImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.PsiFileImpl;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
import com.intellij.psi.stubs.StubUpdatingIndex;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import com.intellij.util.indexing.FileBasedIndex;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
@@ -215,20 +213,15 @@ public class PyStubsTest extends PyTestCase {
|
||||
StubElement fileStub = fileImpl.getStub();
|
||||
assertNull("There should be no stub if file holds tree element", fileStub);
|
||||
|
||||
FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, myFixture.getProject(), null);
|
||||
new WriteCommandAction(myFixture.getProject(), fileImpl) {
|
||||
@Override
|
||||
protected void run(@NotNull Result result) throws Throwable {
|
||||
fileImpl.unloadContent();
|
||||
((SingleRootFileViewProvider)fileImpl.getViewProvider()).onContentReload();
|
||||
}
|
||||
}.execute();
|
||||
assertNull(fileImpl.getTreeElement()); // Test unload successed.
|
||||
assertNull(fileImpl.getTreeElement()); // Test unload succeeded.
|
||||
|
||||
fileStub = fileImpl.getStub();
|
||||
assertNotNull("After tree element have been unloaded we must be able to create updated stub", fileStub);
|
||||
|
||||
final PyClassStub newclassstub = (PyClassStub)fileStub.getChildrenStubs().get(0);
|
||||
assertEquals("RenamedClass", newclassstub.getName());
|
||||
assertEquals("RenamedClass", fileImpl.getTopLevelClasses().get(0).getName());
|
||||
}
|
||||
|
||||
public void testImportStatement() {
|
||||
|
||||
Reference in New Issue
Block a user