mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
refactored injections to avoid caching non-thread-safe InjectionRegistrar
This commit is contained in:
+45
-55
@@ -62,55 +62,23 @@ public class JavaConcatenationInjectorManager extends SimpleModificationTracker
|
||||
return ServiceManager.getService(project, JavaConcatenationInjectorManager.class);
|
||||
}
|
||||
|
||||
private static Pair<PsiElement,PsiElement[]> computeAnchorAndOperandsImpl(@NotNull PsiElement context) {
|
||||
PsiElement element = context;
|
||||
PsiElement parent = context.getParent();
|
||||
while (parent instanceof PsiPolyadicExpression && ((PsiPolyadicExpression)parent).getOperationTokenType() == JavaTokenType.PLUS
|
||||
|| parent instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)parent).getOperationTokenType() == JavaTokenType.PLUSEQ
|
||||
|| parent instanceof PsiConditionalExpression && ((PsiConditionalExpression)parent).getCondition() != element
|
||||
|| parent instanceof PsiTypeCastExpression
|
||||
|| parent instanceof PsiParenthesizedExpression) {
|
||||
element = parent;
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
PsiElement[] operands;
|
||||
PsiElement anchor;
|
||||
if (element instanceof PsiPolyadicExpression) {
|
||||
operands = ((PsiPolyadicExpression)element).getOperands();
|
||||
anchor = element;
|
||||
}
|
||||
else if (element instanceof PsiAssignmentExpression) {
|
||||
PsiExpression rExpression = ((PsiAssignmentExpression)element).getRExpression();
|
||||
operands = new PsiElement[]{rExpression == null ? element : rExpression};
|
||||
anchor = element;
|
||||
}
|
||||
else {
|
||||
operands = new PsiElement[]{context};
|
||||
anchor = context;
|
||||
}
|
||||
|
||||
return Pair.create(anchor, operands);
|
||||
}
|
||||
|
||||
private static InjectionRegistrarImpl doCompute(@NotNull PsiFile containingFile,
|
||||
@NotNull Project project,
|
||||
@NotNull PsiElement anchor,
|
||||
@NotNull PsiElement[] operands) {
|
||||
private static InjectionResult doCompute(@NotNull PsiFile containingFile,
|
||||
@NotNull Project project,
|
||||
@NotNull PsiElement anchor,
|
||||
@NotNull PsiElement[] operands) {
|
||||
InjectionRegistrarImpl registrar = new InjectionRegistrarImpl(project, containingFile, anchor);
|
||||
InjectionResult result = null;
|
||||
JavaConcatenationInjectorManager concatenationInjectorManager = getInstance(project);
|
||||
for (ConcatenationAwareInjector concatenationInjector : concatenationInjectorManager.myConcatenationInjectors) {
|
||||
concatenationInjector.getLanguagesToInject(registrar, operands);
|
||||
if (registrar.getResult() != null) break;
|
||||
result = registrar.getInjectedResult();
|
||||
if (result != null) break;
|
||||
}
|
||||
|
||||
if (registrar.getResult() == null) {
|
||||
registrar = null;
|
||||
}
|
||||
return registrar;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final Key<ParameterizedCachedValue<InjectionRegistrarImpl, PsiElement>> INJECTED_PSI_IN_CONCATENATION = Key.create("INJECTED_PSI_IN_CONCATENATION");
|
||||
private static final Key<ParameterizedCachedValue<InjectionResult, PsiElement>> INJECTED_PSI_IN_CONCATENATION = Key.create("INJECTED_PSI_IN_CONCATENATION");
|
||||
private static final Key<Integer> NO_CONCAT_INJECTION_TIMESTAMP = Key.create("NO_CONCAT_INJECTION_TIMESTAMP");
|
||||
|
||||
public abstract static class BaseConcatenation2InjectorAdapter implements MultiHostInjector {
|
||||
@@ -132,8 +100,8 @@ public class JavaConcatenationInjectorManager extends SimpleModificationTracker
|
||||
PsiElement[] operands = pair.second;
|
||||
Integer noInjectionTimestamp = anchor.getUserData(NO_CONCAT_INJECTION_TIMESTAMP);
|
||||
|
||||
InjectionRegistrarImpl result;
|
||||
ParameterizedCachedValue<InjectionRegistrarImpl, PsiElement> data = null;
|
||||
InjectionResult result;
|
||||
ParameterizedCachedValue<InjectionResult, PsiElement> data = null;
|
||||
if (operands.length == 0 || noInjectionTimestamp != null && noInjectionTimestamp == modificationCount) {
|
||||
result = null;
|
||||
}
|
||||
@@ -145,26 +113,21 @@ public class JavaConcatenationInjectorManager extends SimpleModificationTracker
|
||||
result = doCompute(containingFile, project, anchor, operands);
|
||||
}
|
||||
}
|
||||
if (result != null && result.getResult() != null) {
|
||||
for (Pair<Place, PsiFile> p : result.getResult()) {
|
||||
Place place = p.getFirst();
|
||||
if (place.isValid()) {
|
||||
((InjectionRegistrarImpl)registrar).addToResults(place, p.second, result);
|
||||
}
|
||||
}
|
||||
if (result != null) {
|
||||
((InjectionRegistrarImpl)registrar).addToResults(result);
|
||||
|
||||
if (data == null) {
|
||||
CachedValueProvider.Result<InjectionRegistrarImpl> cachedResult =
|
||||
CachedValueProvider.Result<InjectionResult> cachedResult =
|
||||
CachedValueProvider.Result.create(result, PsiModificationTracker.MODIFICATION_COUNT, myManager);
|
||||
data = CachedValuesManager.getManager(project).createParameterizedCachedValue(
|
||||
context1 -> {
|
||||
PsiFile containingFile1 = context1.getContainingFile();
|
||||
Project project1 = containingFile1.getProject();
|
||||
Pair<PsiElement, PsiElement[]> pair1 = computeAnchorAndOperands(context1);
|
||||
InjectionRegistrarImpl registrar1 = pair1.second.length == 0 ? null : doCompute(containingFile1, project1, pair1.first, pair1.second);
|
||||
return registrar1 == null ? null : CachedValueProvider.Result.create(registrar1, PsiModificationTracker.MODIFICATION_COUNT, myManager);
|
||||
InjectionResult result1 = pair1.second.length == 0 ? null : doCompute(containingFile1, project1, pair1.first, pair1.second);
|
||||
return result1 == null ? null : CachedValueProvider.Result.create(result1, PsiModificationTracker.MODIFICATION_COUNT, myManager);
|
||||
}, false);
|
||||
((PsiParameterizedCachedValue<InjectionRegistrarImpl, PsiElement>)data).setValue(cachedResult);
|
||||
((PsiParameterizedCachedValue<InjectionResult, PsiElement>)data).setValue(cachedResult);
|
||||
|
||||
anchor.putUserData(INJECTED_PSI_IN_CONCATENATION, data);
|
||||
if (anchor.getUserData(NO_CONCAT_INJECTION_TIMESTAMP) != null) {
|
||||
@@ -191,7 +154,34 @@ public class JavaConcatenationInjectorManager extends SimpleModificationTracker
|
||||
|
||||
@Override
|
||||
public Pair<PsiElement, PsiElement[]> computeAnchorAndOperands(@NotNull PsiElement context) {
|
||||
return computeAnchorAndOperandsImpl(context);
|
||||
PsiElement element = context;
|
||||
PsiElement parent = context.getParent();
|
||||
while (parent instanceof PsiPolyadicExpression && ((PsiPolyadicExpression)parent).getOperationTokenType() == JavaTokenType.PLUS
|
||||
|| parent instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)parent).getOperationTokenType() == JavaTokenType.PLUSEQ
|
||||
|| parent instanceof PsiConditionalExpression && ((PsiConditionalExpression)parent).getCondition() != element
|
||||
|| parent instanceof PsiTypeCastExpression
|
||||
|| parent instanceof PsiParenthesizedExpression) {
|
||||
element = parent;
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
PsiElement[] operands;
|
||||
PsiElement anchor;
|
||||
if (element instanceof PsiPolyadicExpression) {
|
||||
operands = ((PsiPolyadicExpression)element).getOperands();
|
||||
anchor = element;
|
||||
}
|
||||
else if (element instanceof PsiAssignmentExpression) {
|
||||
PsiExpression rExpression = ((PsiAssignmentExpression)element).getRExpression();
|
||||
operands = new PsiElement[]{rExpression == null ? element : rExpression};
|
||||
anchor = element;
|
||||
}
|
||||
else {
|
||||
operands = new PsiElement[]{context};
|
||||
anchor = context;
|
||||
}
|
||||
|
||||
return Pair.create(anchor, operands);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+18
-19
@@ -35,11 +35,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author cdr
|
||||
*/
|
||||
public class InjectedFileViewProvider extends SingleRootFileViewProvider implements FreeThreadedFileViewProvider {
|
||||
private Project myProject;
|
||||
private final Object myLock = new Object();
|
||||
private final DocumentWindowImpl myDocumentWindow;
|
||||
private static final ThreadLocal<Boolean> disabledTemporarily = ThreadLocal.withInitial(() -> false);
|
||||
@@ -51,7 +47,6 @@ public class InjectedFileViewProvider extends SingleRootFileViewProvider impleme
|
||||
@NotNull Language language) {
|
||||
super(psiManager, (VirtualFile)virtualFile, true, language);
|
||||
myDocumentWindow = documentWindow;
|
||||
myProject = documentWindow.getShreds().getHostPointer().getProject();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,7 +96,7 @@ public class InjectedFileViewProvider extends SingleRootFileViewProvider impleme
|
||||
for (PsiElement current = hostElementCopy; current != null && current != hostPsiFileCopy; current = current.getParent()) {
|
||||
current.putUserData(LANGUAGE_FOR_INJECTED_COPY_KEY, language);
|
||||
try {
|
||||
InjectedLanguageUtil.enumerate(current, hostPsiFileCopy, false, visitor);
|
||||
InjectedLanguageManager.getInstance(hostPsiFileCopy.getProject()).enumerateEx(current, hostPsiFileCopy, false, visitor);
|
||||
}
|
||||
finally {
|
||||
current.putUserData(LANGUAGE_FOR_INJECTED_COPY_KEY, null);
|
||||
@@ -113,21 +108,23 @@ public class InjectedFileViewProvider extends SingleRootFileViewProvider impleme
|
||||
|
||||
static Key<Language> LANGUAGE_FOR_INJECTED_COPY_KEY = Key.create("LANGUAGE_FOR_INJECTED_COPY_KEY");
|
||||
// returns true if shreds were set, false if old ones were reused
|
||||
boolean setShreds(@NotNull Place newShreds, @NotNull Project project) {
|
||||
boolean setShreds(@NotNull Place newShreds) {
|
||||
synchronized (myLock) {
|
||||
myProject = project;
|
||||
Place oldShreds = myDocumentWindow.getShreds();
|
||||
// try to reuse shreds, otherwise there are too many range markers disposals/re-creations
|
||||
if (same(oldShreds, newShreds)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
myDocumentWindow.setShreds(newShreds);
|
||||
return true;
|
||||
}
|
||||
myDocumentWindow.setShreds(newShreds);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Project getProject() {
|
||||
return getManager().getProject();
|
||||
}
|
||||
|
||||
private static boolean same(Place oldShreds, Place newShreds) {
|
||||
if (oldShreds == newShreds) return true;
|
||||
if (oldShreds.size() != newShreds.size()) return false;
|
||||
@@ -144,9 +141,7 @@ public class InjectedFileViewProvider extends SingleRootFileViewProvider impleme
|
||||
}
|
||||
|
||||
boolean isDisposed() {
|
||||
synchronized (myLock) {
|
||||
return myProject.isDisposed();
|
||||
}
|
||||
return getProject().isDisposed();
|
||||
}
|
||||
|
||||
Place getShreds() {
|
||||
@@ -161,7 +156,6 @@ public class InjectedFileViewProvider extends SingleRootFileViewProvider impleme
|
||||
|
||||
@Override
|
||||
public boolean isEventSystemEnabled() {
|
||||
if (myLock == null) return true; // hack to avoid NPE when this method called from super class constructor
|
||||
return !disabledTemporarily.get();
|
||||
}
|
||||
|
||||
@@ -188,8 +182,13 @@ public class InjectedFileViewProvider extends SingleRootFileViewProvider impleme
|
||||
return "Injected file '"+getVirtualFile().getName()+"' " + (isValid() ? "" : " invalid") + (isPhysical() ? "" : " nonphysical");
|
||||
}
|
||||
|
||||
void setPatchingLeaves(boolean patchingLeaves) {
|
||||
myPatchingLeaves = patchingLeaves;
|
||||
void doNotInterruptMeWhileImPatchingLeaves(@NotNull Runnable runnable) {
|
||||
myPatchingLeaves = true;
|
||||
try {
|
||||
runnable.run();
|
||||
}
|
||||
finally {
|
||||
myPatchingLeaves = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -418,8 +418,9 @@ public class InjectedLanguageManagerImpl extends InjectedLanguageManager impleme
|
||||
Place shreds = ((DocumentWindowImpl)document).getShreds();
|
||||
Project project = shreds.getHostPointer().getProject();
|
||||
DocumentEx delegate = ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(project)).getLastCommittedDocument(document.getDelegate());
|
||||
Place place = new Place(ContainerUtil.map(shreds, shred -> ((ShredImpl) shred).withPsiRange()));
|
||||
return new DocumentWindowImpl(delegate, ((DocumentWindowImpl)document).isOneLine(), place);
|
||||
Place place = new Place();
|
||||
place.addAll(ContainerUtil.map(shreds, shred -> ((ShredImpl)shred).withPsiRange()));
|
||||
return new DocumentWindowImpl(delegate, document.isOneLine(), place);
|
||||
}
|
||||
|
||||
private static int appendRange(@NotNull List<TextRange> result, int start, int length) {
|
||||
|
||||
+57
-42
@@ -39,6 +39,7 @@ import com.intellij.psi.impl.DebugUtil;
|
||||
import com.intellij.psi.impl.PsiManagerEx;
|
||||
import com.intellij.psi.impl.PsiParameterizedCachedValue;
|
||||
import com.intellij.psi.impl.source.DummyHolder;
|
||||
import com.intellij.psi.injection.ReferenceInjector;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.testFramework.LightVirtualFile;
|
||||
@@ -151,25 +152,10 @@ public class InjectedLanguageUtil {
|
||||
containingFile = host.getContainingFile();
|
||||
}
|
||||
|
||||
InjectionRegistrarImpl registrar = probeElementsUp(host, containingFile, probeUp);
|
||||
if (registrar == null) {
|
||||
// no injections found
|
||||
return true;
|
||||
}
|
||||
List<Pair<Place, PsiFile>> places = registrar.getResult();
|
||||
for (Pair<Place, PsiFile> pair : places) {
|
||||
if (visitor instanceof InjectedReferenceVisitor) {
|
||||
if (registrar.getReferenceInjector() != null) {
|
||||
((InjectedReferenceVisitor)visitor).visitInjectedReference(registrar.getReferenceInjector(), pair.first);
|
||||
}
|
||||
}
|
||||
else if (pair.second != null) {
|
||||
visitor.visit(pair.second, pair.first);
|
||||
}
|
||||
}
|
||||
probeElementsUp(host, containingFile, probeUp, visitor);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invocation of this method on uncommitted {@code file} can lead to unexpected results, including throwing an exception!
|
||||
*/
|
||||
@@ -293,7 +279,7 @@ public class InjectedLanguageUtil {
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiFile findInjectedPsiNoCommit(@NotNull PsiFile host, int offset) {
|
||||
PsiElement injected = findInjectedElementNoCommit(host, offset);
|
||||
PsiElement injected = InjectedLanguageManager.getInstance(host.getProject()).findInjectedElementAt(host, offset);
|
||||
return injected == null ? null : injected.getContainingFile();
|
||||
}
|
||||
|
||||
@@ -320,40 +306,57 @@ public class InjectedLanguageUtil {
|
||||
}
|
||||
|
||||
private static final InjectedPsiCachedValueProvider INJECTED_PSI_PROVIDER = new InjectedPsiCachedValueProvider();
|
||||
private static final Key<ParameterizedCachedValue<InjectionRegistrarImpl, PsiElement>> INJECTED_PSI = Key.create("INJECTED_PSI");
|
||||
// list of injected fragments injected into this psi element (can be several if some crazy injector calls startInjecting()/doneInjecting()/startInjecting()/doneInjecting())
|
||||
private static final Key<ParameterizedCachedValue<InjectionResult, PsiElement>> INJECTED_PSI = Key.create("INJECTED_PSI");
|
||||
|
||||
private static InjectionRegistrarImpl probeElementsUp(@NotNull PsiElement element, @NotNull PsiFile hostPsiFile, boolean probeUp) {
|
||||
private static void probeElementsUp(@NotNull PsiElement element,
|
||||
@NotNull PsiFile hostPsiFile,
|
||||
boolean probeUp,
|
||||
@NotNull PsiLanguageInjectionHost.InjectedPsiVisitor visitor) {
|
||||
PsiManager psiManager = hostPsiFile.getManager();
|
||||
final Project project = psiManager.getProject();
|
||||
InjectedLanguageManagerImpl injectedManager = InjectedLanguageManagerImpl.getInstanceImpl(project);
|
||||
InjectionRegistrarImpl registrar = null;
|
||||
InjectionResult result = null;
|
||||
PsiElement current = element;
|
||||
nextParent:
|
||||
|
||||
while (current != null && current != hostPsiFile && !(current instanceof PsiDirectory)) {
|
||||
ProgressManager.checkCanceled();
|
||||
if ("EL".equals(current.getLanguage().getID())) break;
|
||||
ParameterizedCachedValue<InjectionRegistrarImpl, PsiElement> data = current.getUserData(INJECTED_PSI);
|
||||
if (data == null || (registrar = data.getValue(current)) == null || !registrar.isValid()) {
|
||||
registrar = InjectedPsiCachedValueProvider.doCompute(current, injectedManager, project, hostPsiFile);
|
||||
ParameterizedCachedValue<InjectionResult, PsiElement> data = current.getUserData(INJECTED_PSI);
|
||||
if (data == null || (result = data.getValue(current)) == null || !result.isValid()) {
|
||||
result = InjectedPsiCachedValueProvider.doCompute(current, injectedManager, project, hostPsiFile);
|
||||
}
|
||||
|
||||
current = current.getParent(); // cache no injection for current
|
||||
|
||||
if (registrar != null) {
|
||||
List<Pair<Place, PsiFile>> places = registrar.getResult();
|
||||
// check that injections found intersect with queried element
|
||||
TextRange elementRange = element.getTextRange();
|
||||
for (Pair<Place, PsiFile> pair : places) {
|
||||
Place place = pair.first;
|
||||
if (place.isValid()) {
|
||||
for (PsiLanguageInjectionHost.Shred shred : place) {
|
||||
PsiLanguageInjectionHost hostElement = shred.getHost();
|
||||
if (hostElement != null && hostElement.getTextRange().intersects(elementRange)) {
|
||||
break nextParent;
|
||||
if (result != null) {
|
||||
if (result.files != null) {
|
||||
for (PsiFile injectedPsiFile : result.files) {
|
||||
Place place = getShreds(injectedPsiFile);
|
||||
if (place.isValid()) {
|
||||
// check that injections found intersect with queried element
|
||||
boolean intersects = intersects(element, place);
|
||||
if (intersects) {
|
||||
visitor.visit(injectedPsiFile, place);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.references != null && visitor instanceof InjectedReferenceVisitor) {
|
||||
InjectedReferenceVisitor refVisitor = (InjectedReferenceVisitor)visitor;
|
||||
for (Pair<ReferenceInjector, Place> pair : result.references) {
|
||||
Place place = pair.getSecond();
|
||||
if (place.isValid()) {
|
||||
// check that injections found intersect with queried element
|
||||
boolean intersects = intersects(element, place);
|
||||
if (intersects) {
|
||||
ReferenceInjector injector = pair.getFirst();
|
||||
refVisitor.visitInjectedReference(injector, place);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break; // found injection, stop
|
||||
}
|
||||
if (!probeUp) {
|
||||
break;
|
||||
@@ -364,22 +367,34 @@ public class InjectedLanguageUtil {
|
||||
// cache only if we walked all parents
|
||||
for (PsiElement e = element; e != current && e != null && e != hostPsiFile; e = e.getParent()) {
|
||||
ProgressManager.checkCanceled();
|
||||
if (registrar == null) {
|
||||
if (result == null) {
|
||||
e.putUserData(INJECTED_PSI, null);
|
||||
}
|
||||
else {
|
||||
ParameterizedCachedValue<InjectionRegistrarImpl, PsiElement> cachedValue =
|
||||
PsiParameterizedCachedValue<InjectionResult, PsiElement> cachedValue =
|
||||
(PsiParameterizedCachedValue<InjectionResult, PsiElement>)
|
||||
CachedValuesManager.getManager(project).createParameterizedCachedValue(INJECTED_PSI_PROVIDER, false);
|
||||
|
||||
CachedValueProvider.Result<InjectionRegistrarImpl> result =
|
||||
CachedValueProvider.Result.create(registrar, PsiModificationTracker.MODIFICATION_COUNT, registrar);
|
||||
((PsiParameterizedCachedValue<InjectionRegistrarImpl, PsiElement>)cachedValue).setValue(result);
|
||||
CachedValueProvider.Result<InjectionResult> cachedResult = CachedValueProvider.Result.create(result, PsiModificationTracker.MODIFICATION_COUNT, result);
|
||||
cachedValue.setValue(cachedResult);
|
||||
|
||||
e.putUserData(INJECTED_PSI, cachedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return registrar;
|
||||
}
|
||||
|
||||
private static boolean intersects(@NotNull PsiElement hostElement, @NotNull Place place) {
|
||||
TextRange hostElementRange = hostElement.getTextRange();
|
||||
boolean intersects = false;
|
||||
for (PsiLanguageInjectionHost.Shred shred : place) {
|
||||
PsiLanguageInjectionHost shredHost = shred.getHost();
|
||||
if (shredHost != null && shredHost.getTextRange().intersects(hostElementRange)) {
|
||||
intersects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return intersects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+9
-12
@@ -32,12 +32,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author cdr
|
||||
*/
|
||||
class InjectedPsiCachedValueProvider implements ParameterizedCachedValueProvider<InjectionRegistrarImpl, PsiElement> {
|
||||
class InjectedPsiCachedValueProvider implements ParameterizedCachedValueProvider<InjectionResult, PsiElement> {
|
||||
@Override
|
||||
public CachedValueProvider.Result<InjectionRegistrarImpl> compute(PsiElement element) {
|
||||
public CachedValueProvider.Result<InjectionResult> compute(PsiElement element) {
|
||||
PsiFile hostPsiFile = element.getContainingFile();
|
||||
if (hostPsiFile == null) return null;
|
||||
FileViewProvider viewProvider = hostPsiFile.getViewProvider();
|
||||
@@ -48,20 +45,20 @@ class InjectedPsiCachedValueProvider implements ParameterizedCachedValueProvider
|
||||
final Project project = psiManager.getProject();
|
||||
InjectedLanguageManagerImpl injectedManager = InjectedLanguageManagerImpl.getInstanceImpl(project);
|
||||
|
||||
final InjectionRegistrarImpl result = doCompute(element, injectedManager, project, hostPsiFile);
|
||||
InjectionResult result = doCompute(element, injectedManager, project, hostPsiFile);
|
||||
|
||||
return CachedValueProvider.Result.create(result, PsiModificationTracker.MODIFICATION_COUNT, hostDocument);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static InjectionRegistrarImpl doCompute(@NotNull final PsiElement element,
|
||||
@NotNull InjectedLanguageManagerImpl injectedManager,
|
||||
Project project,
|
||||
PsiFile hostPsiFile) {
|
||||
static InjectionResult doCompute(@NotNull final PsiElement element,
|
||||
@NotNull InjectedLanguageManagerImpl injectedManager,
|
||||
@NotNull Project project,
|
||||
@NotNull PsiFile hostPsiFile) {
|
||||
MyInjProcessor processor = new MyInjProcessor(project, hostPsiFile);
|
||||
injectedManager.processInPlaceInjectorsFor(element, processor);
|
||||
InjectionRegistrarImpl registrar = processor.hostRegistrar;
|
||||
return registrar == null || registrar.getResult() == null ? null : registrar;
|
||||
return registrar == null ? null : registrar.getInjectedResult();
|
||||
}
|
||||
|
||||
private static class MyInjProcessor implements InjectedLanguageManagerImpl.InjProcessor {
|
||||
@@ -69,7 +66,7 @@ class InjectedPsiCachedValueProvider implements ParameterizedCachedValueProvider
|
||||
private final Project myProject;
|
||||
private final PsiFile myHostPsiFile;
|
||||
|
||||
private MyInjProcessor(Project project, PsiFile hostPsiFile) {
|
||||
private MyInjProcessor(@NotNull Project project, @NotNull PsiFile hostPsiFile) {
|
||||
myProject = project;
|
||||
myHostPsiFile = hostPsiFile;
|
||||
}
|
||||
|
||||
+278
-190
@@ -53,6 +53,7 @@ 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;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
@@ -62,38 +63,42 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHostRegistrar, ModificationTracker {
|
||||
private List<Pair<Place, PsiFile>> result;
|
||||
@SuppressWarnings("deprecation")
|
||||
class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHostRegistrar {
|
||||
private List<PsiFile> resultFiles;
|
||||
private List<Pair<ReferenceInjector, Place>> resultReferences;
|
||||
private Language myLanguage;
|
||||
private List<LiteralTextEscaper<? extends PsiLanguageInjectionHost>> escapers;
|
||||
private List<PsiLanguageInjectionHost.Shred> shreds;
|
||||
private StringBuilder outChars;
|
||||
private boolean isOneLineEditor;
|
||||
private boolean cleared;
|
||||
private List<PlaceInfo> placeInfos;
|
||||
private boolean cleared = true;
|
||||
private String fileExtension;
|
||||
private final Project myProject;
|
||||
private final PsiManager myPsiManager;
|
||||
private final DocumentEx myHostDocument;
|
||||
private final VirtualFile myHostVirtualFile;
|
||||
private final PsiElement myContextElement;
|
||||
private final PsiFile myHostPsiFile;
|
||||
private ReferenceInjector myReferenceInjector;
|
||||
private Thread currentThread;
|
||||
|
||||
InjectionRegistrarImpl(@NotNull Project project, @NotNull PsiFile hostPsiFile, @NotNull PsiElement contextElement) {
|
||||
myProject = project;
|
||||
myContextElement = contextElement;
|
||||
myHostPsiFile = PsiUtilCore.getTemplateLanguageFile(hostPsiFile);
|
||||
myPsiManager = myHostPsiFile.getManager();
|
||||
cleared = true;
|
||||
FileViewProvider viewProvider = myHostPsiFile.getViewProvider();
|
||||
myHostVirtualFile = viewProvider.getVirtualFile();
|
||||
myHostDocument = (DocumentEx)viewProvider.getDocument();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable("null means nobody cared to call .doneInjecting()")
|
||||
@Deprecated
|
||||
public List<Pair<Place, PsiFile>> getResult() {
|
||||
return result;
|
||||
return resultFiles == null ? null : resultFiles.stream().map(file -> Pair.create(InjectedLanguageUtil.getShreds(file), file)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
InjectionResult getInjectedResult() {
|
||||
return resultFiles == null && resultReferences == null ? null : new InjectionResult(resultFiles, resultReferences);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -106,14 +111,13 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
@Override
|
||||
public MultiHostRegistrar startInjecting(@NotNull Language language, @Nullable String extension) {
|
||||
fileExtension = extension;
|
||||
escapers = new SmartList<>();
|
||||
shreds = new SmartList<>();
|
||||
outChars = new StringBuilder();
|
||||
placeInfos = new SmartList<>();
|
||||
|
||||
if (!cleared) {
|
||||
clear();
|
||||
throw new IllegalStateException("Seems you haven't called doneInjecting()");
|
||||
}
|
||||
currentThread = Thread.currentThread();
|
||||
|
||||
if (LanguageParserDefinitions.INSTANCE.forLanguage(language) == null) {
|
||||
throw new UnsupportedOperationException("Cannot inject language '" + language + "' because it has no ParserDefinition");
|
||||
@@ -123,14 +127,38 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
escapers.clear();
|
||||
shreds.clear();
|
||||
outChars.setLength(0);
|
||||
isOneLineEditor = false;
|
||||
fileExtension = null;
|
||||
myLanguage = null;
|
||||
|
||||
cleared = true;
|
||||
placeInfos = null;
|
||||
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
|
||||
@@ -139,148 +167,93 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
@NonNls @Nullable String suffix,
|
||||
@NotNull PsiLanguageInjectionHost host,
|
||||
@NotNull TextRange rangeInsideHost) {
|
||||
|
||||
checkThreading();
|
||||
if (myLanguage == null) {
|
||||
clear();
|
||||
throw new IllegalStateException("Seems you haven't called startInjecting()");
|
||||
}
|
||||
PsiFile containingFile = PsiUtilCore.getTemplateLanguageFile(host);
|
||||
assert containingFile == myHostPsiFile : exceptionContext("Trying to inject into foreign file: "+containingFile);
|
||||
assert containingFile == myHostPsiFile : exceptionContext("Trying to inject into foreign file: "+containingFile, myLanguage,
|
||||
myHostPsiFile, myHostVirtualFile, myHostDocument, myContextElement, placeInfos);
|
||||
TextRange hostTextRange = host.getTextRange();
|
||||
if (!hostTextRange.contains(rangeInsideHost.shiftRight(hostTextRange.getStartOffset()))) {
|
||||
clear();
|
||||
throw new IllegalArgumentException("rangeInsideHost must lie within host text range. rangeInsideHost:"+rangeInsideHost+"; host textRange:"+
|
||||
hostTextRange);
|
||||
}
|
||||
if (myLanguage == null && myReferenceInjector == null) {
|
||||
clear();
|
||||
throw new IllegalStateException("Seems you haven't called startInjecting()");
|
||||
}
|
||||
|
||||
if (prefix == null) prefix = "";
|
||||
if (suffix == null) suffix = "";
|
||||
cleared = false;
|
||||
PlaceInfo info = new PlaceInfo(ObjectUtils.notNull(prefix, ""), ObjectUtils.notNull(suffix, ""), host, rangeInsideHost);
|
||||
placeInfos.add(info);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void checkThreading() {
|
||||
if (currentThread != Thread.currentThread()) {
|
||||
throw new IllegalStateException("Wow, you must not start injecting in one thread ("+currentThread+") but finish the other");
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Pair.NonNull<ShredImpl, LiteralTextEscaper> createShred(@NotNull Project project, @NotNull PlaceInfo info,
|
||||
@NotNull StringBuilder outChars,
|
||||
@NotNull PsiFile hostPsiFile) {
|
||||
int startOffset = outChars.length();
|
||||
String prefix = info.prefix;
|
||||
outChars.append(prefix);
|
||||
PsiLanguageInjectionHost host = info.host;
|
||||
LiteralTextEscaper<? extends PsiLanguageInjectionHost> textEscaper = host.createLiteralTextEscaper();
|
||||
escapers.add(textEscaper);
|
||||
isOneLineEditor |= textEscaper.isOneLine();
|
||||
|
||||
TextRange rangeInsideHost = info.rangeInsideHost;
|
||||
TextRange relevantRange = textEscaper.getRelevantTextRange().intersection(rangeInsideHost);
|
||||
if (relevantRange == null) {
|
||||
relevantRange = TextRange.from(textEscaper.getRelevantTextRange().getStartOffset(), 0);
|
||||
}
|
||||
else {
|
||||
int before = outChars.length();
|
||||
boolean result = textEscaper.decode(relevantRange, outChars);
|
||||
boolean decodeFailed = !textEscaper.decode(relevantRange, outChars);
|
||||
int after = outChars.length();
|
||||
assert after >= before : "Escaper " + textEscaper + "("+textEscaper.getClass()+") must not mangle char buffer";
|
||||
if (!result) {
|
||||
if (decodeFailed) {
|
||||
// if there are invalid chars, adjust the range
|
||||
int offsetInHost = textEscaper.getOffsetInHost(outChars.length() - before, rangeInsideHost);
|
||||
relevantRange = relevantRange.intersection(new ProperTextRange(0, offsetInHost));
|
||||
}
|
||||
}
|
||||
String suffix = info.suffix;
|
||||
outChars.append(suffix);
|
||||
int endOffset = outChars.length();
|
||||
TextRange hostTextRange = host.getTextRange();
|
||||
TextRange relevantRangeInHost = relevantRange.shiftRight(hostTextRange.getStartOffset());
|
||||
SmartPointerManagerImpl manager = (SmartPointerManagerImpl)SmartPointerManager.getInstance(myProject);
|
||||
shreds.add(new ShredImpl(manager.createSmartPsiFileRangePointer(myHostPsiFile, relevantRangeInHost, true),
|
||||
manager.createSmartPsiElementPointer(host, myHostPsiFile, true),
|
||||
prefix, suffix, new ProperTextRange(startOffset, endOffset), false));
|
||||
return this;
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doneInjecting() {
|
||||
checkThreading();
|
||||
try {
|
||||
if (shreds.isEmpty()) {
|
||||
if (myLanguage == null) {
|
||||
throw new IllegalStateException("Seems you haven't called startInjecting()");
|
||||
}
|
||||
if (placeInfos.isEmpty()) {
|
||||
throw new IllegalStateException("Seems you haven't called addPlace()");
|
||||
}
|
||||
if (myReferenceInjector != null) {
|
||||
addToResults(new Place(shreds), null);
|
||||
return;
|
||||
}
|
||||
PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(myProject);
|
||||
|
||||
Place place = new Place(shreds);
|
||||
DocumentWindowImpl documentWindow = new DocumentWindowImpl(myHostDocument, isOneLineEditor, place);
|
||||
String fileName = PathUtil.makeFileName(myHostVirtualFile.getName(), fileExtension);
|
||||
VirtualFileWindowImpl virtualFile = new VirtualFileWindowImpl(fileName, myHostVirtualFile, documentWindow, myLanguage, outChars);
|
||||
Language forcedLanguage = myContextElement.getUserData(InjectedFileViewProvider.LANGUAGE_FOR_INJECTED_COPY_KEY);
|
||||
myLanguage = forcedLanguage == null ? LanguageSubstitutors.INSTANCE.substituteLanguage(myLanguage, virtualFile, myProject) : forcedLanguage;
|
||||
|
||||
createDocument(virtualFile);
|
||||
|
||||
InjectedFileViewProvider viewProvider = new InjectedFileViewProvider(myPsiManager, virtualFile, documentWindow, myLanguage);
|
||||
ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(myLanguage);
|
||||
assert parserDefinition != null : "Parser definition for language "+myLanguage+" is null";
|
||||
PsiFile psiFile = parserDefinition.createFile(viewProvider);
|
||||
|
||||
SmartPsiElementPointer<PsiLanguageInjectionHost> pointer = ((ShredImpl)shreds.get(0)).getSmartPointer();
|
||||
|
||||
synchronized (InjectedLanguageManagerImpl.ourInjectionPsiLock) {
|
||||
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(outChars) : exceptionContext("Before patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText() + "'\n---chars:\n'"+outChars+"'");
|
||||
|
||||
viewProvider.setPatchingLeaves(true);
|
||||
try {
|
||||
patchLeaves(parsedNode, escapers, place);
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw new RuntimeException(exceptionContext("Patch error"), e);
|
||||
}
|
||||
finally {
|
||||
viewProvider.setPatchingLeaves(false);
|
||||
}
|
||||
if (!((FileElement)parsedNode).textMatches(documentText)) {
|
||||
throw new AssertionError(exceptionContext("After patch: doc:\n'" + documentText + "'\n---PSI:\n'" + parsedNode.getText() + "'\n---chars:\n'" + outChars + "'"));
|
||||
}
|
||||
|
||||
virtualFile.setContent(null, documentWindow.getText(), false);
|
||||
virtualFile.setWritable(virtualFile.getDelegate().isWritable());
|
||||
|
||||
cacheEverything(place, documentWindow, viewProvider, psiFile, pointer);
|
||||
|
||||
PsiFile cachedPsiFile = documentManager.getCachedPsiFile(documentWindow);
|
||||
assert cachedPsiFile == psiFile : "Cached psi :"+ cachedPsiFile +" instead of "+psiFile;
|
||||
|
||||
assert place.isValid();
|
||||
assert viewProvider.isValid();
|
||||
PsiFile newFile = registerDocument(documentWindow, psiFile, place, myHostPsiFile, documentManager);
|
||||
boolean mergeHappened = newFile != psiFile;
|
||||
if (mergeHappened) {
|
||||
InjectedLanguageUtil.clearCaches(psiFile, documentWindow);
|
||||
psiFile = newFile;
|
||||
viewProvider = (InjectedFileViewProvider)psiFile.getViewProvider();
|
||||
documentWindow = (DocumentWindowImpl)viewProvider.getDocument();
|
||||
virtualFile = (VirtualFileWindowImpl)viewProvider.getVirtualFile();
|
||||
boolean shredsRewritten = cacheEverything(place, documentWindow, viewProvider, psiFile, pointer);
|
||||
if (!shredsRewritten) {
|
||||
place.dispose();
|
||||
place = documentWindow.getShreds();
|
||||
}
|
||||
}
|
||||
|
||||
assert psiFile.isValid();
|
||||
assert place.isValid();
|
||||
assert viewProvider.isValid();
|
||||
|
||||
try {
|
||||
List<Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, TextRange>> tokens = obtainHighlightTokensFromLexer(myLanguage, outChars, escapers, place, virtualFile, myProject);
|
||||
psiFile.putUserData(InjectedLanguageUtil.HIGHLIGHT_TOKENS, tokens);
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw new RuntimeException(exceptionContext("Obtaining tokens error"), e);
|
||||
}
|
||||
|
||||
addToResults(place, psiFile);
|
||||
Language forcedLanguage = myContextElement.getUserData(InjectedFileViewProvider.LANGUAGE_FOR_INJECTED_COPY_KEY);
|
||||
PsiFile psiFile =
|
||||
createInjectedFile(myProject, myLanguage, forcedLanguage,
|
||||
myHostDocument, myHostVirtualFile, myHostPsiFile, fileExtension, placeInfos,
|
||||
myContextElement);
|
||||
addFileToResults(psiFile);
|
||||
|
||||
PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(myProject);
|
||||
DocumentWindowImpl documentWindow = (DocumentWindowImpl)documentManager.getDocument(psiFile);
|
||||
assertEverythingIsAllright(documentManager, documentWindow, psiFile);
|
||||
}
|
||||
}
|
||||
@@ -289,6 +262,124 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiFile createInjectedFile(@NotNull Project project,
|
||||
@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) {
|
||||
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);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
PsiFile cachedPsiFile = documentManager.getCachedPsiFile(documentWindow);
|
||||
assert cachedPsiFile == psiFile : "Cached psi :"+ cachedPsiFile +" instead of "+psiFile;
|
||||
|
||||
assert place.isValid();
|
||||
assert viewProvider.isValid();
|
||||
|
||||
PsiFile newFile = registerDocument(documentWindow, psiFile, place, hostPsiFile, documentManager);
|
||||
boolean mergeHappened = newFile != psiFile;
|
||||
Place mergedPlace = place;
|
||||
if (mergeHappened) {
|
||||
InjectedLanguageUtil.clearCaches(psiFile, documentWindow);
|
||||
psiFile = newFile;
|
||||
viewProvider = (InjectedFileViewProvider)psiFile.getViewProvider();
|
||||
documentWindow = (DocumentWindowImpl)viewProvider.getDocument();
|
||||
virtualFile = (VirtualFileWindowImpl)viewProvider.getVirtualFile();
|
||||
boolean shredsReused = !cacheEverything(place, documentWindow, viewProvider, psiFile, pointer);
|
||||
if (shredsReused) {
|
||||
place.dispose();
|
||||
mergedPlace = documentWindow.getShreds();
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -302,21 +393,28 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
if (injector == null) {
|
||||
throw new IllegalArgumentException("Language "+language+" being injected as reference must register reference injector");
|
||||
}
|
||||
escapers = new SmartList<>();
|
||||
shreds = new SmartList<>();
|
||||
outChars = new StringBuilder();
|
||||
placeInfos = new SmartList<>();
|
||||
|
||||
if (!cleared) {
|
||||
clear();
|
||||
throw new IllegalStateException("Seems you haven't called doneInjecting()");
|
||||
}
|
||||
|
||||
myReferenceInjector = injector;
|
||||
myLanguage = language;
|
||||
currentThread = Thread.currentThread();
|
||||
|
||||
addPlace(prefix, suffix, host, rangeInsideHost);
|
||||
doneInjecting();
|
||||
Place place = new Place();
|
||||
StringBuilder decodedChars = new StringBuilder();
|
||||
Pair.NonNull<ShredImpl, LiteralTextEscaper> p = createShred(myProject, placeInfos.get(0), decodedChars, myHostPsiFile);
|
||||
place.add(p.getFirst());
|
||||
if (resultReferences == null) {
|
||||
resultReferences = new SmartList<>();
|
||||
}
|
||||
resultReferences.add(Pair.create(injector, place));
|
||||
clear();
|
||||
}
|
||||
|
||||
|
||||
private static void createDocument(@NotNull LightVirtualFile virtualFile) {
|
||||
CharSequence content = virtualFile.getContent();
|
||||
@@ -345,18 +443,24 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
|
||||
keepTreeFromChameleoningBack(psiFile);
|
||||
|
||||
return viewProvider.setShreds(place, psiFile.getProject());
|
||||
return viewProvider.setShreds(place);
|
||||
}
|
||||
|
||||
|
||||
@NonNls
|
||||
private String exceptionContext(@NonNls String msg) {
|
||||
private static String exceptionContext(@NonNls @NotNull String msg,
|
||||
@NotNull Language language,
|
||||
@NotNull PsiFile hostPsiFile,
|
||||
@NotNull VirtualFile hostVirtualFile,
|
||||
@NotNull DocumentEx hostDocument,
|
||||
@NotNull PsiElement contextElement,
|
||||
@NotNull List<PlaceInfo> placeInfos) {
|
||||
return msg + ".\n" +
|
||||
myLanguage+";\n "+
|
||||
"Host file: "+myHostPsiFile+" in '" + myHostVirtualFile.getPresentableUrl() + "'" +
|
||||
(PsiDocumentManager.getInstance(myProject).isUncommited(myHostDocument) ? " (uncommitted)": "")+ "\n" +
|
||||
"Context element "+myContextElement.getTextRange() + ": '" + myContextElement +"'; "+
|
||||
"Ranges: "+shreds;
|
||||
language + ";\n " +
|
||||
"Host file: " + hostPsiFile + " in '" + hostVirtualFile.getPresentableUrl() + "'" +
|
||||
(PsiDocumentManager.getInstance(hostPsiFile.getProject()).isUncommited(hostDocument) ? " (uncommitted)" : "") + "\n" +
|
||||
"Context element " + contextElement.getTextRange() + ": '" + contextElement + "'; " +
|
||||
"Ranges: " + placeInfos;
|
||||
}
|
||||
|
||||
private static final Key<ASTNode> TREE_HARD_REF = Key.create("TREE_HARD_REF");
|
||||
@@ -377,14 +481,9 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
return node;
|
||||
}
|
||||
|
||||
private void assertEverythingIsAllright(PsiDocumentManagerBase documentManager, DocumentWindowImpl documentWindow, PsiFile psiFile) {
|
||||
boolean isAncestor = false;
|
||||
for (PsiLanguageInjectionHost.Shred shred : shreds) {
|
||||
PsiLanguageInjectionHost host = shred.getHost();
|
||||
isAncestor |= PsiTreeUtil.isAncestor(myContextElement, host, false);
|
||||
}
|
||||
assert isAncestor : exceptionContext(myContextElement + " must be the parent of at least one of injection hosts");
|
||||
|
||||
private static void assertEverythingIsAllright(@NotNull PsiDocumentManagerBase documentManager,
|
||||
@NotNull DocumentWindowImpl documentWindow,
|
||||
@NotNull PsiFile psiFile) {
|
||||
InjectedFileViewProvider injectedFileViewProvider = (InjectedFileViewProvider)psiFile.getViewProvider();
|
||||
assert injectedFileViewProvider.isValid() : "Invalid view provider: "+injectedFileViewProvider;
|
||||
DocumentEx frozenWindow = documentManager.getLastCommittedDocument(documentWindow);
|
||||
@@ -398,22 +497,36 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
PsiDocumentManagerBase.checkConsistency(psiFile, frozenWindow);
|
||||
}
|
||||
|
||||
void addToResults(@NotNull Place place, @Nullable /*WAT?*/PsiFile psiFile, @NotNull InjectionRegistrarImpl from) {
|
||||
addToResults(place, psiFile);
|
||||
myReferenceInjector = from.myReferenceInjector;
|
||||
void addToResults(@NotNull InjectionResult result) {
|
||||
if (result.files != null) {
|
||||
for (PsiFile file : result.files) {
|
||||
addFileToResults(file);
|
||||
}
|
||||
}
|
||||
if (result.references != null) {
|
||||
for (Pair<ReferenceInjector, Place> pair : result.references) {
|
||||
addReferenceToResults(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addToResults(@NotNull Place place, @Nullable /*WAT?*/PsiFile psiFile) {
|
||||
if (result == null) {
|
||||
result = new SmartList<>();
|
||||
private void addFileToResults(@NotNull PsiFile psiFile) {
|
||||
if (resultFiles == null) {
|
||||
resultFiles = new SmartList<>();
|
||||
}
|
||||
result.add(Pair.create(place, psiFile));
|
||||
resultFiles.add(psiFile);
|
||||
}
|
||||
private void addReferenceToResults(@NotNull Pair<ReferenceInjector, Place> pair) {
|
||||
if (resultReferences == null) {
|
||||
resultReferences = new SmartList<>();
|
||||
}
|
||||
resultReferences.add(pair);
|
||||
}
|
||||
|
||||
|
||||
private static void patchLeaves(@NotNull ASTNode parsedNode,
|
||||
@NotNull List<LiteralTextEscaper<? extends PsiLanguageInjectionHost>> escapers,
|
||||
@NotNull Place shreds) {
|
||||
@NotNull Place shreds,
|
||||
@NotNull List<LiteralTextEscaper> escapers) {
|
||||
LeafPatcher patcher = new LeafPatcher(shreds, escapers);
|
||||
((TreeElement)parsedNode).acceptTree(patcher);
|
||||
|
||||
@@ -524,20 +637,21 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
}
|
||||
|
||||
// returns lexer element types with corresponding ranges in encoded (injection host based) PSI
|
||||
@NotNull
|
||||
private static List<Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, TextRange>>
|
||||
obtainHighlightTokensFromLexer(Language language,
|
||||
StringBuilder outChars,
|
||||
List<LiteralTextEscaper<? extends PsiLanguageInjectionHost>> escapers,
|
||||
Place shreds,
|
||||
VirtualFileWindow virtualFile,
|
||||
Project project) {
|
||||
obtainHighlightTokensFromLexer(@NotNull Language language,
|
||||
@NotNull CharSequence outChars,
|
||||
@NotNull Place shreds,
|
||||
@NotNull VirtualFileWindow virtualFile,
|
||||
@NotNull Project project,
|
||||
@NotNull List<LiteralTextEscaper> escapers) {
|
||||
SyntaxHighlighter syntaxHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(language, project, (VirtualFile)virtualFile);
|
||||
Lexer lexer = syntaxHighlighter.getHighlightingLexer();
|
||||
lexer.start(outChars);
|
||||
int hostNum = -1;
|
||||
int prevHostEndOffset = 0;
|
||||
SmartPsiElementPointer<PsiLanguageInjectionHost> host = null;
|
||||
LiteralTextEscaper<? extends PsiLanguageInjectionHost> escaper = null;
|
||||
SmartPsiElementPointer<PsiLanguageInjectionHost> hostPtr = null;
|
||||
LiteralTextEscaper escaper = null;
|
||||
int prefixLength = 0;
|
||||
int suffixLength = 0;
|
||||
TextRange rangeInsideHost = null;
|
||||
@@ -551,7 +665,7 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
PsiLanguageInjectionHost.Shred shred = shreds.get(hostNum);
|
||||
shredEndOffset = shred.getRange().getEndOffset();
|
||||
prevHostEndOffset = range.getStartOffset();
|
||||
host = ((ShredImpl)shred).getSmartPointer();
|
||||
hostPtr = ((ShredImpl)shred).getSmartPointer();
|
||||
escaper = escapers.get(hostNum);
|
||||
rangeInsideHost = shred.getRangeInsideHost();
|
||||
prefixLength = shred.getPrefix().length();
|
||||
@@ -575,7 +689,7 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
prevHostEndOffset = shredEndOffset;
|
||||
}
|
||||
TextRange rangeInHost = new ProperTextRange(start, end);
|
||||
tokens.add(Trinity.create(tokenType, host, rangeInHost));
|
||||
tokens.add(Trinity.create(tokenType, hostPtr, rangeInHost));
|
||||
}
|
||||
range = spilled;
|
||||
}
|
||||
@@ -583,22 +697,9 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// for CachedValue
|
||||
@Override
|
||||
public long getModificationCount() {
|
||||
List<PsiLanguageInjectionHost.Shred> shredList = shreds;
|
||||
if (shredList != null) {
|
||||
for (PsiLanguageInjectionHost.Shred shred : shredList) {
|
||||
if (!shred.isValid()) return -1;
|
||||
}
|
||||
}
|
||||
DocumentEx hostDocument = myHostDocument;
|
||||
return hostDocument == null ? -1 : hostDocument.getModificationStamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(result);
|
||||
return String.valueOf(resultFiles);
|
||||
}
|
||||
|
||||
// performance: avoid context.getContainingFile()
|
||||
@@ -606,17 +707,4 @@ class InjectionRegistrarImpl extends MultiHostRegistrarImpl implements MultiHost
|
||||
PsiFile getHostPsiFile() {
|
||||
return myHostPsiFile;
|
||||
}
|
||||
|
||||
ReferenceInjector getReferenceInjector() {
|
||||
return myReferenceInjector;
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
List<Pair<Place, PsiFile>> result = getResult();
|
||||
if (result == null) return false;
|
||||
for (Pair<Place, PsiFile> pair : result) {
|
||||
if (!pair.getFirst().isValid()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.psi.impl.source.tree.injected;
|
||||
|
||||
import com.intellij.openapi.util.ModificationTracker;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||
import com.intellij.psi.injection.ReferenceInjector;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class InjectionResult implements ModificationTracker {
|
||||
final List<PsiFile> files;
|
||||
final List<Pair<ReferenceInjector, Place>> references;
|
||||
|
||||
InjectionResult(List<PsiFile> files, List<Pair<ReferenceInjector, Place>> references) {
|
||||
this.files = files;
|
||||
this.references = references;
|
||||
if (files == null && references == null) throw new IllegalArgumentException("At least one argument must not be null");
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
if (files != null) {
|
||||
for (PsiFile file : files) {
|
||||
if (!file.isValid()) return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (Pair<ReferenceInjector, Place> pair : references) {
|
||||
Place place = pair.getSecond();
|
||||
if (!place.isValid()) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// for CachedValue
|
||||
@Override
|
||||
public long getModificationCount() {
|
||||
long modCount = 0;
|
||||
if (files != null) {
|
||||
for (PsiFile file : files) {
|
||||
if (!file.isValid()) return -1;
|
||||
modCount += file.getModificationStamp();
|
||||
}
|
||||
}
|
||||
if (references != null) {
|
||||
for (Pair<ReferenceInjector, Place> pair : references) {
|
||||
Place place = pair.getSecond();
|
||||
if (!place.isValid()) return -1;
|
||||
for (PsiLanguageInjectionHost.Shred shred : place) {
|
||||
PsiLanguageInjectionHost host = shred.getHost();
|
||||
if (host == null || !host.isValid()) return -1;
|
||||
PsiFile file = host.getContainingFile();
|
||||
modCount += file.getModificationStamp();
|
||||
}
|
||||
}
|
||||
}
|
||||
return modCount;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ 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 gnu.trove.THashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -36,13 +37,14 @@ import java.util.Map;
|
||||
class LeafPatcher extends RecursiveTreeElementWalkingVisitor {
|
||||
private int shredNo;
|
||||
private String hostText;
|
||||
private LiteralTextEscaper currentTextEscaper;
|
||||
private TextRange rangeInHost;
|
||||
private final Place myShreds;
|
||||
private final List<LiteralTextEscaper<? extends PsiLanguageInjectionHost>> myEscapers;
|
||||
@NotNull private final List<LiteralTextEscaper> myEscapers;
|
||||
final Map<LeafElement, String> newTexts = new THashMap<>();
|
||||
final StringBuilder catLeafs = new StringBuilder();
|
||||
|
||||
LeafPatcher(Place shreds, List<LiteralTextEscaper<? extends PsiLanguageInjectionHost>> escapers) {
|
||||
LeafPatcher(@NotNull Place shreds, @NotNull List<LiteralTextEscaper> escapers) {
|
||||
myShreds = shreds;
|
||||
myEscapers = escapers;
|
||||
}
|
||||
@@ -65,8 +67,10 @@ class LeafPatcher extends RecursiveTreeElementWalkingVisitor {
|
||||
boolean firstTimer = false;
|
||||
PsiLanguageInjectionHost.Shred current = myShreds.get(shredNo);
|
||||
if (hostText == null) {
|
||||
hostText = current.getHost().getText();
|
||||
PsiLanguageInjectionHost host = current.getHost();
|
||||
hostText = host.getText();
|
||||
rangeInHost = current.getRangeInsideHost();
|
||||
currentTextEscaper = myEscapers.get(shredNo);
|
||||
firstTimer = true;
|
||||
}
|
||||
|
||||
@@ -76,7 +80,9 @@ class LeafPatcher extends RecursiveTreeElementWalkingVisitor {
|
||||
String prefix = current.getPrefix();
|
||||
if (startOffset >= shredRange.getEndOffset()) {
|
||||
current = myShreds.get(++shredNo);
|
||||
hostText = current.getHost().getText();
|
||||
PsiLanguageInjectionHost host = current.getHost();
|
||||
hostText = host.getText();
|
||||
currentTextEscaper = myEscapers.get(shredNo);
|
||||
rangeInHost = current.getRangeInsideHost();
|
||||
firstTimer = true;
|
||||
continue;
|
||||
@@ -93,10 +99,10 @@ class LeafPatcher extends RecursiveTreeElementWalkingVisitor {
|
||||
String suffix = current.getSuffix();
|
||||
if (startOffset < shredRange.getEndOffset() - suffix.length()) {
|
||||
// inside host body, cut out from the host text
|
||||
int startOffsetInHost = myEscapers.get(shredNo).getOffsetInHost(
|
||||
int startOffsetInHost = currentTextEscaper.getOffsetInHost(
|
||||
startOffset - shredRange.getStartOffset() - prefix.length(), rangeInHost);
|
||||
int endOffsetCut = Math.min(endOffset, shredRange.getEndOffset() - suffix.length());
|
||||
int endOffsetInHost = myEscapers.get(shredNo).getOffsetInHost(
|
||||
int endOffsetInHost = currentTextEscaper.getOffsetInHost(
|
||||
endOffsetCut - shredRange.getStartOffset() - prefix.length(), rangeInHost);
|
||||
if (endOffsetInHost != -1) {
|
||||
if (firstTimer ) text.append(hostText, rangeInHost.getStartOffset(), startOffsetInHost);
|
||||
|
||||
@@ -21,11 +21,8 @@ import com.intellij.psi.SmartPsiElementPointer;
|
||||
import com.intellij.util.SmartList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Place extends SmartList<PsiLanguageInjectionHost.Shred> {
|
||||
Place(@NotNull List<PsiLanguageInjectionHost.Shred> shreds) {
|
||||
super(shreds);
|
||||
Place() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -29,19 +29,21 @@ class ShredImpl implements PsiLanguageInjectionHost.Shred {
|
||||
private final String prefix;
|
||||
private final String suffix;
|
||||
private final boolean usePsiRange;
|
||||
private final boolean isOneLine;
|
||||
|
||||
ShredImpl(@NotNull SmartPsiFileRange relevantRangeInHost,
|
||||
@NotNull SmartPsiElementPointer<PsiLanguageInjectionHost> hostElementPointer,
|
||||
@NotNull String prefix,
|
||||
@NotNull String suffix,
|
||||
@NotNull TextRange range,
|
||||
boolean usePsiRange) {
|
||||
boolean usePsiRange, boolean isOneLine) {
|
||||
this.hostElementPointer = hostElementPointer;
|
||||
this.relevantRangeInHost = relevantRangeInHost;
|
||||
this.prefix = prefix;
|
||||
this.suffix = suffix;
|
||||
this.range = range;
|
||||
this.usePsiRange = usePsiRange;
|
||||
this.isOneLine = isOneLine;
|
||||
|
||||
assertValid();
|
||||
}
|
||||
@@ -55,7 +57,7 @@ class ShredImpl implements PsiLanguageInjectionHost.Shred {
|
||||
}
|
||||
|
||||
ShredImpl withPsiRange() {
|
||||
return new ShredImpl(relevantRangeInHost, hostElementPointer, prefix, suffix, range, true);
|
||||
return new ShredImpl(relevantRangeInHost, hostElementPointer, prefix, suffix, range, true, isOneLine);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -155,4 +157,8 @@ class ShredImpl implements PsiLanguageInjectionHost.Shred {
|
||||
public String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
boolean isOneLine() {
|
||||
return isOneLine;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user