blob: 58a9463b755df825efb95f1d8c731dc7fd5f2809 [file] [log] [blame]
/*
* Copyright 2000-2009 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.source.tree.injected;
import com.intellij.injected.editor.DocumentWindow;
import com.intellij.injected.editor.DocumentWindowImpl;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.lang.Language;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.FreeThreadedFileViewProvider;
import com.intellij.psi.impl.source.tree.MarkersHolderFileViewProvider;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author cdr
*/
public class InjectedFileViewProvider extends SingleRootFileViewProvider implements FreeThreadedFileViewProvider,
MarkersHolderFileViewProvider {
private Project myProject;
private final Object myLock = new Object();
private final DocumentWindowImpl myDocumentWindow;
private static final ThreadLocal<Boolean> disabledTemporarily = new ThreadLocal<Boolean>(){
@Override
protected Boolean initialValue() {
return false;
}
};
private boolean myPatchingLeaves;
InjectedFileViewProvider(@NotNull PsiManager psiManager,
@NotNull VirtualFileWindow virtualFile,
@NotNull DocumentWindowImpl documentWindow,
@NotNull Language language) {
super(psiManager, (VirtualFile)virtualFile, true, language);
myDocumentWindow = documentWindow;
myProject = documentWindow.getShreds().getHostPointer().getProject();
}
@Override
public void rootChanged(@NotNull PsiFile psiFile) {
super.rootChanged(psiFile);
if (!isPhysical()) return; // injected PSI change happened inside reparse; ignore
if (myPatchingLeaves) return;
DocumentWindowImpl documentWindow = myDocumentWindow;
List<PsiLanguageInjectionHost.Shred> shreds = documentWindow.getShreds();
assert documentWindow.getHostRanges().length == shreds.size();
String[] changes = documentWindow.calculateMinEditSequence(psiFile.getNode().getText());
assert changes.length == shreds.size();
for (int i = 0; i < changes.length; i++) {
String change = changes[i];
if (change != null) {
PsiLanguageInjectionHost.Shred shred = shreds.get(i);
PsiLanguageInjectionHost host = shred.getHost();
TextRange rangeInsideHost = shred.getRangeInsideHost();
String newHostText = StringUtil.replaceSubstring(host.getText(), rangeInsideHost, change);
//shred.host =
host.updateText(newHostText);
}
}
}
@Override
public FileViewProvider clone() {
final DocumentWindow oldDocumentWindow = ((VirtualFileWindow)getVirtualFile()).getDocumentWindow();
Document hostDocument = oldDocumentWindow.getDelegate();
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getManager().getProject());
PsiFile hostFile = documentManager.getPsiFile(hostDocument);
Language language = getBaseLanguage();
PsiFile file = getPsi(language);
final Language hostFileLanguage = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file).getLanguage();
PsiFile hostPsiFileCopy = (PsiFile)hostFile.copy();
Segment firstTextRange = oldDocumentWindow.getHostRanges()[0];
PsiElement hostElementCopy = hostPsiFileCopy.getViewProvider().findElementAt(firstTextRange.getStartOffset(), hostFileLanguage);
assert hostElementCopy != null;
final Ref<FileViewProvider> provider = new Ref<FileViewProvider>();
PsiLanguageInjectionHost.InjectedPsiVisitor visitor = new PsiLanguageInjectionHost.InjectedPsiVisitor() {
@Override
public void visit(@NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) {
Document document = documentManager.getCachedDocument(injectedPsi);
if (document instanceof DocumentWindowImpl && oldDocumentWindow.areRangesEqual((DocumentWindowImpl)document)) {
provider.set(injectedPsi.getViewProvider());
}
}
};
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);
}
finally {
current.putUserData(LANGUAGE_FOR_INJECTED_COPY_KEY, null);
}
if (provider.get() != null) break;
}
return provider.get();
}
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) {
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;
}
}
}
private static boolean same(Place oldShreds, Place newShreds) {
if (oldShreds == newShreds) return true;
if (oldShreds.size() != newShreds.size()) return false;
for (int i = 0; i < oldShreds.size(); i++) {
PsiLanguageInjectionHost.Shred oldShred = oldShreds.get(i);
PsiLanguageInjectionHost.Shred newShred = newShreds.get(i);
if (!oldShred.equals(newShred)) return false;
}
return true;
}
boolean isValid() {
return getShreds().isValid();
}
boolean isDisposed() {
synchronized (myLock) {
return myProject.isDisposed();
}
}
Place getShreds() {
return myDocumentWindow.getShreds();
}
@Override
@NotNull
public DocumentWindow getDocument() {
return myDocumentWindow;
}
@Override
public boolean isEventSystemEnabled() {
if (myLock == null) return true; // hack to avoid NPE when this method called from super class constructor
return !disabledTemporarily.get();
}
@Override
public boolean isPhysical() {
return isEventSystemEnabled();
}
public void performNonPhysically(Runnable runnable) {
synchronized (myLock) {
disabledTemporarily.set(true);
try {
runnable.run();
}
finally {
disabledTemporarily.set(false);
}
}
}
@NonNls
@Override
public String toString() {
return "Injected file '"+getVirtualFile().getName()+"' " + (isValid() ? "" : " invalid") + (isPhysical() ? "" : " nonphysical");
}
public void setPatchingLeaves(boolean patchingLeaves) {
myPatchingLeaves = patchingLeaves;
}
@Override
@NotNull
public RangeMarker[] getCachedMarkers() {
List<RangeMarker> markers = new SmartList<RangeMarker>();
for (PsiLanguageInjectionHost.Shred shred : myDocumentWindow.getShreds()) {
RangeMarker marker = (RangeMarker)shred.getHostRangeMarker();
if (marker != null) {
markers.add(marker);
}
}
return markers.toArray(new RangeMarker[markers.size()]);
}
}