diff --git a/java/debugger/impl/src/com/intellij/debugger/jdi/StackFrameProxyImpl.java b/java/debugger/impl/src/com/intellij/debugger/jdi/StackFrameProxyImpl.java index 1ed0ec8628f4..b0c6ee3008e8 100644 --- a/java/debugger/impl/src/com/intellij/debugger/jdi/StackFrameProxyImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/jdi/StackFrameProxyImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. */ /* @@ -306,6 +306,10 @@ public class StackFrameProxyImpl extends JdiProxy implements StackFrameProxy { error = e; clearCaches(); } + catch (InconsistentDebugInfoException ignored) { + clearCaches(); + throw EvaluateExceptionUtil.INCONSISTEND_DEBUG_INFO; + } catch (InternalException e) { if (e.errorCode() == JvmtiError.INVALID_SLOT || e.errorCode() == JvmtiError.ABSENT_INFORMATION) { throw new EvaluateException(DebuggerBundle.message("error.corrupt.debug.info", e.getMessage()), e); @@ -352,10 +356,6 @@ public class StackFrameProxyImpl extends JdiProxy implements StackFrameProxy { StackFrame stackFrame = getStackFrame(); myAllValues = new THashMap<>(stackFrame.getValues(stackFrame.visibleVariables())); } - catch (InconsistentDebugInfoException ignored) { - clearCaches(); - throw EvaluateExceptionUtil.INCONSISTEND_DEBUG_INFO; - } catch (AbsentInformationException e) { throw EvaluateExceptionUtil.createEvaluateException(e); } diff --git a/java/testFramework/src/com/intellij/codeInsight/completion/CompletionTestCase.java b/java/testFramework/src/com/intellij/codeInsight/completion/CompletionTestCase.java index a100551c257b..20cd16175246 100644 --- a/java/testFramework/src/com/intellij/codeInsight/completion/CompletionTestCase.java +++ b/java/testFramework/src/com/intellij/codeInsight/completion/CompletionTestCase.java @@ -19,6 +19,7 @@ import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.testFramework.PlatformTestCase; import com.intellij.util.containers.ContainerUtil; @@ -63,6 +64,7 @@ public abstract class CompletionTestCase extends DaemonAnalyzerTestCase { } protected void complete(final int time) { + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); new CodeCompletionHandlerBase(myType).invokeCompletion(myProject, InjectedLanguageUtil .getEditorForInjectedLanguageNoCommit(myEditor, getFile()), time); diff --git a/platform/core-impl/src/com/intellij/psi/impl/BooleanRunnable.java b/platform/core-impl/src/com/intellij/psi/impl/BooleanRunnable.java new file mode 100644 index 000000000000..f7da90fc3f9f --- /dev/null +++ b/platform/core-impl/src/com/intellij/psi/impl/BooleanRunnable.java @@ -0,0 +1,9 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + */ +package com.intellij.psi.impl; + +@FunctionalInterface +public interface BooleanRunnable { + boolean run(); +} diff --git a/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitThread.java b/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitThread.java index d52d8d7032ee..299950966bac 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitThread.java +++ b/platform/core-impl/src/com/intellij/psi/impl/DocumentCommitThread.java @@ -30,13 +30,13 @@ import com.intellij.pom.tree.TreeAspectEvent; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.text.BlockSupportImpl; import com.intellij.psi.impl.source.text.DiffLog; import com.intellij.psi.impl.source.tree.FileElement; import com.intellij.psi.impl.source.tree.ForeignLeafPsiElement; import com.intellij.psi.impl.source.tree.TreeUtil; import com.intellij.psi.text.BlockSupport; import com.intellij.util.ExceptionUtil; -import com.intellij.util.Processor; import com.intellij.util.SmartList; import com.intellij.util.concurrency.BoundedTaskExecutor; import com.intellij.util.containers.ContainerUtil; @@ -432,9 +432,9 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi // synchronized to ensure no new similar tasks can start before we hold the document's lock task = createNewTaskAndCancelSimilar(project, document, allFileNodes, SYNC_COMMIT_REASON, TransactionGuard.getInstance().getContextTransaction(), PsiDocumentManager.getInstance(project).getLastCommittedText(document)); - documentLock.lock(); } + documentLock.lock(); try { assert !task.indicator.isCanceled(); Pair result = commitUnderProgress(task, true); @@ -488,7 +488,8 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi final Document document = task.getDocument(); final Project project = task.project; final PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(project); - final List> finishProcessors = new SmartList<>(); + final List finishProcessors = new SmartList<>(); + List reparseInjectedProcessors = new SmartList<>(); Ref changedRange = new Ref<>(); Runnable runnable = () -> { myApplication.assertReadAccessAllowed(); @@ -519,9 +520,11 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi PsiFileImpl file = pair.first; if (file.isValid()) { FileASTNode oldFileNode = pair.second; - Processor finishProcessor = doCommit(task, file, oldFileNode, changedRange); - if (finishProcessor != null) { - finishProcessors.add(finishProcessor); + ProperTextRange changedPsiRange = getChangedPsiRange(file, task, document.getImmutableCharSequence()); + if (changedPsiRange != null) { + BooleanRunnable finishProcessor = doCommit(task, file, oldFileNode, changedPsiRange, reparseInjectedProcessors); + ContainerUtil.addIfNotNull(finishProcessors, finishProcessor); + changedRange.set(changedRange.get() == null ? changedPsiRange : changedRange.get().union(changedPsiRange)); } } else { @@ -541,10 +544,7 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi } } }; - if (synchronously) { - runnable.run(); - } - else if (!myApplication.tryRunReadAction(runnable)) { + if (!myApplication.tryRunReadAction(runnable)) { log(project, "Could not start read action", task, myApplication.isReadAccessAllowed(), Thread.currentThread()); return new Pair<>(null, "Could not start read action"); } @@ -556,15 +556,16 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi } ProperTextRange range = changedRange.isNull() ? ProperTextRange.create(0, document.getTextLength()) : changedRange.get(); - Runnable result = createEdtRunnable(task, synchronously, finishProcessors, range); + Runnable result = createFinishCommitInEDTRunnable(task, synchronously, finishProcessors, reparseInjectedProcessors, range); return Pair.create(result, null); } @NotNull - private Runnable createEdtRunnable(@NotNull final CommitTask task, - final boolean synchronously, - @NotNull final List> finishProcessors, - @NotNull ProperTextRange changedRange) { + private Runnable createFinishCommitInEDTRunnable(@NotNull final CommitTask task, + final boolean synchronously, + @NotNull List finishProcessors, + @NotNull List reparseInjectedProcessors, + @NotNull ProperTextRange changedRange) { return () -> { myApplication.assertIsDispatchThread(); Document document = task.getDocument(); @@ -580,7 +581,7 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi } boolean changeStillValid = task.isStillValid(); - boolean success = changeStillValid && documentManager.finishCommit(document, finishProcessors, changedRange, synchronously, task.reason); + boolean success = changeStillValid && documentManager.finishCommit(document, finishProcessors, reparseInjectedProcessors, changedRange, synchronously, task.reason); if (synchronously) { assert success; } @@ -602,10 +603,11 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi } @NotNull - private Processor handleCommitWithoutPsi(@NotNull final PsiDocumentManagerBase documentManager, + private BooleanRunnable handleCommitWithoutPsi(@NotNull final PsiDocumentManagerBase documentManager, @NotNull final CommitTask task) { - return document -> { + return () -> { log(task.project, "Finishing without PSI", task); + Document document = task.getDocument(); if (!task.isStillValid() || documentManager.getCachedViewProvider(document) != null) { return false; } @@ -721,21 +723,15 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi } } - // public for Upsource // returns runnable to execute under write action in AWT to finish the commit, updates "outChangedRange" @Nullable - public Processor doCommit(@NotNull final CommitTask task, - @NotNull final PsiFile file, - @NotNull final FileASTNode oldFileNode, - @NotNull Ref outChangedRange) { + public BooleanRunnable doCommit(@NotNull final CommitTask task, + @NotNull final PsiFile file, + @NotNull final FileASTNode oldFileNode, + @NotNull ProperTextRange changedPsiRange, + @NotNull List outReparseInjectedProcessors) { Document document = task.getDocument(); final CharSequence newDocumentText = document.getImmutableCharSequence(); - ProperTextRange changedPsiRange = getChangedPsiRange(file, task, newDocumentText); - if (changedPsiRange == null) { - return null; - } - - outChangedRange.set(outChangedRange.get() == null ? changedPsiRange : outChangedRange.get().union(changedPsiRange)); final Boolean data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY); if (data != null) { @@ -743,11 +739,22 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, data); } - BlockSupport blockSupport = BlockSupport.getInstance(file.getProject()); - final DiffLog diffLog = blockSupport.reparseRange(file, oldFileNode, changedPsiRange, newDocumentText, task.indicator, task.myLastCommittedText); + BlockSupportImpl blockSupport = (BlockSupportImpl)BlockSupport.getInstance(file.getProject()); + Trinity result = + BlockSupportImpl.reparse(file, oldFileNode, changedPsiRange, newDocumentText, task.indicator, task.myLastCommittedText); + DiffLog diffLog = result.getFirst(); + ASTNode oldRoot = result.getSecond(); + ASTNode newRoot = result.getThird(); - return document1 -> { + PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(task.project); + + List injectedRunnables = + documentManager.reparseChangedInjectedFragments(document, file, changedPsiRange, task.indicator, oldRoot, newRoot); + outReparseInjectedProcessors.addAll(injectedRunnables); + + return () -> { FileViewProvider viewProvider = file.getViewProvider(); + Document document1 = task.getDocument(); if (!task.isStillValid() || ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(file.getProject())).getCachedViewProvider(document1) != viewProvider) { return false; // optimistic locking failed @@ -864,7 +871,7 @@ public class DocumentCommitThread implements Runnable, Disposable, DocumentCommi return ProperTextRange.create(prefix, end); } - public static void doActualPsiChange(@NotNull final PsiFile file, @NotNull final DiffLog diffLog) { + public static void doActualPsiChange(@NotNull PsiFile file, @NotNull DiffLog diffLog) { CodeStyleManager.getInstance(file.getProject()).performActionWithFormatterDisabled((Runnable)() -> { FileViewProvider viewProvider = file.getViewProvider(); synchronized (((AbstractFileViewProvider)viewProvider).getFilePsiLock()) { 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 e7ebc118cae9..f45b905e6c27 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java @@ -6,6 +6,7 @@ package com.intellij.psi.impl; import com.google.common.annotations.VisibleForTesting; import com.intellij.injected.editor.DocumentWindow; +import com.intellij.lang.ASTNode; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.*; @@ -316,7 +317,8 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen // public for Upsource public boolean finishCommit(@NotNull final Document document, - @NotNull final List> finishProcessors, + @NotNull List finishProcessors, + @NotNull List reparseInjectedProcessors, @NotNull ProperTextRange changedRange, final boolean synchronously, @NotNull final Object reason) { @@ -326,7 +328,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen Runnable runnable = new DocumentRunnable(document, myProject) { @Override public void run() { - ok[0] = finishCommitInWriteAction(document, finishProcessors, synchronously); + ok[0] = finishCommitInWriteAction(document, finishProcessors, reparseInjectedProcessors, synchronously, false); } }; if (synchronously) { @@ -339,7 +341,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen if (ok[0]) { // otherwise changes maybe not synced to the document yet, and injectors will crash if (!mySynchronizer.isDocumentAffectedByTransactions(document)) { - InjectedLanguageManager.getInstance(myProject).startRunInjectorsInRange(document, changedRange, synchronously); + //InjectedLanguageManager.getInstance(myProject).startRunInjectorsInRange(document, changedRange, synchronously); } // run after commit actions outside write action runAfterCommitActions(document); @@ -351,13 +353,8 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen } protected boolean finishCommitInWriteAction(@NotNull final Document document, - @NotNull final List> finishProcessors, - final boolean synchronously) { - return finishCommitInWriteAction(document, finishProcessors, synchronously, false); - } - - protected boolean finishCommitInWriteAction(@NotNull final Document document, - @NotNull final List> finishProcessors, + @NotNull List finishProcessors, + @NotNull List reparseInjectedProcessors, final boolean synchronously, boolean forceNoPsiCommit) { ApplicationManager.getApplication().assertIsDispatchThread(); @@ -374,11 +371,11 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen myIsCommitInProgress = true; boolean success = true; try { - if (viewProvider != null) { - success = commitToExistingPsi(document, finishProcessors, synchronously, virtualFile, viewProvider); + if (viewProvider == null) { + handleCommitWithoutPsi(document); } else { - handleCommitWithoutPsi(document); + success = commitToExistingPsi(document, finishProcessors, reparseInjectedProcessors, synchronously, virtualFile, viewProvider); } } catch (Throwable e) { @@ -396,10 +393,13 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen } private boolean commitToExistingPsi(@NotNull Document document, - @NotNull List> finishProcessors, - boolean synchronously, @Nullable VirtualFile virtualFile, @NotNull FileViewProvider viewProvider) { - for (Processor finishRunnable : finishProcessors) { - boolean success = finishRunnable.process(document); + @NotNull List finishProcessors, + @NotNull List reparseInjectedProcessors, + boolean synchronously, + @Nullable VirtualFile virtualFile, + @NotNull FileViewProvider viewProvider) { + for (BooleanRunnable finishRunnable : finishProcessors) { + boolean success = finishRunnable.run(); if (synchronously) { assert success : finishRunnable + " in " + finishProcessors; } @@ -412,6 +412,9 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen getSmartPointerManager().updatePointerTargetsAfterReparse(virtualFile); } viewProvider.contentsSynchronized(); + for (BooleanRunnable runnable : reparseInjectedProcessors) { + if (!runnable.run()) return false; + } return true; } @@ -886,12 +889,8 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen } // we can end up outside write action here if the document has forUseInNonAWTThread=true - ApplicationManager.getApplication().runWriteAction(new ExternalChangeAction() { - @Override - public void run() { - ((AbstractFileViewProvider)psiFile.getViewProvider()).onContentReload(); - } - }); + ApplicationManager.getApplication().runWriteAction( + (ExternalChangeAction)((AbstractFileViewProvider)psiFile.getViewProvider())::onContentReload); } @Nullable @@ -990,12 +989,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen public void disableBackgroundCommit(@NotNull Disposable parentDisposable) { assert myPerformBackgroundCommit; myPerformBackgroundCommit = false; - Disposer.register(parentDisposable, new Disposable() { - @Override - public void dispose() { - myPerformBackgroundCommit = true; - } - }); + Disposer.register(parentDisposable, () -> myPerformBackgroundCommit = true); } @Override @@ -1015,7 +1009,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen } @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod") - public void reparseFileFromText(PsiFileImpl file) { + public void reparseFileFromText(@NotNull PsiFileImpl file) { ApplicationManager.getApplication().assertIsDispatchThread(); if (isCommitInProgress()) throw new IllegalStateException("Re-entrant commit is not allowed"); @@ -1026,7 +1020,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen WriteAction.run(() -> { ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator(); if (indicator == null) indicator = new EmptyProgressIndicator(); - DiffLog log = BlockSupportImpl.makeFullParse(file, node, text, indicator, text); + DiffLog log = BlockSupportImpl.makeFullParse(file, node, text, indicator, text).getFirst(); DocumentCommitThread.doActualPsiChange(file, log); file.getViewProvider().contentsSynchronized(); }); @@ -1042,7 +1036,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen private final List myEvents = ContainerUtil.newArrayList(); private final ConcurrentMap myFrozenWindows = ContainerUtil.newConcurrentMap(); - private UncommittedInfo(DocumentImpl original) { + private UncommittedInfo(@NotNull DocumentImpl original) { myOriginal = original; myFrozen = original.freeze(); myOriginal.addDocumentListener(this); @@ -1068,4 +1062,13 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen } } + @NotNull + List reparseChangedInjectedFragments(@NotNull Document hostDocument, + @NotNull PsiFile hostPsiFile, + @NotNull TextRange range, + @NotNull ProgressIndicator indicator, + @NotNull ASTNode oldRoot, + @NotNull ASTNode newRoot) { + return Collections.emptyList(); + } } diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/Identikit.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/Identikit.java index fe28a9970c76..dd48c77559f8 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/Identikit.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/Identikit.java @@ -19,6 +19,7 @@ import com.google.common.base.MoreObjects; import com.intellij.lang.Language; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; +import com.intellij.psi.AbstractFileViewProvider; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IElementType; @@ -83,9 +84,14 @@ public abstract class Identikit { @Override public PsiElement findPsiElement(@NotNull PsiFile file, int startOffset, int endOffset) { Language actualLanguage = myFileLanguage != Language.ANY ? myFileLanguage : file.getViewProvider().getBaseLanguage(); - PsiElement anchor = file.getViewProvider().findElementAt(startOffset, actualLanguage); - if (anchor == null && startOffset == file.getTextLength()) { - PsiElement lastChild = file.getViewProvider().getPsi(actualLanguage).getLastChild(); + PsiFile actualLanguagePsi = file.getViewProvider().getPsi(actualLanguage); + return findInside(actualLanguagePsi, startOffset, endOffset); + } + + public PsiElement findInside(@NotNull PsiElement element, int startOffset, int endOffset) { + PsiElement anchor = AbstractFileViewProvider.findElementAt(element, startOffset); // finds child in this tree only, unlike PsiElement.findElementAt() + if (anchor == null && startOffset == element.getTextLength()) { + PsiElement lastChild = element.getLastChild(); if (lastChild != null) { anchor = PsiTreeUtil.getDeepestLast(lastChild); } diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java index 5d7997076a2a..e18d8db3bdc1 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java @@ -15,19 +15,25 @@ */ package com.intellij.psi.impl.smartPointers; +import com.intellij.lang.Language; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.impl.FrozenDocument; import com.intellij.openapi.editor.impl.ManualRangeMarker; import com.intellij.openapi.editor.impl.event.DocumentEventImpl; import com.intellij.openapi.editor.impl.event.RetargetRangeMarkers; +import com.intellij.openapi.util.ProperTextRange; +import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.UnfairTextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; /** * @author peter @@ -158,6 +164,47 @@ class MarkerCache { return updated == null ? null : new UnfairTextRange(updated.getStartOffset(), updated.getEndOffset()); } + @Nullable + static Segment getUpdatedRange(@NotNull PsiFile containingFile, + @NotNull Segment segment, + boolean isSegmentGreedy, + @NotNull FrozenDocument frozen, + @NotNull List events) { + SelfElementInfo info = new SelfElementInfo(containingFile.getProject(), ProperTextRange.create(segment), new Identikit() { + @Nullable + @Override + public PsiElement findPsiElement(@NotNull PsiFile file, int startOffset, int endOffset) { + return null; + } + + @NotNull + @Override + public Language getFileLanguage() { + return null; + } + + @Override + public boolean isForPsiFile() { + return false; + } + }, containingFile, isSegmentGreedy); + List infos = Collections.singletonList(info); + + boolean greedy = info.isGreedy(); + int start = info.getPsiStartOffset(); + int end = info.getPsiEndOffset(); + boolean surviveOnExternalChange = events.stream().anyMatch(event->((DocumentEventImpl)event).getInitialStartOffset() == 0 && ((DocumentEventImpl)event).getInitialOldLength() == frozen.getTextLength()); + ManualRangeMarker marker = new ManualRangeMarker(start, end, greedy, greedy, surviveOnExternalChange, null); + + UpdatedRanges ranges = new UpdatedRanges(0, frozen, infos, new ManualRangeMarker[]{marker}); + // NB: convert events from completion to whole doc change event to more precise translation + List newEvents = + events.stream().map(event -> ((DocumentEventImpl)event).getInitialStartOffset() == 0 && ((DocumentEventImpl)event).getInitialOldLength() == frozen.getTextLength() ? new DocumentEventImpl(event.getDocument(), event.getOffset(), event.getOldFragment(), event.getNewFragment(), event.getOldTimeStamp(), true, ((DocumentEventImpl)event).getInitialStartOffset(), ((DocumentEventImpl)event).getInitialOldLength()) : event) + .collect(Collectors.toList()); + UpdatedRanges updated = applyEvents(newEvents, ranges); + return updated.myMarkers[0]; + } + void rangeChanged() { myUpdatedRanges = null; } @@ -168,9 +215,10 @@ class MarkerCache { private final List mySortedInfos; private final ManualRangeMarker[] myMarkers; - public UpdatedRanges(int eventCount, - FrozenDocument resultDocument, - List sortedInfos, ManualRangeMarker[] markers) { + UpdatedRanges(int eventCount, + @NotNull FrozenDocument resultDocument, + @NotNull List sortedInfos, + @NotNull ManualRangeMarker[] markers) { myEventCount = eventCount; myResultDocument = resultDocument; mySortedInfos = sortedInfos; diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java index 142e38b1329c..c23154fd59b9 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java @@ -234,4 +234,18 @@ public class SelfElementInfo extends SmartPointerElementInfo { public String toString() { return "psi:range=" + calcPsiRange() + ",type=" + myIdentikit; } + + public static Segment calcActualRangeAfterDocumentEvents(@NotNull PsiFile containingFile, @NotNull Document document, @NotNull Segment segment, boolean isSegmentGreedy) { + Project project = containingFile.getProject(); + PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(project); + List events = documentManager.getEventsSinceCommit(document); + if (!events.isEmpty()) { + SmartPointerManagerImpl pointerManager = (SmartPointerManagerImpl)SmartPointerManager.getInstance(project); + SmartPointerTracker tracker = pointerManager.getTracker(containingFile.getViewProvider().getVirtualFile()); + if (tracker != null) { + return tracker.getUpdatedRange(containingFile, segment, isSegmentGreedy, (FrozenDocument)documentManager.getLastCommittedDocument(document), events); + } + } + return null; + } } diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerTracker.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerTracker.java index beca357af39f..f57329165b24 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerTracker.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerTracker.java @@ -23,6 +23,7 @@ import com.intellij.openapi.util.LowMemoryWatcher; import com.intellij.openapi.util.Segment; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.util.CommonProcessors; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; @@ -32,7 +33,9 @@ import org.jetbrains.annotations.TestOnly; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; class SmartPointerTracker { private static final ReferenceQueue ourQueue = new ReferenceQueue<>(); @@ -161,6 +164,10 @@ class SmartPointerTracker { synchronized Segment getUpdatedRange(SelfElementInfo info, FrozenDocument document, List events) { return markerCache.getUpdatedRange(info, document, events); } + @Nullable + synchronized Segment getUpdatedRange(@NotNull PsiFile containingFile, @NotNull Segment segment, boolean isSegmentGreedy, @NotNull FrozenDocument frozen, @NotNull List events) { + return MarkerCache.getUpdatedRange(containingFile, segment, isSegmentGreedy, frozen, events); + } synchronized void switchStubToAst(AnchorElementInfo info, PsiElement element) { info.switchToTreeRange(element); diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/text/BlockSupportImpl.java b/platform/core-impl/src/com/intellij/psi/impl/source/text/BlockSupportImpl.java index 5b3ec7dc1ddc..aed3656102f3 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/text/BlockSupportImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/text/BlockSupportImpl.java @@ -28,10 +28,7 @@ import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.PlainTextLanguage; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Couple; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.*; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.PsiManagerEx; @@ -90,18 +87,48 @@ public class BlockSupportImpl extends BlockSupport { final Couple reparseableRoots = findReparseableRoots(fileImpl, oldFileNode, changedPsiRange, newFileText); return reparseableRoots != null ? mergeTrees(fileImpl, reparseableRoots.first, reparseableRoots.second, indicator, lastCommittedText) - : makeFullParse(fileImpl, oldFileNode, newFileText, indicator, lastCommittedText); + : makeFullParse(fileImpl, oldFileNode, newFileText, indicator, lastCommittedText).getFirst(); } + // return diff log, old node to replace, new node (in dummy file) + @NotNull + public static Trinity reparse(@NotNull final PsiFile file, + @NotNull FileASTNode oldFileNode, + @NotNull TextRange changedPsiRange, + @NotNull final CharSequence newFileText, + @NotNull final ProgressIndicator indicator, + @NotNull CharSequence lastCommittedText) { + PsiFileImpl fileImpl = (PsiFileImpl)file; + + final Couple reparseableRoots = findReparseableRoots(fileImpl, oldFileNode, changedPsiRange, newFileText); + DiffLog diffLog; + ASTNode oldRoot; + ASTNode newRoot; + if (reparseableRoots == null) { + Pair.NonNull pair = makeFullParse(fileImpl, oldFileNode, newFileText, indicator, lastCommittedText); + oldRoot = oldFileNode; + newRoot = pair.getSecond(); + diffLog = pair.getFirst(); + } + else { + oldRoot = reparseableRoots.first; + newRoot = reparseableRoots.second; + diffLog = mergeTrees(fileImpl, oldRoot, newRoot, indicator, lastCommittedText); + } + return Trinity.create(diffLog, oldRoot, newRoot); + } + + /** - * This method searches ast node that could be reparsed incrementally and returns pair of target reparseable node and new replacement node. - * Returns null if there is no any chance to make incremental parsing. + * Find ast node that could be reparsed incrementally + * @return Pair (target reparseable node, new replacement node) + * or {@code null} if can't parse incrementally. */ @Nullable - public Couple findReparseableRoots(@NotNull PsiFileImpl file, - @NotNull FileASTNode oldFileNode, - @NotNull TextRange changedPsiRange, - @NotNull CharSequence newFileText) { + public static Couple findReparseableRoots(@NotNull PsiFileImpl file, + @NotNull FileASTNode oldFileNode, + @NotNull TextRange changedPsiRange, + @NotNull CharSequence newFileText) { Project project = file.getProject(); final FileElement fileElement = (FileElement)oldFileNode; final CharTable charTable = fileElement.getCharTable(); @@ -175,12 +202,13 @@ public class BlockSupportImpl extends BlockSupport { ); } + // returns diff log, new file element @NotNull - public static DiffLog makeFullParse(@NotNull PsiFileImpl fileImpl, - @NotNull FileASTNode oldFileNode, - @NotNull CharSequence newFileText, - @NotNull ProgressIndicator indicator, - @NotNull CharSequence lastCommittedText) { + public static Pair.NonNull makeFullParse(@NotNull PsiFileImpl fileImpl, + @NotNull FileASTNode oldFileNode, + @NotNull CharSequence newFileText, + @NotNull ProgressIndicator indicator, + @NotNull CharSequence lastCommittedText) { if (fileImpl instanceof PsiCodeFragment) { FileElement parent = fileImpl.getTreeElement(); final FileElement holderElement = new DummyHolder(fileImpl.getManager(), fileImpl.getContext()).getTreeElement(); @@ -188,7 +216,7 @@ public class BlockSupportImpl extends BlockSupport { DiffLog diffLog = new DiffLog(); diffLog.appendReplaceFileElement(parent, (FileElement)holderElement.getFirstChildNode()); - return diffLog; + return Pair.createNonNull(diffLog, holderElement); } FileViewProvider viewProvider = fileImpl.getViewProvider(); viewProvider.getLanguages(); @@ -217,7 +245,7 @@ public class BlockSupportImpl extends BlockSupport { DiffLog diffLog = mergeTrees(fileImpl, oldFileElement, newFileElement, indicator, lastCommittedText); ((PsiManagerEx)fileImpl.getManager()).getFileManager().setViewProvider(lightFile, null); - return diffLog; + return Pair.createNonNull(diffLog, newFileElement); } @NotNull @@ -227,7 +255,7 @@ public class BlockSupportImpl extends BlockSupport { PsiFile file = providerCopy.getPsi(language); if (file != null && !(file instanceof PsiFileImpl)) { - throw new RuntimeException("View provider " + viewProvider + " refused to provide PsiFileImpl for " + language + details(providerCopy, viewProvider)); + throw new RuntimeException("View provider " + viewProvider + " refused to provide PsiFileImpl for " + language + details(providerCopy, viewProvider) +" and returned this strange thing instead of PsiFileImpl: "+file +" ("+file.getClass()+")"); } PsiFileImpl newFile = (PsiFileImpl)file; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java index 34afe37ba345..6d6238817dac 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java @@ -232,6 +232,10 @@ public class TypedHandler extends TypedActionHandlerBase { long modificationStampBeforeTyping = editor.getDocument().getModificationStamp(); type(originalEditor, charTyped); AutoHardWrapHandler.getInstance().wrapLineIfNecessary(originalEditor, dataContext, modificationStampBeforeTyping); + + if (editor.isDisposed()) { // can be that injected editor disappear + return; + } if (('(' == charTyped || '[' == charTyped || '{' == charTyped) && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockBuilder.java b/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockBuilder.java index 00a684843a94..23d1ab0b871d 100644 --- a/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockBuilder.java +++ b/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockBuilder.java @@ -28,6 +28,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiLanguageInjectionHost; import com.intellij.psi.codeStyle.CodeStyleSettings; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -38,13 +39,13 @@ import java.util.List; public abstract class InjectedLanguageBlockBuilder { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.formatter.xml.XmlInjectedLanguageBlockBuilder"); - public Block createInjectedBlock(ASTNode node, - Block originalBlock, + @NotNull + public Block createInjectedBlock(@NotNull ASTNode node, + @NotNull Block originalBlock, Indent indent, int offset, TextRange range, - @Nullable Language language) - { + @NotNull Language language) { return new InjectedLanguageBlockWrapper(originalBlock, offset, range, indent, language); } @@ -94,9 +95,9 @@ public abstract class InjectedLanguageBlockBuilder { } String childText; - if ((injectionHost.getTextLength() == textRange.getEndOffset() && textRange.getStartOffset() == 0) || - (canProcessFragment((childText = injectionHost.getText()).substring(0, textRange.getStartOffset()), injectionHost) && - canProcessFragment(childText.substring(textRange.getEndOffset()), injectionHost))) { + if (injectionHost.getTextLength() == textRange.getEndOffset() && textRange.getStartOffset() == 0 || + canProcessFragment((childText = injectionHost.getText()).substring(0, textRange.getStartOffset()), injectionHost) && + canProcessFragment(childText.substring(textRange.getEndOffset()), injectionHost)) { injectedFile[0] = injectedPsi; injectedRangeInsideHost.set(textRange); prefixLength.set(shred.getPrefix().length()); @@ -105,8 +106,7 @@ public abstract class InjectedLanguageBlockBuilder { }; final PsiElement injectionHostPsi = injectionHost.getPsi(); PsiFile containingFile = injectionHostPsi.getContainingFile(); - InjectedLanguageManager - .getInstance(containingFile.getProject()).enumerateEx(injectionHostPsi, containingFile, false, injectedPsiVisitor); + InjectedLanguageManager.getInstance(containingFile.getProject()).enumerateEx(injectionHostPsi, containingFile, true, injectedPsiVisitor); if (injectedFile[0] != null) { final Language childLanguage = injectedFile[0].getLanguage(); @@ -156,7 +156,7 @@ public abstract class InjectedLanguageBlockBuilder { final FormattingModel childModel = builder.createModel(childPsi, getSettings()); Block original = childModel.getRootBlock(); - if ((original.isLeaf() && !injectedNode.getText().trim().isEmpty()) || !original.getSubBlocks().isEmpty()) { + if (original.isLeaf() && !injectedNode.getText().trim().isEmpty() || !original.getSubBlocks().isEmpty()) { result.add(createInjectedBlock(injectedNode, original, indent, offset, range, childLanguage)); } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java index 246de939419f..4e344d4c9921 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java @@ -19,6 +19,7 @@ package com.intellij.psi.impl; import com.google.common.annotations.VisibleForTesting; import com.intellij.AppTopics; import com.intellij.injected.editor.DocumentWindow; +import com.intellij.lang.ASTNode; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; @@ -31,10 +32,13 @@ import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectLocator; import com.intellij.openapi.project.impl.ProjectImpl; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Segment; +import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.core.impl.PomModelImpl; @@ -43,8 +47,10 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.impl.source.PostprocessReformattingAspect; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.util.ArrayUtil; import com.intellij.util.FileContentUtil; -import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBusConnection; import org.jetbrains.annotations.NonNls; @@ -52,9 +58,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; //todo listen & notifyListeners readonly events? public class PsiDocumentManagerImpl extends PsiDocumentManagerBase implements SettingsSavingComponent { @@ -138,7 +142,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManagerBase implements Se @VisibleForTesting public void doCommitWithoutReparse(@NotNull Document document) { - finishCommitInWriteAction(document, Collections.emptyList(), true, true); + finishCommitInWriteAction(document, Collections.emptyList(), Collections.emptyList(), true, true); } @Override @@ -150,13 +154,14 @@ public class PsiDocumentManagerImpl extends PsiDocumentManagerBase implements Se @Override protected boolean finishCommitInWriteAction(@NotNull Document document, - @NotNull List> finishProcessors, - boolean synchronously, + @NotNull List finishProcessors, + List reparseInjectedProcessors, + boolean synchronously, boolean forceNoPsiCommit) { if (ApplicationManager.getApplication().isWriteAccessAllowed()) { // can be false for non-physical PSI InjectedLanguageManagerImpl.disposeInvalidEditors(); } - return super.finishCommitInWriteAction(document, finishProcessors, synchronously, forceNoPsiCommit); + return super.finishCommitInWriteAction(document, finishProcessors, reparseInjectedProcessors, synchronously, forceNoPsiCommit); } @Override @@ -191,6 +196,36 @@ public class PsiDocumentManagerImpl extends PsiDocumentManagerBase implements Se ((DocumentCommitThread)myDocumentCommitThread).clearQueue(); } + @NotNull + @Override + List reparseChangedInjectedFragments(@NotNull Document hostDocument, + @NotNull PsiFile hostPsiFile, + @NotNull TextRange hostChangedRange, + @NotNull ProgressIndicator indicator, + @NotNull ASTNode oldRoot, + @NotNull ASTNode newRoot) { + List changedInjected = InjectedLanguageManager.getInstance(myProject).getCachedInjectedDocumentsInRange(hostPsiFile, hostChangedRange); + if (changedInjected.isEmpty()) return Collections.emptyList(); + FileViewProvider hostViewProvider = hostPsiFile.getViewProvider(); + List fromLast = new ArrayList<>(changedInjected); + // make sure modifications do not ruin all document offsets after + fromLast.sort(Collections.reverseOrder(Comparator.comparingInt(doc -> ArrayUtil.getLastElement(doc.getHostRanges()).getEndOffset()))); + List result = new ArrayList<>(changedInjected.size()); + for (DocumentWindow document : fromLast) { + Segment[] ranges = document.getHostRanges(); + if (ranges.length != 0) { + // host document change has left something valid in this document window place. Try to reparse. + PsiFile injectedPsiFile = getCachedPsiFile(document); + if (injectedPsiFile == null || !injectedPsiFile.isValid()) continue; + + BooleanRunnable runnable = InjectedLanguageUtil.reparse(injectedPsiFile, document, hostPsiFile, hostViewProvider, indicator, oldRoot, newRoot); + ContainerUtil.addIfNotNull(result, runnable); + } + } + + return result; + } + @NonNls @Override public String toString() { diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/DocumentWindowImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/DocumentWindowImpl.java index 70ffb43317f9..99ed32e106ab 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/DocumentWindowImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/DocumentWindowImpl.java @@ -20,6 +20,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiLanguageInjectionHost; import com.intellij.util.ObjectUtils; import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.ImmutableCharSequence; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -39,9 +40,9 @@ class DocumentWindowImpl extends UserDataHolderBase implements Disposable, Docum private CachedText myCachedText; - DocumentWindowImpl(@NotNull DocumentEx delegate, boolean oneLine, @NotNull Place shreds) { + DocumentWindowImpl(@NotNull DocumentEx delegate, @NotNull Place shreds) { myDelegate = delegate; - myOneLine = oneLine; + myOneLine = ContainerUtil.and(shreds, s->((ShredImpl)s).isOneLine()); synchronized (myLock) { myShreds = shreds; } @@ -660,8 +661,7 @@ class DocumentWindowImpl extends UserDataHolderBase implements Disposable, Docum // heuristics: return offset closest to the caret Editor[] editors = EditorFactory.getInstance().getEditors(getDelegate()); Editor editor = editors.length == 0 ? null : editors[0]; - if (editor != null) - { + if (editor != null) { if (editor instanceof EditorWindow) editor = ((EditorWindow)editor).getDelegate(); int caret = editor.getCaretModel().getOffset(); return Math.abs(caret - offsetInLeftFragment) < Math.abs(caret - offsetInRightFragment) ? offsetInLeftFragment : offsetInRightFragment; @@ -872,8 +872,7 @@ class DocumentWindowImpl extends UserDataHolderBase implements Disposable, Docum Segment hostRange = shred.getHostRangeMarker(); Segment other = otherShred.getHostRangeMarker(); - if (hostRange == null || other == null || hostRange.getStartOffset() != other.getStartOffset()) return false; - if (hostRange.getEndOffset() != other.getEndOffset()) return false; + if (hostRange == null || other == null || !TextRange.areSegmentsEqual(hostRange, other)) return false; } return true; } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/EditorWindowImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/EditorWindowImpl.java index c52879eaadbb..44ab25401191 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/EditorWindowImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/EditorWindowImpl.java @@ -113,14 +113,33 @@ class EditorWindowImpl extends com.intellij.injected.editor.EditorWindowImpl imp while (iterator.hasNext()) { EditorWindowImpl editorWindow = iterator.next(); if (!editorWindow.isValid()) { - editorWindow.dispose(); - - InjectedLanguageUtil.clearCaches(editorWindow.myInjectedFile, editorWindow.getDocument()); + disposeEditor(editorWindow); iterator.remove(); } } } + private static void disposeEditor(@NotNull EditorWindow editorWindow) { + EditorWindowImpl impl = (EditorWindowImpl)editorWindow; + impl.dispose(); + + InjectedLanguageUtil.clearCaches(impl.myInjectedFile, impl.getDocument()); + } + + static void disposeEditorFor(@NotNull DocumentWindow documentWindow) { + synchronized (allEditors) { + for (Iterator iterator = allEditors.iterator(); iterator.hasNext(); ) { + EditorWindowImpl editor = iterator.next(); + if (InjectionRegistrarImpl.intersect(editor.getDocument(), (DocumentWindowImpl)documentWindow)) { + disposeEditor(editor); + iterator.remove(); + break; + } + } + } + } + + @Override public boolean isValid() { return !isDisposed() && !myInjectedFile.getProject().isDisposed() && myInjectedFile.isValid() && myDocumentWindow.isValid(); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageManagerImpl.java index a76460ac2e56..2faeb4b772fb 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageManagerImpl.java @@ -420,7 +420,7 @@ public class InjectedLanguageManagerImpl extends InjectedLanguageManager impleme DocumentEx delegate = ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(project)).getLastCommittedDocument(document.getDelegate()); Place place = new Place(); place.addAll(ContainerUtil.map(shreds, shred -> ((ShredImpl)shred).withPsiRange())); - return new DocumentWindowImpl(delegate, document.isOneLine(), place); + return new DocumentWindowImpl(delegate, place); } private static int appendRange(@NotNull List result, int start, int length) { diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java index 9d8596dd32c6..de9ed33fdc7f 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java @@ -20,6 +20,7 @@ import com.intellij.extapi.psi.PsiFileBase; import com.intellij.injected.editor.DocumentWindow; import com.intellij.injected.editor.EditorWindow; import com.intellij.injected.editor.VirtualFileWindow; +import com.intellij.lang.ASTNode; import com.intellij.lang.Language; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.lang.injection.MultiHostRegistrar; @@ -30,11 +31,13 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; +import com.intellij.psi.impl.BooleanRunnable; import com.intellij.psi.impl.DebugUtil; import com.intellij.psi.impl.PsiManagerEx; import com.intellij.psi.impl.PsiParameterizedCachedValue; @@ -60,8 +63,8 @@ public class InjectedLanguageUtil { static final Key, TextRange>>> HIGHLIGHT_TOKENS = Key.create("HIGHLIGHT_TOKENS"); public static final Key INJECTED_FRAGMENT_TYPE = Key.create("INJECTED_FRAGMENT_TYPE"); public static final Key FRANKENSTEIN_INJECTION = InjectedLanguageManager.FRANKENSTEIN_INJECTION; - // meaning: injected file text is probably incorrect + // meaning: injected file text is probably incorrect public static void forceInjectionOnElement(@NotNull PsiElement host) { enumerate(host, (injectedPsi, places) -> { }); @@ -723,4 +726,23 @@ public class InjectedLanguageUtil { @NotNull TextRange rangeInsideHost) { ((InjectionRegistrarImpl)registrar).injectReference(language, prefix, suffix, host, rangeInsideHost); } + + // null means failed to reparse + public static BooleanRunnable reparse(@NotNull PsiFile injectedPsiFile, + @NotNull DocumentWindow document, + @NotNull PsiFile hostPsiFile, + @NotNull FileViewProvider hostViewProvider, + @NotNull ProgressIndicator indicator, @NotNull ASTNode oldRoot, @NotNull ASTNode newRoot) { + Language language = injectedPsiFile.getLanguage(); + InjectedFileViewProvider provider = (InjectedFileViewProvider)injectedPsiFile.getViewProvider(); + VirtualFile oldInjectedVFile = provider.getVirtualFile(); + VirtualFile hostVirtualFile = hostViewProvider.getVirtualFile(); + BooleanRunnable runnable = InjectionRegistrarImpl + .reparse(language, (DocumentWindowImpl)document, injectedPsiFile, (VirtualFileWindow)oldInjectedVFile, hostVirtualFile, hostPsiFile, + indicator, oldRoot, newRoot); + if (runnable == null) { + EditorWindowImpl.disposeEditorFor(document); + } + return runnable; + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectionRegistrarImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectionRegistrarImpl.java index bdeb3f513829..51fd80a5628e 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectionRegistrarImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectionRegistrarImpl.java @@ -19,40 +19,38 @@ package com.intellij.psi.impl.source.tree.injected; import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator; import com.intellij.injected.editor.DocumentWindow; import com.intellij.injected.editor.VirtualFileWindow; -import com.intellij.lang.ASTNode; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageParserDefinitions; -import com.intellij.lang.ParserDefinition; +import com.intellij.lang.*; import com.intellij.lang.injection.MultiHostRegistrar; import com.intellij.lexer.Lexer; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.editor.impl.DocumentImpl; import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; import com.intellij.openapi.fileTypes.SyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; +import com.intellij.psi.impl.BooleanRunnable; import com.intellij.psi.impl.DebugUtil; import com.intellij.psi.impl.DocumentCommitThread; import com.intellij.psi.impl.PsiDocumentManagerBase; +import com.intellij.psi.impl.smartPointers.Identikit; +import com.intellij.psi.impl.smartPointers.SelfElementInfo; import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl; import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.impl.source.resolve.FileContextUtil; import com.intellij.psi.impl.source.text.BlockSupportImpl; import com.intellij.psi.impl.source.text.DiffLog; import com.intellij.psi.impl.source.tree.FileElement; -import com.intellij.psi.impl.source.tree.LeafElement; -import com.intellij.psi.impl.source.tree.TreeElement; import com.intellij.psi.impl.source.tree.TreeUtil; import com.intellij.psi.injection.ReferenceInjector; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; -import com.intellij.testFramework.LightVirtualFile; import com.intellij.util.ObjectUtils; import com.intellij.util.PathUtil; import com.intellij.util.SmartList; @@ -62,7 +60,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; @SuppressWarnings("deprecation") @@ -135,32 +132,6 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost currentThread = null; } - private static class PlaceInfo { - @NotNull private final String prefix; - @NotNull private final String suffix; - @NotNull private final PsiLanguageInjectionHost host; - @NotNull private final TextRange rangeInsideHost; - - PlaceInfo(@NotNull String prefix, - @NotNull String suffix, - @NotNull PsiLanguageInjectionHost host, - @NotNull TextRange rangeInsideHost) { - this.prefix = prefix; - this.suffix = suffix; - this.host = host; - this.rangeInsideHost = rangeInsideHost; - } - - @Override - public String toString() { - return "Shred "+ - (prefix.isEmpty() ? "" : "prefix='"+prefix+"' ") + - (suffix.isEmpty() ? "" : "suffix='"+suffix+"' ") + - "in " + host+ " " + - "in range "+rangeInsideHost; - } - } - @Override @NotNull public MultiHostRegistrar addPlace(@NonNls @Nullable String prefix, @@ -174,7 +145,7 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost } PsiFile containingFile = PsiUtilCore.getTemplateLanguageFile(host); assert containingFile == myHostPsiFile : exceptionContext("Trying to inject into foreign file: "+containingFile, myLanguage, - myHostPsiFile, myHostVirtualFile, myHostDocument, myContextElement, placeInfos); + myHostPsiFile, myHostVirtualFile, myHostDocument, placeInfos); TextRange hostTextRange = host.getTextRange(); if (!hostTextRange.contains(rangeInsideHost.shiftRight(hostTextRange.getStartOffset()))) { clear(); @@ -183,7 +154,9 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost } cleared = false; - PlaceInfo info = new PlaceInfo(ObjectUtils.notNull(prefix, ""), ObjectUtils.notNull(suffix, ""), host, rangeInsideHost); + String nnPrefix = StringUtil.isEmpty(prefix) ? "" : prefix; // intern empty strings too to reduce gc + String nnSuffix = StringUtil.isEmpty(suffix) ? "" : suffix; // intern empty strings too to reduce gc + PlaceInfo info = new PlaceInfo(nnPrefix, nnSuffix, host, rangeInsideHost); placeInfos.add(info); return this; @@ -195,43 +168,44 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost } } - @NotNull - private static Pair.NonNull createShred(@NotNull Project project, @NotNull PlaceInfo info, - @NotNull StringBuilder outChars, - @NotNull PsiFile hostPsiFile) { + private static void decode(@NotNull PlaceInfo info, @NotNull StringBuilder outChars) { int startOffset = outChars.length(); - String prefix = info.prefix; - outChars.append(prefix); - PsiLanguageInjectionHost host = info.host; - LiteralTextEscaper textEscaper = host.createLiteralTextEscaper(); + outChars.append(info.prefix); + LiteralTextEscaper textEscaper = info.myEscaper; - TextRange rangeInsideHost = info.rangeInsideHost; - TextRange relevantRange = textEscaper.getRelevantTextRange().intersection(rangeInsideHost); + TextRange relevantRange = info.getRelevantRangeInsideHost(); if (relevantRange == null) { relevantRange = TextRange.from(textEscaper.getRelevantTextRange().getStartOffset(), 0); } else { int before = outChars.length(); - boolean decodeFailed = !textEscaper.decode(relevantRange, outChars); + boolean decodeSuccessful = textEscaper.decode(relevantRange, outChars); int after = outChars.length(); assert after >= before : "Escaper " + textEscaper + "("+textEscaper.getClass()+") must not mangle char buffer"; - if (decodeFailed) { + if (!decodeSuccessful) { // if there are invalid chars, adjust the range - int offsetInHost = textEscaper.getOffsetInHost(outChars.length() - before, rangeInsideHost); + int offsetInHost = textEscaper.getOffsetInHost(outChars.length() - before, info.registeredRangeInsideHost); relevantRange = relevantRange.intersection(new ProperTextRange(0, offsetInHost)); } } - String suffix = info.suffix; - outChars.append(suffix); + outChars.append(info.suffix); int endOffset = outChars.length(); - TextRange hostTextRange = host.getTextRange(); - TextRange relevantRangeInHost = relevantRange.shiftRight(hostTextRange.getStartOffset()); - SmartPointerManagerImpl manager = (SmartPointerManagerImpl)SmartPointerManager.getInstance(project); - ShredImpl shred = new ShredImpl(manager.createSmartPsiFileRangePointer(hostPsiFile, relevantRangeInHost, true), - manager.createSmartPsiElementPointer(host, hostPsiFile, true), - prefix, suffix, new ProperTextRange(startOffset, endOffset), false, textEscaper.isOneLine()); - return Pair.createNonNull(shred, textEscaper); + info.rangeInDecodedPSI = new ProperTextRange(startOffset, endOffset); + info.rangeInHostElement = relevantRange; + } + @NotNull + private static ShredImpl createShred(@NotNull PlaceInfo info, @NotNull StringBuilder outChars, @NotNull PsiFile hostPsiFile) { + decode(info, outChars); + + TextRange relevantRange = info.rangeInHostElement; + + TextRange hostTextRange = info.host.getTextRange(); + TextRange relevantRangeInHostFile = relevantRange.shiftRight(hostTextRange.getStartOffset()); + SmartPointerManagerImpl manager = (SmartPointerManagerImpl)SmartPointerManager.getInstance(hostPsiFile.getProject()); + return new ShredImpl(manager.createSmartPsiFileRangePointer(hostPsiFile, relevantRangeInHostFile, true), + manager.createSmartPsiElementPointer(info.host, hostPsiFile, true), + info.prefix, info.suffix, info.rangeInDecodedPSI, false, info.myEscaper.isOneLine()); } @Override @@ -244,12 +218,12 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost if (placeInfos.isEmpty()) { throw new IllegalStateException("Seems you haven't called addPlace()"); } + Language forcedLanguage = myContextElement.getUserData(InjectedFileViewProvider.LANGUAGE_FOR_INJECTED_COPY_KEY); + checkForCorrectContextElement(placeInfos, myContextElement, myLanguage, myHostPsiFile, myHostVirtualFile, myHostDocument); + synchronized (InjectedLanguageManagerImpl.ourInjectionPsiLock) { - Language forcedLanguage = myContextElement.getUserData(InjectedFileViewProvider.LANGUAGE_FOR_INJECTED_COPY_KEY); - PsiFile psiFile = - createInjectedFile(myProject, myLanguage, forcedLanguage, - myHostDocument, myHostVirtualFile, myHostPsiFile, fileExtension, placeInfos, - myContextElement); + PsiFile psiFile = createInjectedFile(myLanguage, forcedLanguage, + myHostDocument, myHostVirtualFile, myHostPsiFile, fileExtension, placeInfos); addFileToResults(psiFile); PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(myProject); @@ -262,81 +236,49 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost } } + private static void checkForCorrectContextElement(@NotNull List placeInfos, + @NotNull PsiElement contextElement, + @NotNull Language language, + @NotNull PsiFile hostPsiFile, + @NotNull VirtualFile hostVirtualFile, + @NotNull DocumentEx hostDocument) { + boolean isAncestor = false; + for (PlaceInfo info : placeInfos) { + isAncestor |= PsiTreeUtil.isAncestor(contextElement, info.host, false); + } + assert isAncestor : exceptionContext("Context element " + contextElement.getTextRange() + ": '" + contextElement + "'; " + + " must be the parent of at least one of injection hosts", language, + hostPsiFile, hostVirtualFile, hostDocument, placeInfos); + } + @NotNull - private static PsiFile createInjectedFile(@NotNull Project project, - @NotNull Language language, @Nullable Language forcedLanguage, + private static PsiFile createInjectedFile(@NotNull Language language, @Nullable Language forcedLanguage, @NotNull DocumentEx hostDocument, @NotNull VirtualFile hostVirtualFile, @NotNull PsiFile hostPsiFile, @Nullable String injectedFileExtension, - @NotNull List placeInfos, - @NotNull PsiElement contextElement) { + @NotNull List placeInfos) { + Project project = hostPsiFile.getProject(); + PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(project); + StringBuilder decodedChars = new StringBuilder(); + Place place = new Place(); + for (PlaceInfo info : placeInfos) { + ShredImpl shred = createShred(info, decodedChars, hostPsiFile); + place.add(shred); + info.hostSmartPointer = shred.getSmartPointer(); + } synchronized (InjectedLanguageManagerImpl.ourInjectionPsiLock) { - PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(project); - - StringBuilder decodedChars = new StringBuilder(); - boolean isOneLine = true; - Place place = new Place(); - boolean isAncestor = false; - List escapers = new ArrayList<>(placeInfos.size()); - for (PlaceInfo info : placeInfos) { - Pair.NonNull p = createShred(project, info, decodedChars, hostPsiFile); - ShredImpl shred = p.getFirst(); - isOneLine &= shred.isOneLine(); - place.add(shred); - - isAncestor |= PsiTreeUtil.isAncestor(contextElement, info.host, false); - escapers.add(p.getSecond()); - } - assert isAncestor : exceptionContext(contextElement + " must be the parent of at least one of injection hosts", language, - hostPsiFile, hostVirtualFile, hostDocument, contextElement, placeInfos); - DocumentWindowImpl documentWindow = new DocumentWindowImpl(hostDocument, isOneLine, place); + DocumentWindowImpl documentWindow = new DocumentWindowImpl(hostDocument, place); String fileName = PathUtil.makeFileName(hostVirtualFile.getName(), injectedFileExtension); - VirtualFileWindowImpl virtualFile = new VirtualFileWindowImpl(fileName, hostVirtualFile, documentWindow, language, decodedChars); - Language finalLanguage = forcedLanguage == null ? LanguageSubstitutors.INSTANCE.substituteLanguage(language, virtualFile, project) : forcedLanguage; - createDocument(virtualFile); - - InjectedFileViewProvider viewProvider = new InjectedFileViewProvider(PsiManager.getInstance(project), virtualFile, documentWindow, - finalLanguage); - ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(finalLanguage); - assert parserDefinition != null : "Parser definition for language " + finalLanguage + " is null"; - PsiFile psiFile = parserDefinition.createFile(viewProvider); + ASTNode parsedNode = + parseFile(language, forcedLanguage, documentWindow, hostVirtualFile, hostDocument, hostPsiFile, project, documentWindow.getText(), + placeInfos, decodedChars, fileName); + PsiFile psiFile = (PsiFile)parsedNode.getPsi(); + InjectedFileViewProvider viewProvider = (InjectedFileViewProvider)psiFile.getViewProvider(); SmartPsiElementPointer pointer = ((ShredImpl)place.get(0)).getSmartPointer(); - - final ASTNode parsedNode = keepTreeFromChameleoningBack(psiFile); - - assert parsedNode instanceof FileElement : "Parsed to " + parsedNode + " instead of FileElement"; - - String documentText = documentManager.getLastCommittedDocument(documentWindow).getText(); - assert ((FileElement)parsedNode).textMatches(decodedChars) : exceptionContext("Before patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText() + "'\n---chars:\n'" + - decodedChars + "'", - finalLanguage, hostPsiFile, hostVirtualFile, - hostDocument, contextElement, placeInfos); - - viewProvider.doNotInterruptMeWhileImPatchingLeaves(() -> { - try { - patchLeaves(parsedNode, place, escapers); - } - catch (ProcessCanceledException e) { - throw e; - } - catch (RuntimeException e) { - throw new RuntimeException(exceptionContext("Patch error", finalLanguage, hostPsiFile, hostVirtualFile, hostDocument, - contextElement, placeInfos), e); - } - }); - if (!((FileElement)parsedNode).textMatches(documentText)) { - throw new AssertionError(exceptionContext("After patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText() + "'\n---chars:\n'" + - decodedChars + "'", - finalLanguage, hostPsiFile, hostVirtualFile, hostDocument, contextElement, placeInfos)); - } - - virtualFile.setContent(null, documentWindow.getText(), false); - virtualFile.setWritable(virtualFile.getDelegate().isWritable()); - - cacheEverything(place, documentWindow, viewProvider, psiFile, pointer); + cacheEverything(place, documentWindow, viewProvider, psiFile, pointer, documentManager); PsiFile cachedPsiFile = documentManager.getCachedPsiFile(documentWindow); assert cachedPsiFile == psiFile : "Cached psi :"+ cachedPsiFile +" instead of "+psiFile; @@ -352,8 +294,7 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost psiFile = newFile; viewProvider = (InjectedFileViewProvider)psiFile.getViewProvider(); documentWindow = (DocumentWindowImpl)viewProvider.getDocument(); - virtualFile = (VirtualFileWindowImpl)viewProvider.getVirtualFile(); - boolean shredsReused = !cacheEverything(place, documentWindow, viewProvider, psiFile, pointer); + boolean shredsReused = !cacheEverything(place, documentWindow, viewProvider, psiFile, pointer, documentManager); if (shredsReused) { place.dispose(); mergedPlace = documentWindow.getShreds(); @@ -363,28 +304,33 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost assert psiFile.isValid(); assert mergedPlace.isValid(); assert viewProvider.isValid(); - - try { - List, TextRange>> - tokens = obtainHighlightTokensFromLexer(finalLanguage, decodedChars, place, virtualFile, project, escapers); - psiFile.putUserData(InjectedLanguageUtil.HIGHLIGHT_TOKENS, tokens); - } - catch (ProcessCanceledException e) { - throw e; - } - catch (RuntimeException e) { - throw new RuntimeException(exceptionContext("Obtaining tokens error", finalLanguage, hostPsiFile, hostVirtualFile, - hostDocument, contextElement, placeInfos), e); - } return psiFile; } } - public void injectReference(@NotNull Language language, - @NotNull String prefix, - @NotNull String suffix, - @NotNull PsiLanguageInjectionHost host, - @NotNull TextRange rangeInsideHost) { + private static class PatchException extends Exception { + PatchException(String message) { + super(message); + } + } + private static void patchLeaves(@NotNull List placeInfos, + @NotNull InjectedFileViewProvider viewProvider, + @NotNull ASTNode parsedNode, + @NotNull CharSequence documentText) throws PatchException { + viewProvider.doNotInterruptMeWhileImPatchingLeaves(() -> { + LeafPatcher patcher = new LeafPatcher(placeInfos, parsedNode.getTextLength()); + patcher.patch(parsedNode, placeInfos); + }); + if (!((FileElement)parsedNode).textMatches(documentText)) { + throw new PatchException("After patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText()); + } + } + + void injectReference(@NotNull Language language, + @NotNull String prefix, + @NotNull String suffix, + @NotNull PsiLanguageInjectionHost host, + @NotNull TextRange rangeInsideHost) { ParserDefinition parser = LanguageParserDefinitions.INSTANCE.forLanguage(language); if (parser != null) { throw new IllegalArgumentException("Language "+language+" being injected as reference must not have ParserDefinition and yet - "+parser); @@ -406,8 +352,8 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost addPlace(prefix, suffix, host, rangeInsideHost); Place place = new Place(); StringBuilder decodedChars = new StringBuilder(); - Pair.NonNull p = createShred(myProject, placeInfos.get(0), decodedChars, myHostPsiFile); - place.add(p.getFirst()); + ShredImpl shred = createShred(placeInfos.get(0), decodedChars, myHostPsiFile); + place.add(shred); if (resultReferences == null) { resultReferences = new SmartList<>(); } @@ -415,19 +361,13 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost clear(); } - - private static void createDocument(@NotNull LightVirtualFile virtualFile) { - CharSequence content = virtualFile.getContent(); - DocumentImpl document = new DocumentImpl(content, StringUtil.indexOf(content, '\r') >= 0, false); - FileDocumentManagerImpl.registerDocument(document, virtualFile); - } - // returns true if shreds were set, false if old ones were reused private static boolean cacheEverything(@NotNull Place place, @NotNull DocumentWindowImpl documentWindow, @NotNull InjectedFileViewProvider viewProvider, @NotNull PsiFile psiFile, - @NotNull SmartPsiElementPointer pointer) { + @NotNull SmartPsiElementPointer pointer, + @NotNull PsiDocumentManagerBase documentManager) { FileDocumentManagerImpl.registerDocument(documentWindow, viewProvider.getVirtualFile()); DebugUtil.startPsiModification("MultiHostRegistrar cacheEverything"); @@ -439,7 +379,7 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost } psiFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, pointer); - ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(psiFile.getProject())).associatePsi(documentWindow, psiFile); + documentManager.associatePsi(documentWindow, psiFile); keepTreeFromChameleoningBack(psiFile); @@ -453,14 +393,12 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost @NotNull PsiFile hostPsiFile, @NotNull VirtualFile hostVirtualFile, @NotNull DocumentEx hostDocument, - @NotNull PsiElement contextElement, @NotNull List placeInfos) { return msg + ".\n" + - language + ";\n " + - "Host file: " + hostPsiFile + " in '" + hostVirtualFile.getPresentableUrl() + "'" + + "OK let's see. Host file: " + hostPsiFile + " in '" + hostVirtualFile.getPresentableUrl() + "' (" + hostPsiFile.getLanguage()+") " + (PsiDocumentManager.getInstance(hostPsiFile.getProject()).isUncommited(hostDocument) ? " (uncommitted)" : "") + "\n" + - "Context element " + contextElement.getTextRange() + ": '" + contextElement + "'; " + - "Ranges: " + placeInfos; + "Was injected " + language + + " at ranges: " + placeInfos; } private static final Key TREE_HARD_REF = Key.create("TREE_HARD_REF"); @@ -524,35 +462,6 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost } - private static void patchLeaves(@NotNull ASTNode parsedNode, - @NotNull Place shreds, - @NotNull List escapers) { - LeafPatcher patcher = new LeafPatcher(shreds, escapers); - ((TreeElement)parsedNode).acceptTree(patcher); - - assert ((TreeElement)parsedNode).textMatches(patcher.catLeafs) : "Malformed PSI structure: leaf texts do not add up to the whole file text." + - "\nFile text (from tree) :'"+parsedNode.getText()+"'" + - "\nFile text (from PSI) :'"+parsedNode.getPsi().getText()+"'" + - "\nLeaf texts concatenated:'"+ patcher.catLeafs +"';" + - "\nFile root: "+parsedNode+ - "\nLanguage: "+parsedNode.getPsi().getLanguage()+ - "\nHost file: "+ shreds.getHostPointer().getVirtualFile() - ; - DebugUtil.startPsiModification("injection leaf patching"); - try { - for (Map.Entry entry : patcher.newTexts.entrySet()) { - LeafElement leaf = entry.getKey(); - String newText = entry.getValue(); - leaf.rawReplaceWithText(newText); - } - } - finally { - DebugUtil.finishPsiModification(); - } - - TreeUtil.clearCaches((TreeElement)parsedNode); - } - // under InjectedLanguageManagerImpl.ourInjectionPsiLock @NotNull private static PsiFile registerDocument(@NotNull DocumentWindowImpl newDocumentWindow, @@ -602,7 +511,7 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost return newInjectedPsi; } - private static void mergePsi(@NotNull PsiFileImpl oldFile, + private static void mergePsi(@NotNull PsiFile oldFile, @NotNull ASTNode oldFileNode, @NotNull PsiFile injectedPsi, @NotNull ASTNode injectedNode) { @@ -611,7 +520,7 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost oldViewProvider.performNonPhysically(() -> { DebugUtil.startPsiModification("injected tree diff"); try { - final DiffLog diffLog = BlockSupportImpl.mergeTrees(oldFile, oldFileNode, injectedNode, new DaemonProgressIndicator(), + final DiffLog diffLog = BlockSupportImpl.mergeTrees((PsiFileImpl)oldFile, oldFileNode, injectedNode, new DaemonProgressIndicator(), oldFileNode.getText()); DocumentCommitThread.doActualPsiChange(oldFile, diffLog); } @@ -622,7 +531,226 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost } } - private static boolean intersect(DocumentWindowImpl doc1, DocumentWindowImpl doc2) { + /** + * {@code language} was injected in {@code hostPsiFile} (located in {@code hostVirtualFile}) + * and corresponding injected {@code oldInjectedPsi} (along with {@code oldDocumentWindow} and {@code oldInjectedVirtualFile}) were created. + * Then the user came along and changed the host document in {@code hostVirtualFile}. + * Document commit started and produced PSI diff {@code oldRoot} -> {@code newRoot} in the host PSI. + * + * Now we try to produce similar diff for the injected fragment PSI. + * To do that, we: + *
+   * - calculate where injected shreds from oldRoot will be located in the newRoot
+   *   -- get old range markers for shreds
+   *   -- calculate new ranges by applying doc changes in {@link SelfElementInfo#calcActualRangeAfterDocumentEvents(PsiFile, Document, Segment, boolean)}
+   *      (have to do all that manually because smart pointers are not yet updated)
+   * - find similar PsiLanguageInjectionHost in the {@code newRoot} at these ranges
+   *   (since newRoot is a non-physical copy of hostPsiFile with PSI diff applied, we have to do that semi-manually too)
+   * - create fake injection using this new injection host (along with characters decoding/leaf patching)
+   *   (see call to {@link #parseFile(Language, Language, DocumentWindowImpl, VirtualFile, DocumentEx, PsiFile, Project, CharSequence, List, StringBuilder, String)} )
+   * - feed two injections, the old and the new created fake to the standard tree diff
+   *   (see call to {@link BlockSupportImpl#mergeTrees(PsiFileImpl, ASTNode, ASTNode, ProgressIndicator, CharSequence)} )
+   * - return continuation which performs actual PSI replace, just like {@link DocumentCommitThread#doCommit(DocumentCommitThread.CommitTask, PsiFile, FileASTNode, ProperTextRange, List)} does
+   *   {@code null} means we failed to reparse and will have to kill the injection.
+   * 
+ */ + static BooleanRunnable reparse(@NotNull Language language, + @NotNull DocumentWindowImpl oldDocumentWindow, + @NotNull PsiFile oldInjectedPsi, + @NotNull VirtualFileWindow oldInjectedVirtualFile, + @NotNull VirtualFile hostVirtualFile, + @NotNull PsiFile hostPsiFile, + @NotNull ProgressIndicator indicator, + @NotNull ASTNode oldRoot, @NotNull ASTNode newRoot) { + synchronized (InjectedLanguageManagerImpl.ourInjectionPsiLock) { + Project project = hostPsiFile.getProject(); + String newText = oldDocumentWindow.getText(); + FileASTNode oldNode = oldInjectedPsi.getNode(); + InjectedFileViewProvider oldInjectedPsiViewProvider = (InjectedFileViewProvider)oldInjectedPsi.getViewProvider(); + String oldPsiText = oldNode.getText(); + if (newText.equals(oldPsiText)) return ()->true; + if (oldDocumentWindow.isOneLine() && newText.contains("\n") != oldPsiText.contains("\n")) { + // one-lineness changed, e.g. when enter pressed in the middle of a string literal + return null; + } + Place oldPlace = oldDocumentWindow.getShreds(); + // can be different from newText if decode fails in the middle and we'll have to shrink the document + StringBuilder newDocumentText = new StringBuilder(newText.length()); + // we need escaper but it only works with committed PSI, + // so we get the committed (but not yet applied) PSI from the commit-document-in-the-background process + // and find the corresponding injection host there + // and create literal escaper from that new (dummy) psi + List placeInfos = new SmartList<>(); + StringBuilder chars = new StringBuilder(); + for (PsiLanguageInjectionHost.Shred shred : oldPlace) { + PsiLanguageInjectionHost oldHost = shred.getHost(); + if (oldHost == null) return null; + SmartPsiElementPointer hostPointer = ((ShredImpl)shred).getSmartPointer(); + Segment hostRangeMarker = calcActualRange(hostPointer, oldDocumentWindow.getDelegate()); + if (hostRangeMarker == null) return null; + PsiLanguageInjectionHost newDummyInjectionHost = findNewInjectionHost(hostPsiFile, oldRoot, newRoot, oldHost, hostRangeMarker); + if (newDummyInjectionHost == null) { + return null; + } + Segment hostInjectionRange = shred.getHostRangeMarker(); // in the new document + if (hostInjectionRange == null) return null; + TextRange rangeInsideHost = TextRange.create(hostInjectionRange).shiftLeft(hostRangeMarker.getStartOffset()); + + PlaceInfo info = new PlaceInfo(shred.getPrefix(), shred.getSuffix(), newDummyInjectionHost, rangeInsideHost); + placeInfos.add(info); + info.hostSmartPointer = hostPointer; + + decode(info, chars); + + // pass the old pointers because their offsets will be adjusted automatically (SmartPsiElementPointer does that) + TextRange rangeInHostElementPSI = info.rangeInHostElement; + + newDocumentText.append(shred.getPrefix()); + newDocumentText.append(newDummyInjectionHost.getText(), rangeInHostElementPSI.getStartOffset(), rangeInHostElementPSI.getEndOffset()); + newDocumentText.append(shred.getSuffix()); + } + // newDocumentText can be shorter if decode failed + //assert newText.equals(newDocumentText.toString()) : "-\n"+newText+"\n--\n"+newDocumentText+"\n---\n"; + + PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(project); + DocumentEx hostDocument = oldDocumentWindow.getDelegate(); + assert documentManager.isUncommited(hostDocument); + String fileName = ((VirtualFileWindowImpl)oldInjectedVirtualFile).getName(); + ASTNode parsedNode = parseFile(language, language, oldDocumentWindow, + hostVirtualFile, hostDocument, hostPsiFile, project, newDocumentText, placeInfos, chars, + fileName); + + DiffLog diffLog = BlockSupportImpl.mergeTrees((PsiFileImpl)oldInjectedPsi, oldNode, parsedNode, indicator, oldPsiText); + + return () -> { + oldInjectedPsiViewProvider.performNonPhysically(() -> { + DebugUtil.startPsiModification("injected tree diff"); + try { + DocumentCommitThread.doActualPsiChange(oldInjectedPsi, diffLog); + + PsiDocumentManagerBase documentManagerBase = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(project); + + // create new shreds after commit is complete because otherwise the range markers will be changed in MarkerCache.updateMarkers + Place newPlace = new Place(); + for (int i = 0; i < oldPlace.size(); i++) { + PsiLanguageInjectionHost.Shred shred = oldPlace.get(i); + PlaceInfo info = placeInfos.get(i); + TextRange rangeInDecodedPSI = info.rangeInDecodedPSI; + TextRange rangeInHostElementPSI = info.rangeInHostElement; + ShredImpl newShred = ((ShredImpl)shred).withRange(rangeInDecodedPSI, rangeInHostElementPSI); + newPlace.add(newShred); + } + + SmartPsiElementPointer pointer = placeInfos.get(0).hostSmartPointer; + cacheEverything(newPlace, oldDocumentWindow, oldInjectedPsiViewProvider, oldInjectedPsi, pointer, documentManagerBase); + String docText = oldDocumentWindow.getText(); + assert docText.equals(newText) : "=\n" + docText + "\n==\n" + newDocumentText + "\n===\n"; + } + finally { + DebugUtil.finishPsiModification(); + } + }); + return true; + }; + } + } + + private static Segment calcActualRange(SmartPsiElementPointer pointer, + Document document) { + PsiFile containingFile = pointer.getContainingFile(); + return SelfElementInfo.calcActualRangeAfterDocumentEvents(containingFile, document, pointer.getPsiRange(), true); + } + + @NotNull + private static ASTNode parseFile(@NotNull Language language, Language forcedLanguage, + @NotNull DocumentWindowImpl documentWindow, + @NotNull VirtualFile hostVirtualFile, + @NotNull DocumentEx hostDocument, @NotNull PsiFile hostPsiFile, + @NotNull Project project, + @NotNull CharSequence documentText, + @NotNull List placeInfos, + @NotNull StringBuilder decodedChars, + @NotNull String fileName) { + VirtualFileWindowImpl virtualFile = new VirtualFileWindowImpl(fileName, hostVirtualFile, documentWindow, language, decodedChars); + Language finalLanguage = forcedLanguage == null ? LanguageSubstitutors.INSTANCE.substituteLanguage(language, virtualFile, project) : forcedLanguage; + InjectedFileViewProvider viewProvider = new InjectedFileViewProvider(PsiManager.getInstance(project), virtualFile, documentWindow, finalLanguage); + ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(finalLanguage); + assert parserDefinition != null : "Parser definition for language " + finalLanguage + " is null"; + PsiFile psiFile = parserDefinition.createFile(viewProvider); + + final ASTNode parsedNode = keepTreeFromChameleoningBack(psiFile); + + assert parsedNode instanceof FileElement : "Parsed to " + parsedNode + " instead of FileElement"; + + assert ((FileElement)parsedNode).textMatches(decodedChars) : exceptionContext("Before patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText() + "'\n---chars:\n'" + + decodedChars + "'", + finalLanguage, hostPsiFile, hostVirtualFile, + hostDocument, placeInfos); + try { + patchLeaves(placeInfos, viewProvider, parsedNode, documentText); + } + catch (PatchException e) { + throw new RuntimeException(exceptionContext(e.getMessage()+"'\n---chars:\n'" + decodedChars + "'", finalLanguage, hostPsiFile, hostVirtualFile, hostDocument, placeInfos)); + } + + virtualFile.setContent(null, decodedChars, false); + virtualFile.setWritable(virtualFile.getDelegate().isWritable()); + + + try { + List, TextRange>> + tokens = obtainHighlightTokensFromLexer(language, decodedChars, virtualFile, project, placeInfos); + psiFile.putUserData(InjectedLanguageUtil.HIGHLIGHT_TOKENS, tokens); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (RuntimeException e) { + throw new RuntimeException(exceptionContext("Obtaining tokens error", language, hostPsiFile, hostVirtualFile, hostDocument, placeInfos), e); + } + + return parsedNode; + } + + // find PsiLanguageInjectionHost in range "newInjectionHostRange" in the file which is almost "hostPsiFile" but where "oldRoot" replaced with "newRoot" + private static PsiLanguageInjectionHost findNewInjectionHost(@NotNull PsiFile hostPsiFile, + @NotNull ASTNode oldRoot, + @NotNull ASTNode newRoot, + @NotNull PsiLanguageInjectionHost oldInjectionHost, + @NotNull Segment newInjectionHostRange) { + TextRange oldRootRange = oldRoot.getTextRange(); + TextRange newRootRange = newRoot.getTextRange(); + PsiElement toLookIn; + int startToLook; + int endToLook; + if (newInjectionHostRange.getEndOffset() <= oldRootRange.getStartOffset()) { + // find left of the change + toLookIn = hostPsiFile; + startToLook = newInjectionHostRange.getStartOffset(); + endToLook = newInjectionHostRange.getEndOffset(); + } + else if (newInjectionHostRange.getStartOffset() >= oldRootRange.getStartOffset() + newRootRange.getLength()) { + // find right of the change + toLookIn = hostPsiFile; + startToLook = newInjectionHostRange.getStartOffset() + newRootRange.getLength() - oldRootRange.getLength(); + endToLook = newInjectionHostRange.getEndOffset() + newRootRange.getLength() - oldRootRange.getLength(); + } + else { + // inside + toLookIn = newRoot.getPsi(); + if (toLookIn instanceof PsiFile) { + FileViewProvider viewProvider = ((PsiFile)toLookIn).getViewProvider(); + toLookIn = ObjectUtils.notNull(viewProvider.getPsi(hostPsiFile.getLanguage()), viewProvider.getPsi(viewProvider.getBaseLanguage())); + } + startToLook = newInjectionHostRange.getStartOffset() - oldRootRange.getStartOffset(); + endToLook = newInjectionHostRange.getEndOffset() - oldRootRange.getStartOffset(); + } + Identikit.ByType kit = Identikit.fromPsi(oldInjectionHost, hostPsiFile.getLanguage()); + return (PsiLanguageInjectionHost)kit.findInside(toLookIn, startToLook, endToLook); + } + + + static boolean intersect(DocumentWindowImpl doc1, DocumentWindowImpl doc2) { Segment[] hostRanges1 = doc1.getHostRanges(); Segment[] hostRanges2 = doc2.getHostRanges(); // DocumentWindowImpl.getHostRanges() may theoretically return non-sorted ranges @@ -641,10 +769,9 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost private static List, TextRange>> obtainHighlightTokensFromLexer(@NotNull Language language, @NotNull CharSequence outChars, - @NotNull Place shreds, @NotNull VirtualFileWindow virtualFile, @NotNull Project project, - @NotNull List escapers) { + @NotNull List placeInfos) { SyntaxHighlighter syntaxHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(language, project, (VirtualFile)virtualFile); Lexer lexer = syntaxHighlighter.getHighlightingLexer(); lexer.start(outChars); @@ -662,14 +789,14 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost while (range != null && !range.isEmpty()) { if (range.getStartOffset() >= shredEndOffset) { hostNum++; - PsiLanguageInjectionHost.Shred shred = shreds.get(hostNum); - shredEndOffset = shred.getRange().getEndOffset(); + PlaceInfo info = placeInfos.get(hostNum); + shredEndOffset = info.rangeInDecodedPSI.getEndOffset(); prevHostEndOffset = range.getStartOffset(); - hostPtr = ((ShredImpl)shred).getSmartPointer(); - escaper = escapers.get(hostNum); - rangeInsideHost = shred.getRangeInsideHost(); - prefixLength = shred.getPrefix().length(); - suffixLength = shred.getSuffix().length(); + hostPtr = info.hostSmartPointer; + escaper = info.myEscaper; + rangeInsideHost = info.rangeInHostElement; + prefixLength = info.prefix.length(); + suffixLength = info.suffix.length(); } //in prefix/suffix or spills over to next fragment if (range.getStartOffset() < prevHostEndOffset + prefixLength) { diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/LeafPatcher.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/LeafPatcher.java index 171d4b1d1c54..32700bd5d884 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/LeafPatcher.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/LeafPatcher.java @@ -16,15 +16,14 @@ package com.intellij.psi.impl.source.tree.injected; +import com.intellij.lang.ASTNode; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.psi.LiteralTextEscaper; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiLanguageInjectionHost; -import com.intellij.psi.impl.source.tree.ForeignLeafPsiElement; -import com.intellij.psi.impl.source.tree.LeafElement; -import com.intellij.psi.impl.source.tree.RecursiveTreeElementWalkingVisitor; +import com.intellij.psi.impl.DebugUtil; +import com.intellij.psi.impl.source.tree.*; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; @@ -39,14 +38,15 @@ class LeafPatcher extends RecursiveTreeElementWalkingVisitor { private String hostText; private LiteralTextEscaper currentTextEscaper; private TextRange rangeInHost; - private final Place myShreds; - @NotNull private final List myEscapers; - final Map newTexts = new THashMap<>(); - final StringBuilder catLeafs = new StringBuilder(); + private final Map newTexts = new THashMap<>(); + @NotNull + private final List myPlaceInfos; + private final StringBuilder catLeafs; + private final StringBuilder tempLeafBuffer = new StringBuilder(); - LeafPatcher(@NotNull Place shreds, @NotNull List escapers) { - myShreds = shreds; - myEscapers = escapers; + LeafPatcher(@NotNull List placeInfos, int approxTextLength) { + myPlaceInfos = placeInfos; + catLeafs = new StringBuilder(approxTextLength); } @Override @@ -65,25 +65,24 @@ class LeafPatcher extends RecursiveTreeElementWalkingVisitor { private StringBuilder constructTextFromHostPSI(int startOffset, int endOffset) { boolean firstTimer = false; - PsiLanguageInjectionHost.Shred current = myShreds.get(shredNo); + PlaceInfo currentPlace = myPlaceInfos.get(shredNo); if (hostText == null) { - PsiLanguageInjectionHost host = current.getHost(); - hostText = host.getText(); - rangeInHost = current.getRangeInsideHost(); - currentTextEscaper = myEscapers.get(shredNo); + hostText = currentPlace.myHostText; + rangeInHost = currentPlace.getRelevantRangeInsideHost(); + currentTextEscaper = currentPlace.myEscaper; firstTimer = true; } - StringBuilder text = new StringBuilder(endOffset-startOffset); + StringBuilder text = tempLeafBuffer; + text.setLength(0); while (startOffset < endOffset) { - TextRange shredRange = current.getRange(); - String prefix = current.getPrefix(); + TextRange shredRange = currentPlace.rangeInDecodedPSI; + String prefix = currentPlace.prefix; if (startOffset >= shredRange.getEndOffset()) { - current = myShreds.get(++shredNo); - PsiLanguageInjectionHost host = current.getHost(); - hostText = host.getText(); - currentTextEscaper = myEscapers.get(shredNo); - rangeInHost = current.getRangeInsideHost(); + currentPlace = myPlaceInfos.get(++shredNo); + hostText = currentPlace.myHostText; + currentTextEscaper = currentPlace.myEscaper; + rangeInHost = currentPlace.getRelevantRangeInsideHost(); firstTimer = true; continue; } @@ -96,21 +95,19 @@ class LeafPatcher extends RecursiveTreeElementWalkingVisitor { continue; } - String suffix = current.getSuffix(); + String suffix = currentPlace.suffix; if (startOffset < shredRange.getEndOffset() - suffix.length()) { // inside host body, cut out from the host text - int startOffsetInHost = currentTextEscaper.getOffsetInHost( - startOffset - shredRange.getStartOffset() - prefix.length(), rangeInHost); + int startOffsetInHost = currentTextEscaper.getOffsetInHost(startOffset - shredRange.getStartOffset() - prefix.length(), rangeInHost); int endOffsetCut = Math.min(endOffset, shredRange.getEndOffset() - suffix.length()); - int endOffsetInHost = currentTextEscaper.getOffsetInHost( - endOffsetCut - shredRange.getStartOffset() - prefix.length(), rangeInHost); + int endOffsetInHost = currentTextEscaper.getOffsetInHost(endOffsetCut - shredRange.getStartOffset() - prefix.length(), rangeInHost); if (endOffsetInHost != -1) { - if (firstTimer ) text.append(hostText, rangeInHost.getStartOffset(), startOffsetInHost); + if (firstTimer) text.append(hostText, rangeInHost.getStartOffset(), startOffsetInHost); text.append(hostText, startOffsetInHost, endOffsetInHost); startOffset = endOffsetCut; // todo what about lastTimer? - continue; } + continue; } // inside suffix @@ -123,10 +120,36 @@ class LeafPatcher extends RecursiveTreeElementWalkingVisitor { } static final Key UNESCAPED_TEXT = Key.create("INJECTED_UNESCAPED_TEXT"); - private static void storeUnescapedTextFor(final LeafElement leaf, final String leafText) { + private static void storeUnescapedTextFor(@NotNull LeafElement leaf, @NotNull String leafText) { PsiElement psi = leaf.getPsi(); if (psi != null) { psi.putCopyableUserData(UNESCAPED_TEXT, leafText); } } + + void patch(@NotNull ASTNode parsedNode, @NotNull List placeInfos) { + ((TreeElement)parsedNode).acceptTree(this); + + assert ((TreeElement)parsedNode).textMatches(catLeafs) : + "Malformed PSI structure: leaf texts do not add up to the whole file text." + + "\nFile text (from tree) :'" + parsedNode.getText() + "'" + + "\nFile text (from PSI) :'" + parsedNode.getPsi().getText() + "'" + + "\nLeaf texts concatenated:'" + catLeafs + "';" + + "\nFile root: " + parsedNode + + "\nLanguage: " + parsedNode.getPsi().getLanguage() + + "\nHost file: " + placeInfos.get(0).host.getContainingFile().getVirtualFile(); + DebugUtil.startPsiModification("injection leaf patching"); + try { + for (Map.Entry entry : newTexts.entrySet()) { + LeafElement leaf = entry.getKey(); + String newText = entry.getValue(); + leaf.rawReplaceWithText(newText); + } + } + finally { + DebugUtil.finishPsiModification(); + } + + TreeUtil.clearCaches((TreeElement)parsedNode); + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/PlaceInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/PlaceInfo.java new file mode 100644 index 000000000000..9f9b1aaf7e93 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/PlaceInfo.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + */ +package com.intellij.psi.impl.source.tree.injected; + +import com.intellij.lang.injection.MultiHostRegistrar; +import com.intellij.openapi.util.ProperTextRange; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.LiteralTextEscaper; +import com.intellij.psi.PsiLanguageInjectionHost; +import com.intellij.psi.SmartPsiElementPointer; +import org.jetbrains.annotations.NotNull; + +/** + * stores injection registration info temporarily + * from the {@link MultiHostRegistrar#startInjecting(com.intellij.lang.Language)} call + * to the moment of {@link MultiHostRegistrar#doneInjecting()} + */ +class PlaceInfo { + @NotNull final String prefix; + @NotNull final String suffix; + @NotNull final PsiLanguageInjectionHost host; + @NotNull final TextRange registeredRangeInsideHost; + @NotNull final LiteralTextEscaper myEscaper; + @NotNull final String myHostText; + ProperTextRange rangeInDecodedPSI; + TextRange rangeInHostElement; + SmartPsiElementPointer hostSmartPointer; + + PlaceInfo(@NotNull String prefix, + @NotNull String suffix, + @NotNull PsiLanguageInjectionHost host, + @NotNull TextRange registeredRangeInsideHost) { + this.prefix = prefix; + this.suffix = suffix; + this.host = host; + this.registeredRangeInsideHost = registeredRangeInsideHost; + myEscaper = host.createLiteralTextEscaper(); + myHostText = host.getText(); + } + + @Override + public String toString() { + return "Shred " + + (prefix.isEmpty() ? "" : "prefix='"+prefix+"' ") + + (suffix.isEmpty() ? "" : "suffix='"+suffix+"' ") + + "in " + host + " with range " + host.getTextRange()+" "+ + "inside range " + registeredRangeInsideHost; + } + + TextRange getRelevantRangeInsideHost() { + return myEscaper.getRelevantTextRange().intersection(registeredRangeInsideHost); + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/ShredImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/ShredImpl.java index 35bc824b5ed2..7fd8de91f4d6 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/ShredImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/ShredImpl.java @@ -19,13 +19,14 @@ import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; +import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; class ShredImpl implements PsiLanguageInjectionHost.Shred { private final SmartPsiFileRange relevantRangeInHost; private final SmartPsiElementPointer hostElementPointer; - private final TextRange range; // range in (decoded) PSI + private final TextRange rangeInDecodedPSI; // range in (decoded) PSI private final String prefix; private final String suffix; private final boolean usePsiRange; @@ -35,13 +36,13 @@ class ShredImpl implements PsiLanguageInjectionHost.Shred { @NotNull SmartPsiElementPointer hostElementPointer, @NotNull String prefix, @NotNull String suffix, - @NotNull TextRange range, + @NotNull TextRange rangeInDecodedPSI, boolean usePsiRange, boolean isOneLine) { this.hostElementPointer = hostElementPointer; this.relevantRangeInHost = relevantRangeInHost; this.prefix = prefix; this.suffix = suffix; - this.range = range; + this.rangeInDecodedPSI = rangeInDecodedPSI; this.usePsiRange = usePsiRange; this.isOneLine = isOneLine; @@ -56,8 +57,20 @@ class ShredImpl implements PsiLanguageInjectionHost.Shred { assert host != null && host.isValid() : "no host: " + hostElementPointer; } + @NotNull ShredImpl withPsiRange() { - return new ShredImpl(relevantRangeInHost, hostElementPointer, prefix, suffix, range, true, isOneLine); + return new ShredImpl(relevantRangeInHost, hostElementPointer, prefix, suffix, rangeInDecodedPSI, true, isOneLine); + } + @NotNull + ShredImpl withRange(@NotNull TextRange rangeInDecodedPSI, @NotNull TextRange rangeInHostElementPSI) { + SmartPsiFileRange rangeMarker = relevantRangeInHost; + Segment oldRangeInHostElementPSI = calcRangeInsideHostElement(false); + if (!rangeInHostElementPSI.equals(TextRange.create(oldRangeInHostElementPSI))) { + SmartPointerManager pointerManager = SmartPointerManager.getInstance(rangeMarker.getProject()); + Segment hostElementRange = hostElementPointer.getRange(); + rangeMarker = ((SmartPointerManagerImpl)pointerManager).createSmartPsiFileRangePointer(rangeMarker.getContainingFile(), rangeInHostElementPSI.shiftRight(hostElementRange.getStartOffset()), true); + } + return new ShredImpl(rangeMarker, hostElementPointer, prefix, suffix, rangeInDecodedPSI, usePsiRange, isOneLine); } @NotNull @@ -74,19 +87,25 @@ class ShredImpl implements PsiLanguageInjectionHost.Shred { @Override @NotNull public TextRange getRangeInsideHost() { + return calcRangeInsideHostElement(true); + } + + @NotNull + private TextRange calcRangeInsideHostElement(boolean usePsiRange) { PsiLanguageInjectionHost host = getHost(); - Segment psiRange = relevantRangeInHost.getPsiRange(); + Segment psiRange = usePsiRange ? relevantRangeInHost.getPsiRange() : relevantRangeInHost.getRange(); TextRange textRange = psiRange == null ? null : TextRange.create(psiRange); if (host == null) { if (textRange != null) return textRange; - Segment fromSP = hostElementPointer.getPsiRange(); + Segment fromSP = usePsiRange ? hostElementPointer.getPsiRange() : hostElementPointer.getRange(); if (fromSP != null) return TextRange.create(fromSP); return new TextRange(0,0); } TextRange hostTextRange = host.getTextRange(); textRange = textRange == null ? null : textRange.intersection(hostTextRange); if (textRange == null) return new ProperTextRange(0, hostTextRange.getLength()); - return textRange.shiftRight(-hostTextRange.getStartOffset()); + + return textRange.shiftLeft(hostTextRange.getStartOffset()); } @Override @@ -96,8 +115,7 @@ class ShredImpl implements PsiLanguageInjectionHost.Shred { Segment hostRange = getHostRangeMarker(); return "Shred " + (host == null ? null : host.getTextRange()) + ": " + host + " In host range: " + (hostRange != null ? "(" + hostRange.getStartOffset() + "," + hostRange.getEndOffset() + ");" : "invalid;") + - " PSI range: " + - range; + " PSI range: " + rangeInDecodedPSI; } @Override @@ -119,15 +137,15 @@ class ShredImpl implements PsiLanguageInjectionHost.Shred { host.equals(shred.getHost()) && prefix.equals(shred.getPrefix()) && suffix.equals(shred.getSuffix()) && - range.equals(shred.getRange()) && + rangeInDecodedPSI.equals(shred.getRange()) && hostRangeMarker != null && hostRangeMarker2 != null && - TextRange.create(hostRangeMarker).equals(TextRange.create(hostRangeMarker2)); + TextRange.areSegmentsEqual(hostRangeMarker, hostRangeMarker2); } @Override public int hashCode() { - return range.hashCode(); + return rangeInDecodedPSI.hashCode(); } @Override @@ -143,7 +161,7 @@ class ShredImpl implements PsiLanguageInjectionHost.Shred { @NotNull @Override public TextRange getRange() { - return range; + return rangeInDecodedPSI; } @NotNull diff --git a/platform/platform-tests/testSrc/com/intellij/execution/impl/ConsoleViewImplTest.java b/platform/platform-tests/testSrc/com/intellij/execution/impl/ConsoleViewImplTest.java index 381e60e74eed..e57a09348f72 100644 --- a/platform/platform-tests/testSrc/com/intellij/execution/impl/ConsoleViewImplTest.java +++ b/platform/platform-tests/testSrc/com/intellij/execution/impl/ConsoleViewImplTest.java @@ -415,31 +415,25 @@ public class ConsoleViewImplTest extends LightPlatformTestCase { public void testInputFilter() { Disposer.dispose(myConsole); // have to re-init extensions List> registered = new ArrayList<>(); - ExtensionPoint extensionPoint = Extensions.getRootArea().getExtensionPoint(ConsoleInputFilterProvider.INPUT_FILTER_PROVIDERS); ConsoleInputFilterProvider crazyProvider = project -> new InputFilter[]{ (text, contentType) -> { registered.add(Pair.create(text, contentType)); return Collections.singletonList(Pair.create("+!" + text + "-!", contentType)); } }; - extensionPoint.registerExtension(crazyProvider); + PlatformTestUtil.registerExtension(ConsoleInputFilterProvider.INPUT_FILTER_PROVIDERS, crazyProvider, getTestRootDisposable()); myConsole = createConsole(); - try { - StringBuilder expectedText = new StringBuilder(); - List> expectedRegisteredTokens = new ArrayList<>(); - for (int i=0;i<25;i++) { - String chunk = i + ""; - myConsole.print(chunk, ConsoleViewContentType.USER_INPUT); - expectedText.append("+!" + i + "-!"); - expectedRegisteredTokens.add(Pair.create(chunk, ConsoleViewContentType.USER_INPUT)); - } - myConsole.flushDeferredText(); - myConsole.waitAllRequests(); - assertEquals(expectedText.toString(), myConsole.getText()); - assertEquals(expectedRegisteredTokens, registered); - } - finally { - extensionPoint.unregisterExtension(crazyProvider); + StringBuilder expectedText = new StringBuilder(); + List> expectedRegisteredTokens = new ArrayList<>(); + for (int i=0;i<25;i++) { + String chunk = i + ""; + myConsole.print(chunk, ConsoleViewContentType.USER_INPUT); + expectedText.append("+!" + i + "-!"); + expectedRegisteredTokens.add(Pair.create(chunk, ConsoleViewContentType.USER_INPUT)); } + myConsole.flushDeferredText(); + myConsole.waitAllRequests(); + assertEquals(expectedText.toString(), myConsole.getText()); + assertEquals(expectedRegisteredTokens, registered); } } diff --git a/platform/platform-tests/testSrc/com/intellij/usages/impl/UsageViewTest.java b/platform/platform-tests/testSrc/com/intellij/usages/impl/UsageViewTest.java index c5fce3ff91a9..9106a7eb0ddb 100644 --- a/platform/platform-tests/testSrc/com/intellij/usages/impl/UsageViewTest.java +++ b/platform/platform-tests/testSrc/com/intellij/usages/impl/UsageViewTest.java @@ -147,8 +147,8 @@ public class UsageViewTest extends LightPlatformCodeInsightFixtureTestCase { assertTrue(usageView.canPerformReRun()); - usageView.doReRun(); - Set usages = usageView.getUsages(); + UsageView newView = usageView.doReRun(); + Set usages = newView.getUsages(); assertEquals(2, usages.size()); } diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java index 596aa2af81ad..9405cb5dcbe3 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java @@ -1131,6 +1131,8 @@ public class PlatformTestUtil { cleanupAllProjects(); + UIUtil.dispatchAllInvocationEvents(); + ApplicationImpl application = (ApplicationImpl)getApplication(); System.out.println(application.writeActionStatistics()); System.out.println(ActionUtil.ActionPauses.STAT.statistics()); diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedCellReaders.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedCellReaders.kt index bd3718399c32..e38cf3f191c2 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedCellReaders.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedCellReaders.kt @@ -46,7 +46,7 @@ class ExtendedJTreeCellReader : BasicJTreeCellReader(), JTreeCellReader { override fun valueAt(tree: JTree, modelValue: Any?): String? { if (modelValue == null) return null - val cellRendererComponent = tree.cellRenderer.getTreeCellRendererComponent(tree, modelValue, false, false, false, 0, false) + val cellRendererComponent = tree.cellRenderer.getTreeCellRendererComponent(tree, modelValue, false, false, true, 0, false) return getValueWithCellRenderer(cellRendererComponent) } diff --git a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java index a2f3c4c0e971..16155badbedb 100644 --- a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java +++ b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java @@ -32,6 +32,8 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.Navigatable; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiInvalidElementAccessException; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.SmartPsiElementPointer; import com.intellij.psi.impl.PsiDocumentManagerBase; import com.intellij.ui.*; import com.intellij.ui.components.JBPanelWithEmptyText; @@ -155,6 +157,7 @@ public class UsageViewImpl implements UsageView { private final UsageViewTreeCellRenderer myUsageViewTreeCellRenderer; private Usage myOriginUsage; @Nullable private Runnable myRerunActivity; + private boolean myDisposeSmartPointersOnClose = true; public UsageViewImpl(@NotNull final Project project, @NotNull UsageViewPresentation presentation, @@ -1043,16 +1046,18 @@ public class UsageViewImpl implements UsageView { doReRun(); } + /** + * @return usage view which will be shown after re-run (either {@code this} if it knows how to re-run itself, or the new created one otherwise) + */ @SuppressWarnings("WeakerAccess") // used in rider - protected void doReRun() { + protected UsageView doReRun() { myChangesDetected = false; - if (myRerunActivity != null) { - myRerunActivity.run(); - } - else { - com.intellij.usages.UsageViewManager.getInstance(getProject()). + if (myRerunActivity == null) { + return com.intellij.usages.UsageViewManager.getInstance(getProject()). searchAndShowUsages(myTargets, myUsageSearcherFactory, true, false, myPresentation, null); } + myRerunActivity.run(); + return this; } private void reset() { @@ -1271,6 +1276,20 @@ public class UsageViewImpl implements UsageView { } myUpdateAlarm.cancelAllRequests(); } + if (myDisposeSmartPointersOnClose) { + disposeSmartPointers(); + } + } + + private void disposeSmartPointers() { + SmartPointerManager pointerManager = SmartPointerManager.getInstance(getProject()); + for (Usage usage : myUsageNodes.keySet()) { + if (usage instanceof UsageInfo2UsageAdapter) { + SmartPsiElementPointer pointer = ((UsageInfo2UsageAdapter)usage).getUsageInfo().getSmartPointer(); + pointerManager.removePointer(pointer); + } + } + myUsageNodes.clear(); } @Override @@ -1866,13 +1885,20 @@ public class UsageViewImpl implements UsageView { return; } + // can't dispose pointers because refactoring might want to re-use the usage infos from the preview + myDisposeSmartPointersOnClose = false; close(); - CommandProcessor.getInstance().executeCommand( - myProject, myProcessRunnable, - myCommandName, - null - ); + try { + CommandProcessor.getInstance().executeCommand( + myProject, myProcessRunnable, + myCommandName, + null + ); + } + finally { + disposeSmartPointers(); + } } } diff --git a/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java b/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java index 9961a190516e..c914e90a975e 100644 --- a/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java +++ b/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java @@ -24,7 +24,6 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.BitSet; import java.util.Iterator; /** @@ -36,13 +35,6 @@ import java.util.Iterator; * @author peter */ public class MinusculeMatcher implements Matcher { - private final ThreadLocal myMatchingState = new ThreadLocal() { - @Override - protected MatchingState initialValue() { - return new MatchingState(); - } - }; - private final char[] myPattern; private final String myHardSeparators; private final NameUtil.MatchingCaseSensitivity myOptions; @@ -258,25 +250,18 @@ public class MinusculeMatcher implements Matcher { return null; } - MatchingState state = myMatchingState.get(); - state.initializeState(isAscii, length); - try { - return matchWildcards(name, 0, 0, state); - } - finally { - state.releaseState(); - } + return matchWildcards(name, 0, 0, isAscii); } /** * After a wildcard (* or space), search for the first non-wildcard pattern character in the name starting from nameIndex - * and try to {@link #matchFragment(String, int, int, MinusculeMatcher.MatchingState)} for it. + * and try to {@link #matchFragment} for it. */ @Nullable private FList matchWildcards(@NotNull String name, int patternIndex, int nameIndex, - MatchingState matchingState) { + boolean isAsciiName) { if (nameIndex < 0) { return null; } @@ -284,7 +269,7 @@ public class MinusculeMatcher implements Matcher { if (patternIndex == myPattern.length) { return FList.emptyList(); } - return matchFragment(name, patternIndex, nameIndex, matchingState); + return matchFragment(name, patternIndex, nameIndex, isAsciiName); } do { @@ -304,12 +289,12 @@ public class MinusculeMatcher implements Matcher { return FList.emptyList(); } - FList ranges = matchFragment(name, patternIndex, nameIndex, matchingState); + FList ranges = matchFragment(name, patternIndex, nameIndex, isAsciiName); if (ranges != null) { return ranges; } - return matchSkippingWords(name, patternIndex, nameIndex, true, matchingState); + return matchSkippingWords(name, patternIndex, nameIndex, true, isAsciiName); } private static boolean isUpperCaseOrDigit(char p) { @@ -318,19 +303,19 @@ public class MinusculeMatcher implements Matcher { /** * Enumerates places in name that could be matched by the pattern at patternIndex position - * and invokes {@link #matchFragment(String, int, int, MinusculeMatcher.MatchingState)} at those candidate positions + * and invokes {@link #matchFragment} at those candidate positions */ @Nullable private FList matchSkippingWords(@NotNull String name, final int patternIndex, int nameIndex, boolean allowSpecialChars, - MatchingState matchingState) { + boolean isAsciiName) { boolean star = isPatternChar(patternIndex - 1, '*'); final char p = myPattern[patternIndex]; while (true) { int nextOccurrence = star || isWordSeparator[patternIndex] ? - indexOfIgnoreCase(name, nameIndex + 1, p, patternIndex, matchingState.isAsciiName) : + indexOfIgnoreCase(name, nameIndex + 1, p, patternIndex, isAsciiName) : indexOfWordStart(name, patternIndex, nameIndex); if (nextOccurrence < 0) { return null; @@ -350,7 +335,7 @@ public class MinusculeMatcher implements Matcher { NameUtil.isWordStart(name, nextOccurrence) || // accept uppercase matching lowercase if the whole prefix is uppercase and case sensitivity allows that !myHasHumps && myOptions != NameUtil.MatchingCaseSensitivity.ALL) { - FList ranges = matchFragment(name, patternIndex, nextOccurrence, matchingState); + FList ranges = matchFragment(name, patternIndex, nextOccurrence, isAsciiName); if (ranges != null) { return ranges; } @@ -368,26 +353,7 @@ public class MinusculeMatcher implements Matcher { private FList matchFragment(@NotNull String name, int patternIndex, int nameIndex, - MatchingState matchingState) { - if (matchingState.hasFailed(patternIndex, nameIndex)) { - return null; - } - - FList result = doMatchFragments(name, patternIndex, nameIndex, matchingState); - if (result == null) { - matchingState.registerFailure(patternIndex, nameIndex); - } - return result; - } - - /** - * Attempts to match an alphanumeric sequence of pattern (starting at patternIndex) - * to some continuous substring of name, starting from nameIndex. - */ - private FList doMatchFragments(String name, - int patternIndex, - int nameIndex, - MatchingState matchingState) { + boolean isAsciiName) { if (!isFirstCharMatching(name, nameIndex, patternIndex)) { return null; } @@ -402,7 +368,7 @@ public class MinusculeMatcher implements Matcher { patternIndex + i < myPattern.length && charEquals(myPattern[patternIndex+i], patternIndex+i, name.charAt(nameIndex + i), ignoreCase)) { if (isUppercasePatternVsLowercaseNameChar(name, patternIndex + i, nameIndex + i)) { - FList ranges = i < minFragment ? null : findUppercaseMatchFurther(name, patternIndex + i, nameIndex + i, matchingState); + FList ranges = i < minFragment ? null : findUppercaseMatchFurther(name, patternIndex + i, nameIndex + i, isAsciiName); if (ranges != null) { return prependRange(ranges, nameIndex, i); } @@ -423,8 +389,8 @@ public class MinusculeMatcher implements Matcher { // it may not succeed with the longest matching fragment, then try shorter matches while (i >= minFragment || isWildcard(patternIndex + i)) { FList ranges = isWildcard(patternIndex + i) ? - matchWildcards(name, patternIndex + i, nameIndex + i, matchingState) : - matchSkippingWords(name, patternIndex + i, nameIndex + i, false, matchingState); + matchWildcards(name, patternIndex + i, nameIndex + i, isAsciiName) : + matchSkippingWords(name, patternIndex + i, nameIndex + i, false, isAsciiName); if (ranges != null) { return prependRange(ranges, nameIndex, i); } @@ -440,9 +406,9 @@ public class MinusculeMatcher implements Matcher { private FList findUppercaseMatchFurther(String name, int patternIndex, int nameIndex, - MatchingState matchingState) { + boolean isAsciiName) { int nextWordStart = indexOfWordStart(name, patternIndex, nameIndex); - return matchWildcards(name, patternIndex, nextWordStart, matchingState); + return matchWildcards(name, patternIndex, nextWordStart, isAsciiName); } private boolean shouldProhibitCaseMismatch(String name, int patternIndex, int nameIndex) { @@ -539,32 +505,4 @@ public class MinusculeMatcher implements Matcher { return "MinusculeMatcher{myPattern=" + new String(myPattern) + ", myOptions=" + myOptions + '}'; } - private static class MatchingState { - private boolean myBusy; - private int myNameLength; - private boolean isAsciiName; - private final BitSet myTable = new BitSet(); - - void initializeState(boolean isAscii, int length) { - assert !myBusy; - myBusy = true; - myNameLength = length; - isAsciiName = isAscii; - myTable.clear(); - } - - void releaseState() { - assert myBusy; - myBusy = false; - } - - void registerFailure(int patternIndex, int nameIndex) { - myTable.set(patternIndex * myNameLength + nameIndex); - } - - boolean hasFailed(int patternIndex, int nameIndex) { - return myTable.get(patternIndex * myNameLength + nameIndex); - } - - } }