refactored injections to avoid caching non-thread-safe InjectionRegistrar

This commit is contained in:
Alexey Kudravtsev
2017-12-01 18:02:38 +03:00
parent 7ae97bf00e
commit 521cea8535
10 changed files with 492 additions and 332 deletions
@@ -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
@@ -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;
}
}
}
@@ -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) {
@@ -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;
}
/**
@@ -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;
}
@@ -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;
}
}
@@ -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;
}
}