From c887211a58992a3a15b2014e7330cddfadb3538f Mon Sep 17 00:00:00 2001 From: Peter Gromov Date: Wed, 15 Jul 2020 08:52:57 +0200 Subject: [PATCH] simplify PsiToDocumentSynchronizer: process TreeChangeEvent directly instead of PSI events towards uniform PSI-doc synchronization for physical and non-physical PSI, which is needed for rename to work in ModelBranch (LAB-51) GitOrigin-RevId: eddfe0de35137fcc37a4a6854616d4194b10d772 --- .../pom/tree/events/impl/ChangeInfoImpl.java | 12 ++ .../tree/events/impl/TreeChangeEventImpl.java | 9 +- .../pom/wrappers/PsiEventWrapperAspect.java | 9 +- .../src/com/intellij/psi/impl/DiffLog.java | 5 +- .../psi/impl/PsiDocumentManagerBase.java | 1 - .../psi/impl/PsiToDocumentSynchronizer.java | 109 +++++++----------- 6 files changed, 65 insertions(+), 80 deletions(-) diff --git a/platform/core-impl/src/com/intellij/pom/tree/events/impl/ChangeInfoImpl.java b/platform/core-impl/src/com/intellij/pom/tree/events/impl/ChangeInfoImpl.java index e2faa11a53b6..09f6e0cfedd8 100644 --- a/platform/core-impl/src/com/intellij/pom/tree/events/impl/ChangeInfoImpl.java +++ b/platform/core-impl/src/com/intellij/pom/tree/events/impl/ChangeInfoImpl.java @@ -45,6 +45,18 @@ public class ChangeInfoImpl implements ChangeInfo { return myOldChild; } + public int getOffsetInParent() { + return myOffset; + } + + public int getOldLength() { + return myOldLength; + } + + public int getNewLength() { + return myNewLength; + } + @Nullable public TreeElement getNewChild() { return myNewChild; diff --git a/platform/core-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java b/platform/core-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java index 54e12ae7240d..0434a9d7df43 100644 --- a/platform/core-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java +++ b/platform/core-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java @@ -14,11 +14,13 @@ import com.intellij.psi.impl.source.tree.TreeElement; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.JBIterable; import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +@ApiStatus.Internal public class TreeChangeEventImpl implements TreeChangeEvent{ private final Map myChangedElements = new LinkedHashMap<>(); private final MultiMap myChangesByAllParents = MultiMap.createSet(); @@ -127,12 +129,17 @@ public class TreeChangeEventImpl implements TreeChangeEvent{ } public void fireEvents() { - Collection changes = ContainerUtil.sorted(myChangedElements.values()); + Collection changes = getSortedChanges(); for (TreeChangeImpl change : changes) { change.fireEvents((PsiFile)myFileElement.getPsi()); } } + @NotNull + public List getSortedChanges() { + return ContainerUtil.sorted(myChangedElements.values()); + } + @Override public void beforeNestedTransaction() { // compute changes and remember them, to prevent lazy computation to happen in another transaction diff --git a/platform/core-impl/src/com/intellij/pom/wrappers/PsiEventWrapperAspect.java b/platform/core-impl/src/com/intellij/pom/wrappers/PsiEventWrapperAspect.java index 7d7de6fb1baa..59e9aa83e87e 100644 --- a/platform/core-impl/src/com/intellij/pom/wrappers/PsiEventWrapperAspect.java +++ b/platform/core-impl/src/com/intellij/pom/wrappers/PsiEventWrapperAspect.java @@ -9,7 +9,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.PomModelAspect; import com.intellij.pom.event.PomModelEvent; import com.intellij.pom.tree.TreeAspect; -import com.intellij.pom.tree.events.TreeChangeEvent; import com.intellij.pom.tree.events.impl.TreeChangeEventImpl; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; @@ -29,12 +28,9 @@ public final class PsiEventWrapperAspect implements PomModelAspect { @Override public void update(PomModelEvent event) { - final TreeChangeEvent changeSet = (TreeChangeEvent)event.getChangeSet(myTreeAspect); + TreeChangeEventImpl changeSet = (TreeChangeEventImpl)event.getChangeSet(myTreeAspect); if(changeSet == null) return; - sendAfterEvents(changeSet); - } - private static void sendAfterEvents(TreeChangeEvent changeSet) { ASTNode rootElement = changeSet.getRootElement(); PsiFile file = (PsiFile)rootElement.getPsi(); if (!file.isPhysical()) { @@ -43,7 +39,8 @@ public final class PsiEventWrapperAspect implements PomModelAspect { return; } - ((TreeChangeEventImpl)changeSet).fireEvents(); + ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(file.getProject())).getSynchronizer().processEvents(changeSet, file); + changeSet.fireEvents(); } private static void promoteNonPhysicalChangesToDocument(ASTNode rootElement, PsiFile file) { diff --git a/platform/core-impl/src/com/intellij/psi/impl/DiffLog.java b/platform/core-impl/src/com/intellij/psi/impl/DiffLog.java index 8ed09d880fe6..27634316cfec 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/DiffLog.java +++ b/platform/core-impl/src/com/intellij/psi/impl/DiffLog.java @@ -228,15 +228,12 @@ public class DiffLog implements DiffTreeChangeBuilder { @Override void doActualPsiChange(@NotNull PsiFile file, @NotNull TreeChangeEventImpl event) { PsiFileImpl fileImpl = (PsiFileImpl)file; - final int oldLength = myOldNode.getTextLength(); - PsiManagerImpl manager = (PsiManagerImpl)fileImpl.getManager(); - BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, fileImpl, false); + event.addElementaryChange(myOldNode); if (myOldNode.getFirstChildNode() != null) myOldNode.rawRemoveAllChildren(); final TreeElement firstChildNode = myNewNode.getFirstChildNode(); if (firstChildNode != null) myOldNode.rawAddChildren(firstChildNode); fileImpl.calcTreeElement().setCharTable(myNewNode.getCharTable()); myOldNode.subtreeChanged(); - BlockSupportImpl.sendAfterChildrenChangedEvent(manager,fileImpl, oldLength, false); } } 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 c569b49259fc..08e83e1fee04 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java @@ -74,7 +74,6 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen myPsiManager = PsiManager.getInstance(project); myDocumentCommitProcessor = ApplicationManager.getApplication().getService(DocumentCommitProcessor.class); mySynchronizer = new PsiToDocumentSynchronizer(this, project.getMessageBus()); - myPsiManager.addPsiTreeChangeListener(mySynchronizer, this); } @Override diff --git a/platform/core-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java b/platform/core-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java index 749c5cb5152b..38b5f2568f76 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java @@ -3,6 +3,7 @@ package com.intellij.psi.impl; import com.intellij.injected.editor.DocumentWindow; +import com.intellij.lang.ASTNode; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; @@ -12,14 +13,23 @@ import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.*; +import com.intellij.pom.tree.events.impl.ChangeInfoImpl; +import com.intellij.pom.tree.events.impl.TreeChangeEventImpl; +import com.intellij.pom.tree.events.impl.TreeChangeImpl; +import com.intellij.psi.IgnorePsiEventsMarker; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiTreeChangeEvent; +import com.intellij.psi.impl.source.DummyHolder; import com.intellij.psi.impl.source.tree.ForeignLeafPsiElement; +import com.intellij.psi.impl.source.tree.TreeElement; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.ExceptionUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; import com.intellij.util.text.CharArrayUtil; import com.intellij.util.text.ImmutableCharSequence; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -29,7 +39,7 @@ import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; -public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { +public class PsiToDocumentSynchronizer { private static final Logger LOG = Logger.getInstance(PsiToDocumentSynchronizer.class); private static final Key PSI_DOCUMENT_ATOMIC_ACTION = Key.create("PSI_DOCUMENT_ATOMIC_ACTION"); @@ -83,12 +93,12 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { return document; } - private void doSync(final @NotNull PsiTreeChangeEvent event, boolean force, final @NotNull DocSyncAction syncAction) { + private void doSync(@NotNull PsiTreeChangeEvent event, @NotNull DocSyncAction syncAction) { if (!toProcessPsiEvent()) return; final PsiFile psiFile = event.getFile(); if (!(psiFile instanceof PsiFileEx) || !((PsiFileEx)psiFile).isContentsLoaded()) return; - final DocumentEx document = getCachedDocument(psiFile, force); + DocumentEx document = getCachedDocument(psiFile, true); if (document == null) return; performAtomically(psiFile, () -> syncAction.syncDocument(document, (PsiTreeChangeEventImpl)event)); @@ -116,56 +126,6 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { } } - @Override - public void beforeChildAddition(@NotNull PsiTreeChangeEvent event) { - checkPsiModificationAllowed(event); - } - - @Override - public void beforeChildRemoval(@NotNull PsiTreeChangeEvent event) { - checkPsiModificationAllowed(event); - } - - @Override - public void beforeChildReplacement(@NotNull PsiTreeChangeEvent event) { - checkPsiModificationAllowed(event); - } - - @Override - public void beforeChildrenChange(@NotNull PsiTreeChangeEvent event) { - checkPsiModificationAllowed(event); - } - - @Override - public void childAdded(final @NotNull PsiTreeChangeEvent event) { - if (!(event.getChild() instanceof ForeignLeafPsiElement)) { - doSync(event, false, (document, event1) -> insertString(document, event1.getOffset(), event1.getChild().getText())); - } - } - - @Override - public void childRemoved(final @NotNull PsiTreeChangeEvent event) { - if (!(event.getChild() instanceof ForeignLeafPsiElement)) { - doSync(event, false, (document, event1) -> deleteString(document, event1.getOffset(), event1.getOffset() + event1.getOldLength())); - } - } - - @Override - public void childReplaced(final @NotNull PsiTreeChangeEvent event) { - doSync(event, false, (document, event1) -> { - int oldLength = event1.getOldChild() instanceof ForeignLeafPsiElement ? 0 : event1.getOldLength(); - String newText = event1.getNewChild() instanceof ForeignLeafPsiElement ? "" : event1.getNewChild().getText(); - replaceString(document, event1.getOffset(), event1.getOffset() + oldLength, newText, event1.getNewChild()); - }); - } - - @Override - public void childrenChanged(final @NotNull PsiTreeChangeEvent event) { - doSync(event, false, (document, event1) -> - replaceString(document, event1.getOffset(), event1.getOffset() + event1.getOldLength(), event1.getParent().getText(), event1 - .getParent())); - } - private boolean myIgnorePsiEvents; public void setIgnorePsiEvents(boolean ignorePsiEvents) { myIgnorePsiEvents = ignorePsiEvents; @@ -181,16 +141,13 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { @TestOnly public void replaceString(@NotNull Document document, int startOffset, int endOffset, @NotNull String s) { - replaceString(document, startOffset, endOffset, s, null); - } - - private void replaceString(@NotNull Document document, int startOffset, int endOffset, @NotNull String s, @Nullable PsiElement replacement) { final DocumentChangeTransaction documentChangeTransaction = getTransaction(document); if(documentChangeTransaction != null) { - documentChangeTransaction.replace(startOffset, endOffset - startOffset, s, replacement); + documentChangeTransaction.replace(startOffset, endOffset - startOffset, s, null); } } + @TestOnly public void insertString(@NotNull Document document, int offset, @NotNull String s) { final DocumentChangeTransaction documentChangeTransaction = getTransaction(document); if(documentChangeTransaction != null){ @@ -198,13 +155,6 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { } } - private void deleteString(@NotNull Document document, int startOffset, int endOffset){ - final DocumentChangeTransaction documentChangeTransaction = getTransaction(document); - if(documentChangeTransaction != null){ - documentChangeTransaction.replace(startOffset, endOffset - startOffset, "", null); - } - } - public void startTransaction(@NotNull Project project, @NotNull Document doc, @NotNull PsiElement scope) { LOG.assertTrue(!project.isDisposed()); Pair pair = myTransactionsMap.get(doc); @@ -232,11 +182,12 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { fakeEvent.setParent(changeScope); fakeEvent.setFile(changeScope); checkPsiModificationAllowed(fakeEvent); - doSync(fakeEvent, true, (document1, event) -> doCommitTransaction(document1, documentChangeTransaction)); + doSync(fakeEvent, (document1, event) -> doCommitTransaction(document1, documentChangeTransaction)); myBus.syncPublisher(PsiDocumentTransactionListener.TOPIC).transactionCompleted(document, changeScope); } catch (Throwable e) { myPsiDocumentManager.forceReload(changeScope.getViewProvider().getVirtualFile(), changeScope.getViewProvider()); + //noinspection ConstantConditions ExceptionUtil.rethrowAllAsUnchecked(e); } finally { @@ -280,6 +231,27 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { return myTransactionsMap.containsKey(document); } + @ApiStatus.Internal + public void processEvents(@NotNull TreeChangeEventImpl changeSet, @NotNull PsiFile file) { + if (file instanceof DummyHolder || !toProcessPsiEvent()) return; + + Document document = getCachedDocument(file, false); + DocumentChangeTransaction transaction = document == null ? null : getTransaction(document); + if (transaction == null) return; + + for (TreeChangeImpl change : changeSet.getSortedChanges()) { + int parentStart = change.getChangedParent().getStartOffset(); + for (ASTNode child : change.getAffectedChildren()) { + ChangeInfoImpl info = change.getChangeByChild(child); + TreeElement newChild = info.getNewChild(); + PsiElement newPsi = newChild == null ? null : newChild.getPsi(); + if (!(newPsi instanceof ForeignLeafPsiElement)) { + transaction.replace(info.getOffsetInParent() + parentStart, info.getOldLength(), newChild == null ? "" : newChild.getText(), newPsi); + } + } + } + } + public static class DocumentChangeTransaction{ private final TreeMap myAffectedFragments = new TreeMap<>(Comparator.comparingInt(TextRange::getStartOffset)); private final PsiFile myChangeScope; @@ -290,11 +262,12 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter { myPsiText = CharArrayUtil.createImmutableCharSequence(doc.getImmutableCharSequence()); } + @TestOnly public @NotNull Map getAffectedFragments() { return myAffectedFragments; } - public void replace(int psiStart, int length, @NotNull String replace, @Nullable PsiElement replacement) { + void replace(int psiStart, int length, @NotNull String replace, @Nullable PsiElement replacement) { // calculating fragment // minimize replace int start = 0;