mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
incremental reparse of injected fragments
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user