incremental reparse of injected fragments

This commit is contained in:
Alexey Kudravtsev
2018-01-11 16:28:21 +03:00
parent 7334670616
commit da89f80d86
20 changed files with 796 additions and 371 deletions

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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<Runnable, Object> 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<Processor<Document>> finishProcessors = new SmartList<>();
final List<BooleanRunnable> finishProcessors = new SmartList<>();
List<BooleanRunnable> reparseInjectedProcessors = new SmartList<>();
Ref<ProperTextRange> 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<Document> 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<Processor<Document>> finishProcessors,
@NotNull ProperTextRange changedRange) {
private Runnable createFinishCommitInEDTRunnable(@NotNull final CommitTask task,
final boolean synchronously,
@NotNull List<BooleanRunnable> finishProcessors,
@NotNull List<BooleanRunnable> 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<Document> 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<Document> doCommit(@NotNull final CommitTask task,
@NotNull final PsiFile file,
@NotNull final FileASTNode oldFileNode,
@NotNull Ref<ProperTextRange> outChangedRange) {
public BooleanRunnable doCommit(@NotNull final CommitTask task,
@NotNull final PsiFile file,
@NotNull final FileASTNode oldFileNode,
@NotNull ProperTextRange changedPsiRange,
@NotNull List<BooleanRunnable> 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<DiffLog, ASTNode, ASTNode> 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<BooleanRunnable> 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()) {

View File

@@ -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<Processor<Document>> finishProcessors,
@NotNull List<BooleanRunnable> finishProcessors,
@NotNull List<BooleanRunnable> 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<Processor<Document>> finishProcessors,
final boolean synchronously) {
return finishCommitInWriteAction(document, finishProcessors, synchronously, false);
}
protected boolean finishCommitInWriteAction(@NotNull final Document document,
@NotNull final List<Processor<Document>> finishProcessors,
@NotNull List<BooleanRunnable> finishProcessors,
@NotNull List<BooleanRunnable> 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<Processor<Document>> finishProcessors,
boolean synchronously, @Nullable VirtualFile virtualFile, @NotNull FileViewProvider viewProvider) {
for (Processor<Document> finishRunnable : finishProcessors) {
boolean success = finishRunnable.process(document);
@NotNull List<BooleanRunnable> finishProcessors,
@NotNull List<BooleanRunnable> 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<DocumentEvent> myEvents = ContainerUtil.newArrayList();
private final ConcurrentMap<DocumentWindow, DocumentWindow> 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<BooleanRunnable> reparseChangedInjectedFragments(@NotNull Document hostDocument,
@NotNull PsiFile hostPsiFile,
@NotNull TextRange range,
@NotNull ProgressIndicator indicator,
@NotNull ASTNode oldRoot,
@NotNull ASTNode newRoot) {
return Collections.emptyList();
}
}

View File

@@ -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);
}

View File

@@ -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<DocumentEvent> 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<SelfElementInfo> 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<DocumentEvent> 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<SelfElementInfo> mySortedInfos;
private final ManualRangeMarker[] myMarkers;
public UpdatedRanges(int eventCount,
FrozenDocument resultDocument,
List<SelfElementInfo> sortedInfos, ManualRangeMarker[] markers) {
UpdatedRanges(int eventCount,
@NotNull FrozenDocument resultDocument,
@NotNull List<SelfElementInfo> sortedInfos,
@NotNull ManualRangeMarker[] markers) {
myEventCount = eventCount;
myResultDocument = resultDocument;
mySortedInfos = sortedInfos;

View File

@@ -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<DocumentEvent> 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;
}
}

View File

@@ -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<SmartPsiElementPointerImpl> ourQueue = new ReferenceQueue<>();
@@ -161,6 +164,10 @@ class SmartPointerTracker {
synchronized Segment getUpdatedRange(SelfElementInfo info, FrozenDocument document, List<DocumentEvent> events) {
return markerCache.getUpdatedRange(info, document, events);
}
@Nullable
synchronized Segment getUpdatedRange(@NotNull PsiFile containingFile, @NotNull Segment segment, boolean isSegmentGreedy, @NotNull FrozenDocument frozen, @NotNull List<DocumentEvent> events) {
return MarkerCache.getUpdatedRange(containingFile, segment, isSegmentGreedy, frozen, events);
}
synchronized void switchStubToAst(AnchorElementInfo info, PsiElement element) {
info.switchToTreeRange(element);

View File

@@ -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<ASTNode> 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<DiffLog, ASTNode, ASTNode> 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<ASTNode> reparseableRoots = findReparseableRoots(fileImpl, oldFileNode, changedPsiRange, newFileText);
DiffLog diffLog;
ASTNode oldRoot;
ASTNode newRoot;
if (reparseableRoots == null) {
Pair.NonNull<DiffLog, FileElement> 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<ASTNode> findReparseableRoots(@NotNull PsiFileImpl file,
@NotNull FileASTNode oldFileNode,
@NotNull TextRange changedPsiRange,
@NotNull CharSequence newFileText) {
public static Couple<ASTNode> 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<DiffLog, FileElement> 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;

View File

@@ -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 &&

View File

@@ -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));
}
}

View File

@@ -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<Processor<Document>> finishProcessors,
boolean synchronously,
@NotNull List<BooleanRunnable> finishProcessors,
List<BooleanRunnable> 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<BooleanRunnable> reparseChangedInjectedFragments(@NotNull Document hostDocument,
@NotNull PsiFile hostPsiFile,
@NotNull TextRange hostChangedRange,
@NotNull ProgressIndicator indicator,
@NotNull ASTNode oldRoot,
@NotNull ASTNode newRoot) {
List<DocumentWindow> changedInjected = InjectedLanguageManager.getInstance(myProject).getCachedInjectedDocumentsInRange(hostPsiFile, hostChangedRange);
if (changedInjected.isEmpty()) return Collections.emptyList();
FileViewProvider hostViewProvider = hostPsiFile.getViewProvider();
List<DocumentWindow> 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<BooleanRunnable> 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() {

View File

@@ -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;
}

View File

@@ -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<EditorWindowImpl> 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();

View File

@@ -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<TextRange> result, int start, int length) {

View File

@@ -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<List<Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, TextRange>>> HIGHLIGHT_TOKENS = Key.create("HIGHLIGHT_TOKENS");
public static final Key<IElementType> INJECTED_FRAGMENT_TYPE = Key.create("INJECTED_FRAGMENT_TYPE");
public static final Key<Boolean> 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;
}
}

View File

@@ -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<ShredImpl, LiteralTextEscaper> 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<? extends PsiLanguageInjectionHost> textEscaper = host.createLiteralTextEscaper();
outChars.append(info.prefix);
LiteralTextEscaper<? extends PsiLanguageInjectionHost> 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<PlaceInfo> 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<PlaceInfo> placeInfos,
@NotNull PsiElement contextElement) {
@NotNull List<PlaceInfo> 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<LiteralTextEscaper> escapers = new ArrayList<>(placeInfos.size());
for (PlaceInfo info : placeInfos) {
Pair.NonNull<ShredImpl, LiteralTextEscaper> 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<PsiLanguageInjectionHost> 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<Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, 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<PlaceInfo> 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<ShredImpl, LiteralTextEscaper> 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<PsiLanguageInjectionHost> pointer) {
@NotNull SmartPsiElementPointer<PsiLanguageInjectionHost> 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<PlaceInfo> 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<ASTNode> 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<LiteralTextEscaper> 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<LeafElement, String> 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:
* <pre>
* - 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.
* </pre>
*/
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<PlaceInfo> placeInfos = new SmartList<>();
StringBuilder chars = new StringBuilder();
for (PsiLanguageInjectionHost.Shred shred : oldPlace) {
PsiLanguageInjectionHost oldHost = shred.getHost();
if (oldHost == null) return null;
SmartPsiElementPointer<PsiLanguageInjectionHost> 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<PsiLanguageInjectionHost> 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<PsiLanguageInjectionHost> 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<PlaceInfo> 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<Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, 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<Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, TextRange>>
obtainHighlightTokensFromLexer(@NotNull Language language,
@NotNull CharSequence outChars,
@NotNull Place shreds,
@NotNull VirtualFileWindow virtualFile,
@NotNull Project project,
@NotNull List<LiteralTextEscaper> escapers) {
@NotNull List<PlaceInfo> 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) {

View File

@@ -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<LiteralTextEscaper> myEscapers;
final Map<LeafElement, String> newTexts = new THashMap<>();
final StringBuilder catLeafs = new StringBuilder();
private final Map<LeafElement, String> newTexts = new THashMap<>();
@NotNull
private final List<PlaceInfo> myPlaceInfos;
private final StringBuilder catLeafs;
private final StringBuilder tempLeafBuffer = new StringBuilder();
LeafPatcher(@NotNull Place shreds, @NotNull List<LiteralTextEscaper> escapers) {
myShreds = shreds;
myEscapers = escapers;
LeafPatcher(@NotNull List<PlaceInfo> 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<String> 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<PlaceInfo> 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<LeafElement, String> entry : newTexts.entrySet()) {
LeafElement leaf = entry.getKey();
String newText = entry.getValue();
leaf.rawReplaceWithText(newText);
}
}
finally {
DebugUtil.finishPsiModification();
}
TreeUtil.clearCaches((TreeElement)parsedNode);
}
}

View File

@@ -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<? extends PsiLanguageInjectionHost> myEscaper;
@NotNull final String myHostText;
ProperTextRange rangeInDecodedPSI;
TextRange rangeInHostElement;
SmartPsiElementPointer<PsiLanguageInjectionHost> 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);
}
}

View File

@@ -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<PsiLanguageInjectionHost> 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<PsiLanguageInjectionHost> 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