/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.psi.impl.smartPointers;
import com.intellij.injected.editor.DocumentWindow;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.lang.LanguageUtil;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.ProperTextRange;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.FreeThreadedFileViewProvider;
import com.intellij.psi.impl.PsiDocumentManagerBase;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
/**
* User: cdr
*/
class InjectedSelfElementInfo extends SmartPointerElementInfo {
private final SmartPsiFileRange myInjectedFileRangeInHostFile;
@Nullable private final AffixOffsets myAffixOffsets;
private final Identikit myType;
@NotNull
private final SmartPsiElementPointer<PsiLanguageInjectionHost> myHostContext;
InjectedSelfElementInfo(@NotNull Project project,
@NotNull PsiElement injectedElement,
@NotNull TextRange injectedRange,
@NotNull PsiFile containingFile,
@NotNull SmartPsiElementPointer<PsiLanguageInjectionHost> hostContext) {
myHostContext = hostContext;
assert containingFile.getViewProvider() instanceof FreeThreadedFileViewProvider : "element parameter must be an injected element: "+injectedElement+"; "+containingFile;
assert containingFile.getTextRange().contains(injectedRange) : "Injected range outside the file: "+injectedRange +"; file: "+containingFile.getTextRange();
TextRange hostRange = InjectedLanguageManager.getInstance(project).injectedToHost(injectedElement, injectedRange);
PsiFile hostFile = hostContext.getContainingFile();
assert !(hostFile.getViewProvider() instanceof FreeThreadedFileViewProvider) : "hostContext parameter must not be and injected element: "+hostContext;
SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(project);
myInjectedFileRangeInHostFile = smartPointerManager.createSmartPsiFileRangePointer(hostFile, hostRange);
myType = Identikit.fromPsi(injectedElement, LanguageUtil.getRootLanguage(containingFile));
int startAffixIndex = -1;
int startAffixOffset = -1;
int endAffixIndex = -1;
int endAffixOffset = -1;
List<TextRange> fragments = InjectedLanguageManager.getInstance(project).getNonEditableFragments((DocumentWindow)containingFile.getViewProvider().getDocument());
for (int i = 0; i < fragments.size(); i++) {
TextRange range = fragments.get(i);
if (range.containsOffset(injectedRange.getStartOffset())) {
startAffixIndex = i;
startAffixOffset = injectedRange.getStartOffset() - range.getStartOffset();
}
if (range.containsOffset(injectedRange.getEndOffset())) {
endAffixIndex = i;
endAffixOffset = injectedRange.getEndOffset() - range.getStartOffset();
}
}
myAffixOffsets = startAffixIndex >= 0 || endAffixIndex >= 0 ? new AffixOffsets(startAffixIndex, startAffixOffset, endAffixIndex, endAffixOffset) : null;
}
@Override
public VirtualFile getVirtualFile() {
PsiElement element = restoreElement();
if (element == null) return null;
return element.getContainingFile().getVirtualFile();
}
@Override
public Segment getRange() {
return getInjectedRange(false);
}
@Nullable
@Override
public Segment getPsiRange() {
return getInjectedRange(true);
}
@Override
public PsiElement restoreElement() {
PsiFile hostFile = myHostContext.getContainingFile();
if (hostFile == null || !hostFile.isValid()) return null;
PsiElement hostContext = myHostContext.getElement();
if (hostContext == null) return null;
Segment segment = myInjectedFileRangeInHostFile.getPsiRange();
if (segment == null) return null;
PsiFile injectedPsi = getInjectedFileIn(hostContext, hostFile, TextRange.create(segment));
ProperTextRange rangeInInjected = hostToInjected(true, segment, injectedPsi, myAffixOffsets);
if (rangeInInjected == null) return null;
return myType.findPsiElement(injectedPsi, rangeInInjected.getStartOffset(), rangeInInjected.getEndOffset());
}
private PsiFile getInjectedFileIn(@NotNull final PsiElement hostContext,
@NotNull final PsiFile hostFile,
@NotNull final TextRange rangeInHostFile) {
final PsiDocumentManagerBase docManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(getProject());
final PsiFile[] result = {null};
final PsiLanguageInjectionHost.InjectedPsiVisitor visitor = (injectedPsi, places) -> {
Document document = docManager.getDocument(injectedPsi);
if (document instanceof DocumentWindow) {
DocumentWindow window = (DocumentWindow)docManager.getLastCommittedDocument(document);
TextRange hostRange = window.injectedToHost(new TextRange(0, injectedPsi.getTextLength()));
if (hostRange.contains(rangeInHostFile)) {
result[0] = injectedPsi;
}
}
};
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject());
Document document = documentManager.getDocument(hostFile);
if (document != null && documentManager.isUncommited(document)) {
for (DocumentWindow documentWindow : InjectedLanguageManager.getInstance(getProject()).getCachedInjectedDocuments(hostFile)) {
PsiFile injected = documentManager.getPsiFile(documentWindow);
if (injected != null) {
visitor.visit(injected, Collections.emptyList());
}
}
}
else {
List<Pair<PsiElement,TextRange>> injected = InjectedLanguageManager.getInstance(getProject()).getInjectedPsiFiles(hostContext);
if (injected != null) {
for (Pair<PsiElement, TextRange> pair : injected) {
PsiFile injectedFile = pair.first.getContainingFile();
visitor.visit(injectedFile, ContainerUtil.emptyList());
}
}
}
return result[0];
}
@Override
public boolean pointsToTheSameElementAs(@NotNull SmartPointerElementInfo other) {
if (getClass() != other.getClass()) return false;
if (!((InjectedSelfElementInfo)other).myHostContext.equals(myHostContext)) return false;
SmartPointerElementInfo myElementInfo = ((SmartPsiElementPointerImpl)myInjectedFileRangeInHostFile).getElementInfo();
SmartPointerElementInfo oElementInfo = ((SmartPsiElementPointerImpl)((InjectedSelfElementInfo)other).myInjectedFileRangeInHostFile).getElementInfo();
return myElementInfo.pointsToTheSameElementAs(oElementInfo);
}
@Override
public PsiFile restoreFile() {
PsiFile hostFile = myHostContext.getContainingFile();
if (hostFile == null || !hostFile.isValid()) return null;
PsiElement hostContext = myHostContext.getElement();
if (hostContext == null) return null;
Segment segment = myInjectedFileRangeInHostFile.getPsiRange();
if (segment == null) return null;
final TextRange rangeInHostFile = TextRange.create(segment);
return getInjectedFileIn(hostContext, hostFile, rangeInHostFile);
}
@Nullable
private ProperTextRange getInjectedRange(boolean psi) {
PsiElement hostContext = myHostContext.getElement();
if (hostContext == null) return null;
Segment hostElementRange = psi ? myInjectedFileRangeInHostFile.getPsiRange() : myInjectedFileRangeInHostFile.getRange();
if (hostElementRange == null) return null;
return hostToInjected(psi, hostElementRange, restoreFile(), myAffixOffsets);
}
@Nullable
private static ProperTextRange hostToInjected(boolean psi, Segment hostRange, @Nullable PsiFile injectedFile, @Nullable AffixOffsets affixOffsets) {
VirtualFile virtualFile = injectedFile == null ? null : injectedFile.getVirtualFile();
if (virtualFile instanceof VirtualFileWindow) {
Project project = injectedFile.getProject();
DocumentWindow documentWindow = ((VirtualFileWindow)virtualFile).getDocumentWindow();
if (psi) {
documentWindow = (DocumentWindow) ((PsiDocumentManagerBase) PsiDocumentManager.getInstance(project)).getLastCommittedDocument(documentWindow);
}
int start = documentWindow.hostToInjected(hostRange.getStartOffset());
int end = documentWindow.hostToInjected(hostRange.getEndOffset());
if (affixOffsets != null) {
return affixOffsets.expandRangeToAffixes(start, end, InjectedLanguageManager.getInstance(project).getNonEditableFragments(documentWindow));
}
return ProperTextRange.create(start, end);
}
return null;
}
@Override
public void cleanup() {
SmartPointerManager.getInstance(getProject()).removePointer(myInjectedFileRangeInHostFile);
}
@Nullable
@Override
public Document getDocumentToSynchronize() {
return ((SmartPsiElementPointerImpl)myHostContext).getElementInfo().getDocumentToSynchronize();
}
@Override
public int elementHashCode() {
return ((SmartPsiElementPointerImpl)myHostContext).getElementInfo().elementHashCode();
}
@NotNull
@Override
public Project getProject() {
return myHostContext.getProject();
}
@Override
public String toString() {
return "injected{type=" + myType + ", range=" + myInjectedFileRangeInHostFile + ", host=" + myHostContext + "}";
}
private static class AffixOffsets {
final int startAffixIndex;
final int startAffixOffset;
final int endAffixIndex;
final int endAffixOffset;
AffixOffsets(int startAffixIndex, int startAffixOffset, int endAffixIndex, int endAffixOffset) {
this.startAffixIndex = startAffixIndex;
this.startAffixOffset = startAffixOffset;
this.endAffixIndex = endAffixIndex;
this.endAffixOffset = endAffixOffset;
}
@Nullable
ProperTextRange expandRangeToAffixes(int start, int end, List<TextRange> fragments) {
if (startAffixIndex >= 0) {
TextRange fragment = startAffixIndex < fragments.size() ? fragments.get(startAffixIndex) : null;
if (fragment == null || startAffixOffset > fragment.getLength()) return null;
start = fragment.getStartOffset() + startAffixOffset;
}
if (endAffixIndex >= 0) {
TextRange fragment = endAffixIndex < fragments.size() ? fragments.get(endAffixIndex) : null;
if (fragment == null || endAffixOffset > fragment.getLength()) return null;
end = fragment.getStartOffset() + endAffixOffset;
}
return ProperTextRange.create(start, end);
}
}
}