usage view memory optimization

This commit is contained in:
Alexey Kudravtsev
2011-02-04 19:06:49 +03:00
parent 3ea5ba50fd
commit 54c000b5c0
45 changed files with 1347 additions and 872 deletions
@@ -15,12 +15,11 @@
*/
package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.xml.XmlToken;
import com.intellij.psi.xml.XmlTokenType;
import org.jetbrains.annotations.NotNull;
@@ -29,72 +28,21 @@ import org.jetbrains.annotations.Nullable;
/**
* User: cdr
*/
class AnchorElementInfo implements SmartPointerElementInfo {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.AnchorElementInfo");
private PsiFile myFile;
private final RangeMarker myMarker;
private int mySyncStartOffset;
private int mySyncEndOffset;
private boolean mySyncMarkerIsValid;
private final Project myProject;
AnchorElementInfo(@NotNull PsiElement anchor) {
LOG.assertTrue(anchor.isPhysical());
LOG.assertTrue(anchor.isValid());
myFile = anchor.getContainingFile();
myProject = myFile.getProject();
TextRange range = anchor.getTextRange();
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
Document document = documentManager.getDocument(myFile);
LOG.assertTrue(!documentManager.isUncommited(document));
if (myFile.getTextLength() != document.getTextLength()) {
final String docText = document.getText();
myFile.accept(new PsiRecursiveElementWalkingVisitor() {
@Override
public void visitElement(PsiElement element) {
super.visitElement(element);
TextRange elementRange = element.getTextRange();
final String rangeText = docText.length() <= elementRange.getEndOffset() ? "(IOOBE: "+docText.length()+" is out of "+elementRange+")" : elementRange.substring(docText);
final String elemText = element.getText();
if (!rangeText.equals(elemText)) {
throw new AssertionError("PSI text doesn't equal to the document's one: element" + element + "\ndocText=" + rangeText + "\npsiText" + elemText);
}
}
});
LOG.error("File=" + myFile);
}
myMarker = document.createRangeMarker(range.getStartOffset(), range.getEndOffset(), true);
mySyncStartOffset = range.getStartOffset();
mySyncEndOffset = range.getEndOffset();
mySyncMarkerIsValid = true;
}
public Document getDocumentToSynchronize() {
return myMarker.getDocument();
}
public void documentAndPsiInSync() {
if (!myMarker.isValid()) {
mySyncMarkerIsValid = false;
return;
}
mySyncStartOffset = myMarker.getStartOffset();
mySyncEndOffset = myMarker.getEndOffset();
class AnchorElementInfo extends SelfElementInfo {
AnchorElementInfo(@NotNull PsiElement anchor, PsiFile containingFile) {
super(anchor, containingFile);
}
@Nullable
public PsiElement restoreElement() {
if (!mySyncMarkerIsValid) return null;
myFile = SelfElementInfo.restoreFile(myFile, myProject);
if (myFile == null) return null;
PsiElement anchor = myFile.findElementAt(mySyncStartOffset);
PsiFile file = SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject);
if (file == null) return null;
PsiElement anchor = file.findElementAt(getSyncStartOffset());
if (anchor == null) return null;
TextRange range = anchor.getTextRange();
if (range.getStartOffset() != mySyncStartOffset || range.getEndOffset() != mySyncEndOffset) return null;
if (range.getStartOffset() != getSyncStartOffset() || range.getEndOffset() != getSyncEndOffset()) return null;
if (anchor instanceof PsiIdentifier) {
PsiElement parent = anchor.getParent();
@@ -106,20 +54,10 @@ class AnchorElementInfo implements SmartPointerElementInfo {
return parent;
}
else if (anchor instanceof XmlToken) {
if (anchor instanceof XmlToken) {
XmlToken token = (XmlToken)anchor;
return token.getTokenType() == XmlTokenType.XML_NAME ? token.getParent() : null;
}
else {
return null;
}
}
@Override
public void dispose() {
if (myMarker != null) {
myMarker.dispose();
}
return null;
}
}
@@ -29,7 +29,7 @@ public class AnchorElementInfoFactory implements SmartPointerElementInfoFactory
public SmartPointerElementInfo createElementInfo(@NotNull PsiElement element) {
PsiElement anchor = getAnchor(element);
if (anchor != null) {
return new AnchorElementInfo(anchor);
return new AnchorElementInfo(anchor, element.getContainingFile());
}
return null;
}
@@ -55,7 +55,7 @@ public class AnchorElementInfoFactory implements SmartPointerElementInfoFactory
else if (element instanceof XmlTag) {
anchor = XmlTagUtil.getStartTagNameElement((XmlTag)element);
}
if (anchor != null && !anchor.isPhysical()) return null;
if (anchor != null && (!anchor.isPhysical() || anchor.getTextRange()==null)) return null;
return anchor;
}
@@ -15,15 +15,20 @@
*/
package com.intellij.psi.impl.smartPointers;
import org.jetbrains.annotations.Nullable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ImplicitVariable;
import com.intellij.psi.PsiIdentifier;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.ImplicitVariable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.util.PsiUtilBase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ImplicitVariableElementInfoFactory implements SmartPointerElementInfoFactory {
@Nullable
public SmartPointerElementInfo createElementInfo(final PsiElement element) {
public SmartPointerElementInfo createElementInfo(@NotNull final PsiElement element) {
if (element instanceof ImplicitVariable) {
return new ImplicitVariableInfo((ImplicitVariable) element);
}
@@ -54,5 +59,38 @@ public class ImplicitVariableElementInfoFactory implements SmartPointerElementIn
public void documentAndPsiInSync() {
}
@Override
public void fastenBelt(int offset) {
}
@Override
public void unfastenBelt(int offset) {
}
@Override
public int elementHashCode() {
return myVar.hashCode();
}
@Override
public boolean pointsToTheSameElementAs(SmartPointerElementInfo other) {
if (other instanceof ImplicitVariableInfo) {
return myVar == ((ImplicitVariableInfo)other).myVar;
}
return Comparing.equal(restoreElement(), other.restoreElement());
}
@Override
public VirtualFile getVirtualFile() {
return PsiUtilBase.getVirtualFile(myVar);
}
@Override
public Segment getSegment() {
PsiIdentifier psiIdentifier = myVar.getNameIdentifier();
if (psiIdentifier == null || !psiIdentifier.isValid()) return null;
return psiIdentifier.getTextRange();
}
}
}
@@ -15,42 +15,30 @@
*/
package com.intellij.psi.impl.smartPointers;
import org.jetbrains.annotations.Nullable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.PsiImportList;
import com.intellij.openapi.editor.Document;
import com.intellij.psi.PsiJavaFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ImportListElementInfoFactory implements SmartPointerElementInfoFactory {
@Nullable
public SmartPointerElementInfo createElementInfo(final PsiElement element) {
public SmartPointerElementInfo createElementInfo(@NotNull final PsiElement element) {
if (element instanceof PsiImportList) {
return new ImportListInfo((PsiJavaFile)element.getContainingFile());
}
return null;
}
private static class ImportListInfo implements SmartPointerElementInfo {
private final PsiJavaFile myFile;
public ImportListInfo(PsiJavaFile file) {
myFile = file;
private static class ImportListInfo extends FileElementInfo {
public ImportListInfo(@NotNull PsiJavaFile file) {
super(file);
}
public PsiElement restoreElement() {
if (!myFile.isValid()) return null;
return myFile.getImportList();
}
@Override
public void dispose() {
}
public Document getDocumentToSynchronize() {
return null;
}
public void documentAndPsiInSync() {
PsiElement element = super.restoreElement();
if (!(element instanceof PsiJavaFile)) return null;
return ((PsiJavaFile)element).getImportList();
}
}
}
@@ -100,20 +100,16 @@ public class ClassGroupingRule implements UsageGroupingRule {
private final SmartPsiElementPointer myClassPointer;
private final String myText;
private final String myQName;
private Icon myIcon;
private final Icon myIcon;
public ClassUsageGroup(PsiClass aClass) {
myQName = aClass.getQualifiedName();
myText = createText(aClass);
SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(aClass.getProject());
myClassPointer = smartPointerManager.createLazyPointer(aClass);
update();
myClassPointer = SmartPointerManager.getInstance(aClass.getProject()).createSmartPsiElementPointer(aClass);
myIcon = getPsiClass().getIcon(Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS);
}
public void update() {
if (isValid()) {
myIcon = getPsiClass().getIcon(Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS);
}
}
private static String createText(PsiClass aClass) {
@@ -23,6 +23,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
@@ -69,7 +70,7 @@ public class MethodGroupingRule implements UsageGroupingRule {
private static class MethodUsageGroup implements UsageGroup, TypeSafeDataProvider {
private final SmartPsiElementPointer<PsiMethod> myMethodPointer;
private final String myName;
private Icon myIcon;
private final Icon myIcon;
private final Project myProject;
public MethodUsageGroup(PsiMethod psiMethod) {
@@ -80,15 +81,12 @@ public class MethodGroupingRule implements UsageGroupingRule {
PsiFormatUtilBase.SHOW_TYPE
);
myProject = psiMethod.getProject();
myMethodPointer = SmartPointerManager.getInstance(myProject).createLazyPointer(psiMethod);
myMethodPointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(psiMethod);
myIcon = getIconImpl(psiMethod);
}
public void update() {
if (isValid()) {
myIcon = getIconImpl(getMethod());
}
}
private static Icon getIconImpl(PsiMethod psiMethod) {
@@ -104,10 +102,8 @@ public class MethodGroupingRule implements UsageGroupingRule {
return false;
}
MethodUsageGroup group = (MethodUsageGroup) object;
if (isValid() && group.isValid()) {
return getMethod().getManager().areElementsEquivalent(getMethod(), group.getMethod());
}
return Comparing.equal(myName, ((MethodUsageGroup)object).myName);
return Comparing.equal(myName, ((MethodUsageGroup)object).myName)
&& SmartPointerManager.getInstance(myProject).pointToTheSameElement(myMethodPointer, group.myMethodPointer);
}
public Icon getIcon(boolean isOpen) {
@@ -151,10 +147,15 @@ public class MethodGroupingRule implements UsageGroupingRule {
LOG.error("MethodUsageGroup expected but " + usageGroup.getClass() + " found");
}
MethodUsageGroup other = (MethodUsageGroup)usageGroup;
PsiMethod myMethod = myMethodPointer.getElement();
PsiMethod otherMethod = other.myMethodPointer.getElement();
if (myMethod != null && otherMethod != null && !UsageViewSettings.getInstance().IS_SORT_MEMBERS_ALPHABETICALLY) {
return myMethod.getTextOffset() < otherMethod.getTextOffset() ? -1 : 1;
if (SmartPointerManager.getInstance(myProject).pointToTheSameElement(myMethodPointer, other.myMethodPointer)) {
return 0;
}
if (!UsageViewSettings.getInstance().IS_SORT_MEMBERS_ALPHABETICALLY) {
Segment segment1 = myMethodPointer.getSegment();
Segment segment2 = other.myMethodPointer.getSegment();
if (segment1 != null && segment2 != null) {
return segment1.getStartOffset() - segment2.getStartOffset();
}
}
return myName.compareTo(other.myName);
@@ -20,14 +20,15 @@
package com.intellij.psi;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
public class IdentitySmartPointer<T extends PsiElement> implements SmartPsiElementPointer<T> {
private T myElement;
private final PsiFile myFile;
public IdentitySmartPointer(T element, PsiFile file) {
public IdentitySmartPointer(@NotNull T element, @NotNull PsiFile file) {
myElement = element;
myFile = file;
}
@@ -41,11 +42,17 @@ public class IdentitySmartPointer<T extends PsiElement> implements SmartPsiEleme
return myFile.getProject();
}
@Override
public VirtualFile getVirtualFile() {
return myFile.getVirtualFile();
}
public T getElement() {
if (myElement != null && !myElement.isValid()) {
myElement = null;
T element = myElement;
if (element != null && !element.isValid()) {
element = null;
}
return myElement;
return element;
}
public int hashCode() {
@@ -54,10 +61,17 @@ public class IdentitySmartPointer<T extends PsiElement> implements SmartPsiEleme
}
public boolean equals(Object obj) {
return obj instanceof SmartPsiElementPointer && Comparing.equal(getElement(), ((SmartPsiElementPointer)obj).getElement());
return obj instanceof SmartPsiElementPointer
&& SmartPointerManager.getInstance(getProject()).pointToTheSameElement(this, (SmartPsiElementPointer)obj);
}
public PsiFile getContainingFile() {
return myFile;
}
@Override
public Segment getSegment() {
T element = myElement;
return element == null ? null : element.getTextRange();
}
}
@@ -39,9 +39,17 @@ public abstract class SmartPointerManager {
/**
* Creates a smart pointer to the specified PSI element which doesn't hold a strong reference to the PSI
* element.
*
* @deprecated use {@link #createSmartPsiElementPointer(PsiElement)} instead
* @param element the element to create a pointer to.
* @return the smart pointer instance.
*/
@NotNull public abstract <E extends PsiElement> SmartPsiElementPointer<E> createLazyPointer(@NotNull E element);
/**
* @param pointer1 smart pointer to compare
* @param pointer2 smart pointer to compare
* @return true if both pointers point to the same PSI element.
* This method is cheaper than dereferencing both pointers and comparing the result.
*/
public abstract boolean pointToTheSameElement(@NotNull SmartPsiElementPointer pointer1, @NotNull SmartPsiElementPointer pointer2);
}
@@ -16,6 +16,8 @@
package com.intellij.psi;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,4 +43,8 @@ public interface SmartPsiElementPointer<E extends PsiElement> {
@NotNull
Project getProject();
VirtualFile getVirtualFile();
Segment getSegment();
}
@@ -23,7 +23,6 @@ import com.intellij.openapi.fileTypes.NativeFileType;
import com.intellij.openapi.fileTypes.UnknownFileType;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.UserDataHolderBase;
@@ -31,6 +30,8 @@ import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.ui.IconDeferrer;
import com.intellij.ui.LayeredIcon;
import com.intellij.ui.RowIcon;
@@ -48,6 +49,14 @@ public abstract class ElementBase extends UserDataHolderBase implements Iconable
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.ElementBase");
public static final int FLAGS_LOCKED = 0x800;
private static final NullableFunction<ElementIconRequest,Icon> ICON_COMPUTE = new NullableFunction<ElementIconRequest, Icon>() {
public Icon fun(ElementIconRequest request) {
final PsiElement element = request.getElement();
if (!element.isValid()) return null;
if (element.getProject().isDisposed()) return null;
return computeIconNow(element, request.getFlags());
}
};
private TIntObjectHashMap<Icon> myBaseIcon;
private static final Icon VISIBILITY_ICON_PLACHOLDER = new EmptyIcon(Icons.PUBLIC_ICON);
@@ -88,18 +97,10 @@ public abstract class ElementBase extends UserDataHolderBase implements Iconable
baseIcon = myBaseIcon.get(flags);
}
final Project project = psiElement.getProject();
if (isToDeferIconLoading()) {
return IconDeferrer.getInstance().defer(baseIcon, new ElementIconRequest(psiElement, flags), new NullableFunction<ElementIconRequest, Icon>() {
public Icon fun(ElementIconRequest request) {
if (project.isDisposed()) return null;
final PsiElement element = request.getElement();
if (!element.isValid()) return null;
if (element.getProject().isDisposed()) return null;
return computeIconNow(element, request.getFlags());
}
});
} else {
return IconDeferrer.getInstance().defer(baseIcon, new ElementIconRequest(psiElement, flags), ICON_COMPUTE);
}
else {
if (!psiElement.isValid()) return null;
return computeIconNow(psiElement, flags);
}
@@ -110,12 +111,12 @@ public abstract class ElementBase extends UserDataHolderBase implements Iconable
}
@Nullable
private Icon computeIconNow(PsiElement element, int flags) {
private static Icon computeIconNow(PsiElement element, int flags) {
final Icon providersIcon = PsiIconUtil.getProvidersIcon(element, flags);
if (providersIcon != null) {
return providersIcon instanceof RowIcon ? (RowIcon)providersIcon : createLayeredIcon(providersIcon, flags);
}
return getElementIcon(flags);
return ((ElementBase)element).getElementIcon(flags);
}
protected Icon computeBaseIcon(int flags) {
@@ -177,11 +178,16 @@ public abstract class ElementBase extends UserDataHolderBase implements Iconable
public static class ElementIconRequest extends ComparableObject.Impl {
public ElementIconRequest(PsiElement element, int flags) {
super(new Object[] {element, flags});
super(new Object[] {createPointer(element), flags});
}
private static Object createPointer(PsiElement element) {
return SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element);
}
public PsiElement getElement() {
return (PsiElement)getEqualityObjects()[0];
SmartPsiElementPointer pointer = (SmartPsiElementPointer)getEqualityObjects()[0];
return pointer.getElement();
}
public int getFlags() {
@@ -15,11 +15,12 @@
*/
package com.intellij.usageView;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
@@ -29,8 +30,7 @@ import org.jetbrains.annotations.Nullable;
public class UsageInfo {
public static final UsageInfo[] EMPTY_ARRAY = new UsageInfo[0];
private static final Logger LOG = Logger.getInstance("#com.intellij.usageView.UsageInfo");
private final SmartPsiElementPointer mySmartPointer;
private final VirtualFile myVirtualFile;
private final SmartPsiElementPointer<?> mySmartPointer;
public final int startOffset; // in navigation element
public final int endOffset; // in navigation element
@@ -38,9 +38,19 @@ public class UsageInfo {
public UsageInfo(@NotNull PsiElement element, int startOffset, int endOffset, boolean isNonCodeUsage) {
LOG.assertTrue(element.isValid());
LOG.assertTrue(element == element.getNavigationElement());
mySmartPointer = SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element);
myVirtualFile = element.getContainingFile().getVirtualFile();
element = element.getNavigationElement();
SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(element.getProject());
mySmartPointer = smartPointerManager.createSmartPsiElementPointer(element);
if (startOffset == -1 && endOffset == -1) {
// calculate natural element range
TextRange range = element.getTextRange();
if (range == null) {
LOG.error("text range null for " + element + "; " + element.getClass());
}
startOffset = element.getTextOffset() - range.getStartOffset();
endOffset = range.getEndOffset() - range.getStartOffset();
}
this.startOffset = startOffset;
this.endOffset = endOffset;
this.isNonCodeUsage = isNonCodeUsage;
@@ -49,22 +59,7 @@ public class UsageInfo {
}
public UsageInfo(@NotNull PsiElement element, boolean isNonCodeUsage) {
LOG.assertTrue(element.isValid());
element = element.getNavigationElement();
mySmartPointer = SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element);
myVirtualFile = element.getContainingFile().getVirtualFile();
TextRange range = element.getTextRange();
if (range == null) {
LOG.error("text range null for " + element+"; "+element.getClass());
}
startOffset = element.getTextOffset() - range.getStartOffset();
endOffset = range.getEndOffset() - range.getStartOffset();
this.isNonCodeUsage = isNonCodeUsage;
LOG.assertTrue(startOffset >= 0, startOffset);
LOG.assertTrue(endOffset >= startOffset, endOffset-startOffset);
this(element, -1, -1, isNonCodeUsage);
}
public UsageInfo(@NotNull PsiElement element, int startOffset, int endOffset) {
@@ -105,15 +100,25 @@ public class UsageInfo {
}
public final void navigateTo(boolean requestFocus) {
PsiElement element = getElement();
if (element == null) return;
VirtualFile file = element.getContainingFile().getVirtualFile();
TextRange range = element.getTextRange();
int offset = range.getStartOffset() + startOffset;
Project project = element.getProject();
int offset = getNavigationOffset();
VirtualFile file = getVirtualFile();
Project project = getProject();
FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, file, offset), requestFocus);
}
public int getNavigationOffset() {
PsiElement element = getElement();
if (element == null) return -1;
TextRange range = element.getTextRange();
return range.getStartOffset() + startOffset;
}
public Segment getSegment() {
PsiElement element = getElement();
if (element == null) return null;
TextRange range = element.getTextRange();
return new TextRange(range.getStartOffset() + startOffset, Math.min(range.getEndOffset(), range.getStartOffset() + endOffset));
}
public Project getProject() {
return mySmartPointer.getProject();
}
@@ -132,9 +137,8 @@ public class UsageInfo {
if (endOffset != usageInfo.endOffset) return false;
if (isNonCodeUsage != usageInfo.isNonCodeUsage) return false;
if (startOffset != usageInfo.startOffset) return false;
PsiElement thisElement = mySmartPointer.getElement();
PsiElement thatElement = usageInfo.mySmartPointer.getElement();
return Comparing.equal(thisElement, thatElement);
return SmartPointerManager.getInstance(getProject()).pointToTheSameElement(mySmartPointer, usageInfo.mySmartPointer);
}
public int hashCode() {
@@ -150,7 +154,12 @@ public class UsageInfo {
return mySmartPointer.getContainingFile();
}
@Nullable
public VirtualFile getVirtualFile() {
return myVirtualFile;
return mySmartPointer.getVirtualFile();
}
public void dispose() {
((Disposable)mySmartPointer).dispose();
}
}
@@ -29,6 +29,7 @@ import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ui.RangeBlinker;
import org.jetbrains.annotations.NonNls;
@@ -78,23 +79,28 @@ class IntentionUsagePanel extends JPanel{
}
private void setupSpots(Document document) {
List<RangeMarker> markers = new ArrayList<RangeMarker>();
List<Segment> markers = new ArrayList<Segment>();
while (true) {
String text = document.getText();
int spotStart = text.indexOf("<" + SPOT_MARKER + ">");
final int spotStart = text.indexOf("<" + SPOT_MARKER + ">");
if (spotStart < 0) break;
int spotEnd = text.indexOf("</" + SPOT_MARKER + ">", spotStart);
final int spotEnd = text.indexOf("</" + SPOT_MARKER + ">", spotStart);
if (spotEnd < 0) break;
document.deleteString(spotEnd, spotEnd + SPOT_MARKER.length() + 3);
document.deleteString(spotStart, spotStart + SPOT_MARKER.length() + 2);
final RangeMarker spotMarker = document.createRangeMarker(spotStart, spotEnd - SPOT_MARKER.length() - 2);
if (spotMarker == null) {
break;
}
else {
markers.add(spotMarker);
}
Segment spotMarker = new Segment() {
@Override
public int getStartOffset() {
return spotStart;
}
@Override
public int getEndOffset() {
return spotEnd - SPOT_MARKER.length() - 2;
}
};
markers.add(spotMarker);
}
myRangeBlinker.resetMarkers(markers);
if (!markers.isEmpty()) {
@@ -29,11 +29,11 @@ import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -322,37 +322,40 @@ public class ReplaceInProjectManager {
return;
}
List<RangeMarker> markers = ((UsageInfo2UsageAdapter)usage).getRangeMarkers();
for (RangeMarker marker : markers) {
Document document = marker.getDocument();
if (!document.isWritable()) return;
final Document document = ((UsageInfo2UsageAdapter)usage).getDocument();
((UsageInfo2UsageAdapter)usage).processRangeMarkers(new Processor<Segment>() {
@Override
public boolean process(Segment segment) {
if (!document.isWritable()) return false;
final int textOffset = marker.getStartOffset();
if (textOffset < 0 || textOffset >= document.getTextLength()) {
return;
final int textOffset = segment.getStartOffset();
if (textOffset < 0 || textOffset >= document.getTextLength()) {
return true;
}
final int textEndOffset = segment.getEndOffset();
if (textEndOffset < 0 || textOffset > document.getTextLength()) {
return true;
}
FindManager findManager = FindManager.getInstance(myProject);
final CharSequence foundString = document.getCharsSequence().subSequence(textOffset, textEndOffset);
FindResult findResult = findManager.findString(document.getCharsSequence(), textOffset, replaceContext.getFindModel());
if (!findResult.isStringFound()) {
return true;
}
String stringToReplace = null;
try {
stringToReplace =
findManager.getStringToReplace(foundString.toString(), replaceContext.getFindModel(), textOffset, document.getText());
}
catch (FindManager.MalformedReplacementStringException e) {
Messages.showErrorDialog(myProject, e.getMessage(), FindBundle.message("find.replace.invalid.replacement.string.title"));
}
if (stringToReplace != null) {
document.replaceString(textOffset, textEndOffset, stringToReplace);
}
return true;
}
final int textEndOffset = marker.getEndOffset();
if (textEndOffset < 0 || textOffset > document.getTextLength()) {
return;
}
FindManager findManager = FindManager.getInstance(myProject);
final CharSequence foundString = document.getCharsSequence().subSequence(textOffset, textEndOffset);
FindResult findResult = findManager.findString(document.getCharsSequence(), textOffset, replaceContext.getFindModel());
if (!findResult.isStringFound()) {
return;
}
String stringToReplace = null;
try {
stringToReplace =
findManager.getStringToReplace(foundString.toString(), replaceContext.getFindModel(), textOffset, document.getText());
}
catch (FindManager.MalformedReplacementStringException e) {
Messages.showErrorDialog(myProject, e.getMessage(), FindBundle.message("find.replace.invalid.replacement.string.title"));
}
if (stringToReplace != null) {
document.replaceString(textOffset, textEndOffset, stringToReplace);
}
}
});
}
});
}
@@ -30,15 +30,13 @@ import com.intellij.psi.PsiDirectoryContainer;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.usages.Usage;
import com.intellij.usages.UsageDataUtil;
import com.intellij.usages.UsageTarget;
import com.intellij.usages.UsageView;
import com.intellij.usages.rules.UsageInFile;
import com.intellij.usages.rules.UsageInFiles;
import com.intellij.util.containers.ContainerUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class VirtualFileArrayRule implements GetDataRule {
public Object getData(final DataProvider dataProvider) {
@@ -84,44 +82,13 @@ public class VirtualFileArrayRule implements GetDataRule {
Usage[] usages = UsageView.USAGES_KEY.getData(dataProvider);
UsageTarget[] usageTargets = UsageView.USAGE_TARGETS_KEY.getData(dataProvider);
if (usages != null || usageTargets != null) {
return getFilesFromUsages(usages, usageTargets);
if (usages != null || usageTargets != null) {
return UsageDataUtil.provideVirtualFileArray(usages, usageTargets);
}
return null;
}
private static VirtualFile[] getFilesFromUsages(Usage[] usages, UsageTarget[] usageTargets) {
Set<VirtualFile> result = new HashSet<VirtualFile>();
if (usages != null) {
for (Usage usage : usages) {
if (!usage.isValid()) continue;
if (usage instanceof UsageInFile) {
UsageInFile usageInFile = (UsageInFile)usage;
result.add(usageInFile.getFile());
}
if (usage instanceof UsageInFiles) {
UsageInFiles usageInFiles = (UsageInFiles)usage;
ContainerUtil.addAll(result, usageInFiles.getFiles());
}
}
}
if (usageTargets != null) {
for (UsageTarget usageTarget : usageTargets) {
if (!usageTarget.isValid()) continue;
VirtualFile[] files = usageTarget.getFiles();
if (files != null) {
ContainerUtil.addAll(result, files);
}
}
}
return VfsUtil.toVirtualFileArray(result);
}
private static Object getFilesFromPsiElement(PsiElement elem) {
if (elem instanceof PsiFile) {
@@ -21,10 +21,12 @@ import com.intellij.lang.Language;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.NullableComputable;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.impl.smartPointers.SelfElementInfo;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.impl.source.PsiFileWithStubSupport;
import com.intellij.psi.stubs.IStubElementType;
@@ -61,6 +63,8 @@ public abstract class PsiAnchor {
if (file == null) {
return new HardReference(element);
}
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null) return new HardReference(element);
if (element instanceof StubBasedPsiElement && element.isPhysical() && (element instanceof PsiCompiledElement || ((PsiFileImpl)file).getContentElementType() instanceof IStubFileElementType)) {
final StubBasedPsiElement elt = (StubBasedPsiElement)element;
@@ -88,7 +92,7 @@ public abstract class PsiAnchor {
}
if (lang == null) lang = element.getLanguage();
return new TreeRangeReference(file, textRange.getStartOffset(), textRange.getEndOffset(), element.getClass(), lang);
return new TreeRangeReference(file, textRange.getStartOffset(), textRange.getEndOffset(), element.getClass(), lang, virtualFile);
}
private static int calcStubIndex(StubBasedPsiElement psi) {
@@ -112,16 +116,22 @@ public abstract class PsiAnchor {
return -1; // it is possible via custom stub builder intentionally not producing stubs for stubbed elements
}
private static class TreeRangeReference extends PsiAnchor {
private final PsiFile myFile;
private final VirtualFile myVirtualFile;
private final Project myProject;
private final Language myLanguage;
private final int myStartOffset;
private final int myEndOffset;
private final Class myClass;
private TreeRangeReference(final PsiFile file, final int startOffset, final int endOffset, final Class aClass, final Language language) {
myFile = file;
private TreeRangeReference(@NotNull PsiFile file,
int startOffset,
int endOffset,
@NotNull Class aClass,
@NotNull Language language,
@NotNull VirtualFile virtualFile) {
myVirtualFile = virtualFile;
myProject = file.getProject();
myStartOffset = startOffset;
myEndOffset = endOffset;
myClass = aClass;
@@ -130,7 +140,9 @@ public abstract class PsiAnchor {
@Nullable
public PsiElement retrieve() {
PsiElement element = myFile.getViewProvider().findElementAt(myStartOffset, myLanguage);
PsiFile psiFile = getFile();
if (psiFile == null || !psiFile.isValid()) return null;
PsiElement element = psiFile.getViewProvider().findElementAt(myStartOffset, myLanguage);
if (element == null) return null;
while (!element.getClass().equals(myClass) ||
@@ -144,7 +156,7 @@ public abstract class PsiAnchor {
}
public PsiFile getFile() {
return myFile;
return SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject);
}
public int getStartOffset() {
@@ -161,24 +173,26 @@ public abstract class PsiAnchor {
final TreeRangeReference that = (TreeRangeReference)o;
if (myEndOffset != that.myEndOffset) return false;
if (myStartOffset != that.myStartOffset) return false;
if (myClass != null ? !myClass.equals(that.myClass) : that.myClass != null) return false;
if (myFile != null ? !myFile.equals(that.myFile) : that.myFile != null) return false;
return true;
return myEndOffset == that.myEndOffset &&
myStartOffset == that.myStartOffset &&
myClass.equals(that.myClass) &&
myVirtualFile.equals(that.myVirtualFile);
}
public int hashCode() {
int result = myClass != null ? myClass.getName().hashCode() : 0;
result = 31 * result + myStartOffset; //todo
result = 31 * result + myEndOffset;
if (myFile != null) {
result = 31 * result + myFile.getName().hashCode();
}
result = 31 * result + myVirtualFile.getName().hashCode();
return result;
}
@Override
public boolean pointsToTheSameElementAs(PsiAnchor other) {
if (other instanceof TreeRangeReference) return equals(other);
return Comparing.equal(retrieve(), other.retrieve());
}
}
private static class HardReference extends PsiAnchor {
@@ -217,9 +231,14 @@ public abstract class PsiAnchor {
public int hashCode() {
return myElement.hashCode();
}
@Override
public boolean pointsToTheSameElementAs(PsiAnchor other) {
return myElement == other.retrieve();
}
}
public static class StubIndexReference extends PsiAnchor {
private static class StubIndexReference extends PsiAnchor {
private final VirtualFile myVirtualFile;
private final Project myProject;
private final int myIndex;
@@ -313,6 +332,14 @@ public abstract class PsiAnchor {
if (resolved == null) throw new PsiInvalidElementAccessException(null);
return resolved.getTextRange().getEndOffset();
}
@Override
public boolean pointsToTheSameElementAs(PsiAnchor other) {
if (other instanceof StubIndexReference) return equals(other);
return Comparing.equal(retrieve(), other.retrieve());
}
}
public abstract boolean pointsToTheSameElementAs(PsiAnchor other);
}
@@ -16,6 +16,7 @@
package com.intellij.psi.impl;
import com.intellij.AppTopics;
import com.intellij.injected.editor.DocumentWindow;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.Application;
@@ -31,6 +32,7 @@ import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
@@ -48,6 +50,7 @@ import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.text.BlockSupport;
import com.intellij.util.SmartList;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -74,8 +77,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
private volatile boolean myIsCommitInProgress;
private final PsiToDocumentSynchronizer mySynchronizer;
private final List<Listener> myListeners = new ArrayList<Listener>();
private Listener[] myCachedListeners = null; //guarded by mylisteners
private final List<Listener> myListeners = ContainerUtil.createEmptyCOWList();
private final SmartPointerManagerImpl mySmartPointerManager;
public PsiDocumentManagerImpl(Project project,
@@ -91,6 +93,17 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
mySynchronizer = new PsiToDocumentSynchronizer(this, bus);
myPsiManager.addPsiTreeChangeListener(mySynchronizer);
editorFactory.getEventMulticaster().addDocumentListener(this, myProject);
bus.connect().subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() {
@Override
public void fileContentLoaded(final VirtualFile virtualFile, Document document) {
PsiFile psiFile = ApplicationManager.getApplication().runReadAction(new Computable<PsiFile>() {
public PsiFile compute() {
return getCachedPsiFile(virtualFile);
}
});
fireDocumentCreated(document, psiFile);
}
});
}
public void projectOpened() {
@@ -183,8 +196,6 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
}
}
fireDocumentCreated(document, file);
return document;
}
@@ -366,27 +377,12 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
}
}
private Listener[] getCachedListeners() {
synchronized (myListeners) {
if (myCachedListeners == null) {
myCachedListeners = myListeners.toArray(new Listener[myListeners.size()]);
}
return myCachedListeners;
}
}
public void addListener(@NotNull Listener listener) {
synchronized (myListeners) {
myListeners.add(listener);
myCachedListeners = null;
}
myListeners.add(listener);
}
public void removeListener(@NotNull Listener listener) {
synchronized (myListeners) {
myListeners.remove(listener);
myCachedListeners = null;
}
myListeners.remove(listener);
}
public boolean isDocumentBlockedByPsi(@NotNull Document doc) {
@@ -402,15 +398,13 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
}
private void fireDocumentCreated(Document document, PsiFile file) {
Listener[] listeners = getCachedListeners();
for (Listener listener : listeners) {
for (Listener listener : myListeners) {
listener.documentCreated(document, file);
}
}
private void fireFileCreated(Document document, PsiFile file) {
Listener[] listeners = getCachedListeners();
for (Listener listener : listeners) {
for (Listener listener : myListeners) {
listener.fileCreated(file, document);
}
}
@@ -425,11 +419,7 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
if (textBlock.isEmpty()) return false;
((DocumentImpl)document).normalizeRangeMarkers();
myIsCommitInProgress = true;
try{
if (mySmartPointerManager != null) { // mock tests
SmartPointerManagerImpl.synchronizePointers(file);
}
try {
myTreeElementBeingReparsedSoItWontBeCollected = ((PsiFileImpl)file).calcTreeElement();
if (textBlock.isEmpty()) return false ; // if tree was just loaded above textBlock will be cleared by contentsLoaded
@@ -479,9 +469,6 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
}
myBlockSupport.reparseRange(file, startOffset, endOffset, lengthShift, chars);
textBlock.unlock();
textBlock.clear();
if (file.getTextLength() != document.getTextLength()) {
if (ApplicationManagerEx.getApplicationEx().isInternal()) {
boolean x = false;
@@ -502,8 +489,15 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
}
}
finally {
textBlock.unlock();
textBlock.clear();
myTreeElementBeingReparsedSoItWontBeCollected = null;
myIsCommitInProgress = false;
if (mySmartPointerManager != null) { // mock tests
SmartPointerManagerImpl.synchronizePointers(file);
}
}
return true;
}
@@ -530,12 +524,18 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
final FileViewProvider provider = getCachedViewProvider(document);
if (provider == null) return;
if (provider.getVirtualFile().getFileType().isBinary()) return;
VirtualFile virtualFile = provider.getVirtualFile();
if (virtualFile.getFileType().isBinary()) return;
final List<PsiFile> files = provider.getAllFiles();
boolean hasLockedBlocks = false;
for (PsiFile file : files) {
if (file == null) continue;
if (file.isPhysical() && mySmartPointerManager != null) { // mock tests
SmartPointerManagerImpl.fastenBelts(file, event.getOffset());
}
final TextBlock textBlock = getTextBlock(document, file);
if (textBlock.isLocked()) {
hasLockedBlocks = true;
@@ -553,12 +553,6 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
myIsCommitInProgress = false;
}
}
if (file.isPhysical()) {
if (mySmartPointerManager != null) { // mock tests
SmartPointerManagerImpl.fastenBelts(file);
}
}
}
if (!hasLockedBlocks) {
@@ -570,19 +564,20 @@ public class PsiDocumentManagerImpl extends PsiDocumentManager implements Projec
final Document document = event.getDocument();
final FileViewProvider viewProvider = getCachedViewProvider(document);
if (viewProvider == null) return;
if (viewProvider.getVirtualFile().getFileType().isBinary()) return;
VirtualFile virtualFile = viewProvider.getVirtualFile();
if (virtualFile.getFileType().isBinary()) return;
if (viewProvider.getManager() != myPsiManager) return;
final List<PsiFile> files = viewProvider.getAllFiles();
boolean commitNecessary = false;
for (PsiFile file : files) {
if (file == null || file instanceof PsiFileImpl && ((PsiFileImpl)file).getTreeElement() == null) continue;
if (mySmartPointerManager != null) { // mock tests
SmartPointerManagerImpl.unfastenBelts(file, event.getOffset());
}
final TextBlock textBlock = getTextBlock(document, file);
if (textBlock.isLocked()) continue;
if (mySmartPointerManager != null) { // mock tests
SmartPointerManagerImpl.unfastenBelts(file);
}
textBlock.documentChanged(event);
assert file instanceof PsiFileImpl || "mock.file".equals(file.getName()) && ApplicationManager.getApplication().isUnitTestMode() : event + "; file="+file+"; allFiles="+files+"; viewProvider="+viewProvider;
@@ -26,7 +26,6 @@ import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl;
import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.Nullable;
@@ -88,7 +87,6 @@ public class PsiToDocumentSynchronizer extends PsiTreeChangeAdapter {
final boolean insideTransaction = myTransactionsMap.containsKey(document);
if(!insideTransaction){
document.setModificationStamp(psiFile.getModificationStamp());
SmartPointerManagerImpl.synchronizePointers(psiFile);
if (LOG.isDebugEnabled()) {
PsiDocumentManagerImpl.checkConsistency(psiFile, document);
}
@@ -866,7 +866,7 @@ public class FileManagerImpl implements FileManager {
}
if (oldPsiFile != null && oldPsiFile.isPhysical()) {
SmartPointerManagerImpl.fastenBelts(oldPsiFile);
SmartPointerManagerImpl.fastenBelts(oldPsiFile, 0);
}
ApplicationManager.getApplication().runWriteAction(
new ExternalChangeAction() {
@@ -1085,7 +1085,9 @@ public class FileManagerImpl implements FileManager {
removeFilesAndDirsRecursively(child);
}
}
else myVFileToViewProviderMap.remove(vFile);
else {
myVFileToViewProviderMap.remove(vFile);
}
}
}
@@ -15,9 +15,12 @@
*/
package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
@@ -26,15 +29,12 @@ import org.jetbrains.annotations.NotNull;
* User: cdr
*/
class FileElementInfo implements SmartPointerElementInfo {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.FileElementInfo");
private PsiFile myFile;
private final VirtualFile myVirtualFile;
private final Project myProject;
public FileElementInfo(@NotNull PsiFile file) {
LOG.assertTrue(file.isPhysical());
myFile = file;
myProject = myFile.getProject();
myVirtualFile = file.getVirtualFile();
myProject = file.getProject();
}
public Document getDocumentToSynchronize() {
@@ -44,12 +44,42 @@ class FileElementInfo implements SmartPointerElementInfo {
public void documentAndPsiInSync() {
}
@Override
public void fastenBelt(int offset) {
}
@Override
public void unfastenBelt(int offset) {
}
public PsiElement restoreElement() {
myFile = SelfElementInfo.restoreFile(myFile, myProject);
return myFile;
return SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject);
}
@Override
public void dispose() {
}
@Override
public int elementHashCode() {
return myVirtualFile.hashCode();
}
@Override
public boolean pointsToTheSameElementAs(SmartPointerElementInfo other) {
if (other instanceof FileElementInfo) {
return myVirtualFile == ((FileElementInfo)other).myVirtualFile;
}
return Comparing.equal(restoreElement(), other.restoreElement());
}
@Override
public VirtualFile getVirtualFile() {
return myVirtualFile;
}
@Override
public Segment getSegment() {
return new TextRange(0, (int)myVirtualFile.getLength());
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2000-2011 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.openapi.editor.Document;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiUtilBase;
import org.jetbrains.annotations.NotNull;
/**
* User: cdr
*/
class HardElementInfo implements SmartPointerElementInfo {
private final PsiElement myElement;
public HardElementInfo(@NotNull PsiElement element) {
myElement = element;
}
public Document getDocumentToSynchronize() {
return null;
}
public void documentAndPsiInSync() {
}
@Override
public void fastenBelt(int offset) {
}
@Override
public void unfastenBelt(int offset) {
}
public PsiElement restoreElement() {
return myElement;
}
@Override
public void dispose() {
}
@Override
public int elementHashCode() {
return myElement.hashCode();
}
@Override
public boolean pointsToTheSameElementAs(SmartPointerElementInfo other) {
return Comparing.equal(myElement, other.restoreElement());
}
@Override
public VirtualFile getVirtualFile() {
return PsiUtilBase.getVirtualFile(myElement);
}
@Override
public Segment getSegment() {
return myElement.getTextRange();
}
}
@@ -18,7 +18,9 @@ package com.intellij.psi.impl.smartPointers;
import com.intellij.injected.editor.DocumentWindow;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
/**
@@ -27,14 +29,16 @@ import org.jetbrains.annotations.NotNull;
class InjectedSelfElementInfo extends SelfElementInfo {
private DocumentWindow myDocument;
InjectedSelfElementInfo(@NotNull PsiElement anchor, @NotNull Document document) {
super(anchor, document);
assert myFile.getContext() != null;
InjectedSelfElementInfo(@NotNull PsiElement anchor, PsiFile containingFile) {
super(anchor, containingFile);
assert containingFile.getContext() != null;
}
protected TextRange getPersistentAnchorRange(final PsiElement anchor, final Document document) {
final TextRange textRange = super.getPersistentAnchorRange(anchor, document);
protected TextRange getPersistentAnchorRange(final PsiElement anchor) {
final TextRange textRange = super.getPersistentAnchorRange(anchor);
Document document = PsiDocumentManager.getInstance(anchor.getProject()).getDocument(anchor.getContainingFile());
// must be non-text file
if (!(document instanceof DocumentWindow)) return textRange;
myDocument = (DocumentWindow)document;
return myDocument.injectedToHost(textRange);
@@ -16,6 +16,9 @@
package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.util.ReflectionCache;
import org.jetbrains.annotations.NotNull;
@@ -27,7 +30,7 @@ public class LazyPointerImpl<E extends PsiElement> implements SmartPointerEx<E>
private final Class<? extends PsiElement> myElementClass;
private final Project myProject;
public LazyPointerImpl(E element) {
LazyPointerImpl(@NotNull E element) {
myElementClass = element.getClass();
if (element instanceof PsiCompiledElement) {
myElement = element;
@@ -38,21 +41,35 @@ public class LazyPointerImpl<E extends PsiElement> implements SmartPointerEx<E>
myProject = element.getProject();
}
public boolean pointsToTheSameElementAs(LazyPointerImpl pointer) {
if (myElementClass != pointer.myElementClass) return false;
if (myElement != null) return pointer.myElement == myElement;
if (myAnchor != null && pointer.myAnchor != null) {
return myAnchor.pointsToTheSameElementAs(pointer.myAnchor);
}
if (myPointer != null && pointer.myPointer != null) {
return SmartPointerManager.getInstance(myProject).pointToTheSameElement(myPointer, pointer.myPointer);
}
return Comparing.equal(getElement(), pointer.getElement());
}
private static SmartPsiElementPointer setupPointer(PsiElement element) {
return SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element);
}
public void fastenBelt() {
public void fastenBelt(int offset) {
if (myAnchor != null) {
final PsiElement element = myAnchor.retrieve();
if (element != null) {
myPointer = setupPointer(element);
((SmartPointerEx)myPointer).fastenBelt();
//((SmartPointerEx)myPointer).fastenBelt(offset);
myAnchor = null;
}
else myAnchor = null;
}
}
@Override
public void unfastenBelt(int offset) {
}
public void documentAndPsiInSync() {
}
@@ -60,20 +77,12 @@ public class LazyPointerImpl<E extends PsiElement> implements SmartPointerEx<E>
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof LazyPointerImpl)) return false;
final LazyPointerImpl that = (LazyPointerImpl)o;
if (myAnchor != null ? !myAnchor.equals(that.myAnchor) : that.myAnchor != null) return false;
if (myElement != null ? !myElement.equals(that.myElement) : that.myElement != null) return false;
if (myElementClass != null ? !myElementClass.equals(that.myElementClass) : that.myElementClass != null) return false;
if (myPointer != null ? !myPointer.equals(that.myPointer) : that.myPointer != null) return false;
return true;
return pointsToTheSameElementAs(that);
}
public int hashCode() {
int result;
result = (myElement != null ? myElement.hashCode() : 0);
int result = myElement != null ? myElement.hashCode() : 0;
result = 31 * result + (myAnchor != null ? myAnchor.hashCode() : 0);
result = 31 * result + (myPointer != null ? myPointer.hashCode() : 0);
result = 31 * result + (myElementClass != null ? myElementClass.hashCode() : 0);
@@ -85,11 +94,20 @@ public class LazyPointerImpl<E extends PsiElement> implements SmartPointerEx<E>
return myProject;
}
@Override
public VirtualFile getVirtualFile() {
PsiFile psiFile = getContainingFile();
return psiFile == null ? null : psiFile.getVirtualFile();
}
public E getElement() {
if (myElement != null) return myElement.isValid() ? myElement : null;
if (myPointer != null) return (E) myPointer.getElement();
if (myAnchor != null) {
final PsiElement psiElement = myAnchor.retrieve();
E element = myElement;
if (element != null) return element.isValid() ? element : null;
SmartPsiElementPointer pointer = myPointer;
if (pointer != null) return (E) pointer.getElement();
PsiAnchor anchor = myAnchor;
if (anchor != null) {
final PsiElement psiElement = anchor.retrieve();
if (psiElement != null) {
return ReflectionCache.isAssignable(myElementClass, psiElement.getClass()) ? (E) psiElement : null;
}
@@ -99,12 +117,19 @@ public class LazyPointerImpl<E extends PsiElement> implements SmartPointerEx<E>
}
public PsiFile getContainingFile() {
if (myElement != null) {
return myElement.getContainingFile();
E element = myElement;
if (element != null) {
return element.getContainingFile();
}
if (myAnchor != null) {
return myAnchor.getFile();
PsiAnchor anchor = myAnchor;
if (anchor != null) {
return anchor.getFile();
}
SmartPsiElementPointer pointer = myPointer;
if (pointer != null) {
return pointer.getContainingFile();
}
return null;
@@ -114,4 +139,21 @@ public class LazyPointerImpl<E extends PsiElement> implements SmartPointerEx<E>
public void dispose() {
if (myPointer != null) ((SmartPointerEx)myPointer).dispose();
}
@Override
public Segment getSegment() {
E element = myElement;
if (element != null && element.isValid()) return element.getTextRange();
SmartPsiElementPointer pointer = myPointer;
if (pointer != null) return pointer.getSegment();
PsiAnchor anchor = myAnchor;
if (anchor != null) {
final PsiElement psiElement = anchor.retrieve();
if (psiElement != null) {
return psiElement.getTextRange();
}
}
return null;
}
}
@@ -18,7 +18,10 @@ package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
@@ -28,70 +31,144 @@ import com.intellij.psi.PsiManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
/**
* User: cdr
*/
class SelfElementInfo implements SmartPointerElementInfo {
public class SelfElementInfo implements SmartPointerElementInfo {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SelfElementInfo");
protected PsiFile myFile;
private final RangeMarker myMarker;
protected final VirtualFile myVirtualFile;
private Reference<RangeMarker> myMarkerRef; // create marker only in case of live document
private int mySyncStartOffset;
private int mySyncEndOffset;
private boolean mySyncMarkerIsValid;
protected boolean mySyncMarkerIsValid;
private Class myType;
private final Project myProject;
protected final Project myProject;
private RangeMarker myRangeMarker; //maintain hard reference during modification
public SelfElementInfo(@NotNull PsiElement anchor, @NotNull Document document) {
public SelfElementInfo(@NotNull PsiElement anchor, PsiFile containingFile) {
LOG.assertTrue(anchor.isPhysical());
myFile = anchor.getContainingFile();
LOG.assertTrue(anchor.isValid());
PsiFile file = anchor.getContainingFile();
myVirtualFile = file.getVirtualFile();
TextRange range = anchor.getTextRange();
LOG.assertTrue(range != null, anchor);
range = getPersistentAnchorRange(anchor);
myProject = myFile.getProject();
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
if (documentManager.isUncommited(document)) {
mySyncMarkerIsValid = false;
myMarker = document.createRangeMarker(0, 0, false);
}
else {
myMarker = document.createRangeMarker(range.getStartOffset(), range.getEndOffset(), true);
range = getPersistentAnchorRange(anchor, document);
mySyncStartOffset = range.getStartOffset();
mySyncEndOffset = range.getEndOffset();
mySyncMarkerIsValid = true;
myType = anchor.getClass();
}
}
protected TextRange getPersistentAnchorRange(final PsiElement anchor, final Document document) {
return anchor.getTextRange();
}
public Document getDocumentToSynchronize() {
return myMarker.getDocument();
}
public void documentAndPsiInSync() {
if (!myMarker.isValid()) {
myProject = file.getProject();
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
Document document = documentManager.getDocument(file);
if (document == null || documentManager.isUncommited(document)) {
mySyncMarkerIsValid = false;
return;
}
mySyncStartOffset = myMarker.getStartOffset();
mySyncEndOffset = myMarker.getEndOffset();
//if (file.getTextLength() != document.getTextLength()) {
// final String docText = document.getText();
// file.accept(new PsiRecursiveElementWalkingVisitor() {
// @Override
// public void visitElement(PsiElement element) {
// super.visitElement(element);
// TextRange elementRange = element.getTextRange();
// final String rangeText = docText.length() < elementRange.getEndOffset() ? "(IOOBE: "+elementRange +" is out of (0,"+docText.length()+"))" : elementRange.substring(docText);
// final String elemText = element.getText();
// if (!rangeText.equals(elemText)) {
// throw new AssertionError("PSI text doesn't equal to the document's one: element: " + element + "\ndocText=" + rangeText + "\npsiText: " + elemText);
// }
// }
// });
// LOG.error("File=" + file);
//}
if (documentManager.isUncommited(document)) {
mySyncMarkerIsValid = false;
}
else {
mySyncMarkerIsValid = true;
myType = anchor.getClass();
}
mySyncStartOffset = range.getStartOffset();
mySyncEndOffset = range.getEndOffset();
}
protected TextRange getPersistentAnchorRange(final PsiElement anchor) {
return anchor.getTextRange();
}
public Document getDocumentToSynchronize() {
RangeMarker marker = getMarker();
if (marker != null) {
return marker.getDocument();
}
return FileDocumentManager.getInstance().getCachedDocument(myVirtualFile);
}
// before change
@Override
public void fastenBelt(int offset) {
if (!mySyncMarkerIsValid) return;
RangeMarker marker = getMarker();
int actualEndOffset = marker == null || !marker.isValid() ? getSyncEndOffset() : marker.getEndOffset();
if (offset > actualEndOffset) {
return; // no need to update, the change is far after
}
if (marker == null) {
Document document = FileDocumentManager.getInstance().getDocument(myVirtualFile);
if (document == null) {
mySyncMarkerIsValid = false;
return;
}
//PsiToDocumentSynchronizer synchronizer = ((PsiDocumentManagerImpl)PsiDocumentManager.getInstance(myProject)).getSynchronizer();
//boolean inSynchronization = synchronizer.isInSynchronization(document);
//if (!inSynchronization) { // otherwise doc offsets are incorrect
int start = Math.min(getSyncStartOffset(), document.getTextLength());
int end = Math.min(Math.max(getSyncEndOffset(), start), document.getTextLength());
marker = document.createRangeMarker(start, end, true);
setMarker(marker);
myRangeMarker = marker; //make sure marker wont be gced
//}
}
else if (!marker.isValid()) {
mySyncMarkerIsValid = false;
marker.dispose();
setMarker(null);
}
}
// after change
@Override
public void unfastenBelt(int offset) {
if (!mySyncMarkerIsValid) return;
RangeMarker marker = getMarker();
if (marker != null) {
if (marker.isValid()) {
mySyncStartOffset = marker.getStartOffset();
mySyncEndOffset = marker.getEndOffset();
assert mySyncEndOffset <= marker.getDocument().getTextLength();
}
else {
mySyncMarkerIsValid = false;
}
}
myRangeMarker = null;
}
// commit
public void documentAndPsiInSync() {
}
public PsiElement restoreElement() {
if (!mySyncMarkerIsValid) return null;
myFile = restoreFile(myFile, myProject);
if (myFile == null) return null;
PsiFile file = restoreFileFromVirtual(myVirtualFile, myProject);
if (file == null || !file.isValid()) return null;
final int syncStartOffset = getSyncStartOffset();
final int syncEndOffset = getSyncEndOffset();
PsiElement anchor = myFile.getViewProvider().findElementAt(syncStartOffset, myFile.getLanguage());
PsiElement anchor = file.getViewProvider().findElementAt(syncStartOffset, file.getLanguage());
if (anchor == null) return null;
TextRange range = anchor.getTextRange();
@@ -115,23 +192,46 @@ class SelfElementInfo implements SmartPointerElementInfo {
@Override
public void dispose() {
if (myMarker != null) {
myMarker.dispose();
RangeMarker marker = getMarker();
if (marker != null) {
marker.dispose();
setMarker(null);
}
}
private RangeMarker getMarker() {
Reference<RangeMarker> ref = myMarkerRef;
return ref == null ? null : ref.get();
}
private void setMarker(RangeMarker marker) {
myMarkerRef = marker == null ? null : new SoftReference<RangeMarker>(marker);
}
@Nullable
public static PsiFile restoreFile(PsiFile file,@NotNull Project project) {
if (file == null) return null;
if (file.isValid()) return file;
VirtualFile virtualFile = file.getVirtualFile();
return restoreFileFromVirtual(virtualFile, project);
}
@Nullable
public static PsiFile restoreFileFromVirtual(VirtualFile virtualFile, @NotNull Project project) {
if (virtualFile == null) return null;
VirtualFile vParent = virtualFile.getParent();
if (vParent == null || !vParent.isDirectory()) return null;
String name = file.getName();
VirtualFile child = vParent.findChild(name);
VirtualFile child;
if (virtualFile.isValid()) {
child = virtualFile;
}
else {
VirtualFile vParent = virtualFile.getParent();
if (vParent == null || !vParent.isDirectory()) return null;
String name = virtualFile.getName();
child = vParent.findChild(name);
}
if (child == null || !child.isValid()) return null;
file = PsiManager.getInstance(project).findFile(child);
PsiFile file = PsiManager.getInstance(project).findFile(child);
if (file == null || !file.isValid()) return null;
return file;
}
@@ -143,4 +243,32 @@ class SelfElementInfo implements SmartPointerElementInfo {
protected int getSyncStartOffset() {
return mySyncStartOffset;
}
@Override
public int elementHashCode() {
return myVirtualFile.hashCode();
}
@Override
public boolean pointsToTheSameElementAs(SmartPointerElementInfo other) {
if (other instanceof SelfElementInfo) {
return myVirtualFile == ((SelfElementInfo)other).myVirtualFile
&& myType == ((SelfElementInfo)other).myType
&& mySyncStartOffset == ((SelfElementInfo)other).mySyncStartOffset
&& mySyncEndOffset == ((SelfElementInfo)other).mySyncEndOffset
;
}
return Comparing.equal(restoreElement(), other.restoreElement());
}
@Override
public VirtualFile getVirtualFile() {
return myVirtualFile;
}
@Override
public Segment getSegment() {
if (!mySyncMarkerIsValid) return null;
return new TextRange(getSyncStartOffset(), getSyncEndOffset());
}
}
@@ -17,6 +17,8 @@
package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.Nullable;
@@ -25,9 +27,17 @@ interface SmartPointerElementInfo {
Document getDocumentToSynchronize();
void documentAndPsiInSync();
void fastenBelt(int offset);
void unfastenBelt(int offset);
@Nullable
PsiElement restoreElement();
void dispose();
int elementHashCode(); // must be immutable
boolean pointsToTheSameElementAs(SmartPointerElementInfo other);
VirtualFile getVirtualFile();
Segment getSegment();
}
@@ -18,6 +18,7 @@ package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -27,5 +28,5 @@ public interface SmartPointerElementInfoFactory {
ExtensionPointName<SmartPointerElementInfoFactory> EP_NAME = ExtensionPointName.create("com.intellij.smartPointerElementInfoFactory");
@Nullable
SmartPointerElementInfo createElementInfo(PsiElement element);
SmartPointerElementInfo createElementInfo(@NotNull PsiElement element);
}
@@ -15,11 +15,14 @@
*/
package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.Disposable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPsiElementPointer;
public interface SmartPointerEx<E extends PsiElement> extends SmartPsiElementPointer<E> {
void fastenBelt();
public interface SmartPointerEx<E extends PsiElement> extends SmartPsiElementPointer<E>, Disposable {
void fastenBelt(int offset);
void documentAndPsiInSync();
void dispose();
void unfastenBelt(int offset);
}
@@ -20,21 +20,21 @@ import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiDocumentManagerImpl;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class SmartPointerManagerImpl extends SmartPointerManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl");
private static final Key<ArrayList<WeakReference<SmartPointerEx>>> SMART_POINTERS_IN_PSI_FILE_KEY = Key.create(
"SMART_POINTERS_IN_DOCUMENT_KEY");
private static final Key<List<WeakReference<SmartPointerEx>>> SMART_POINTERS_IN_PSI_FILE_KEY = Key.create("SMART_POINTERS_IN_DOCUMENT_KEY");
private static final Key<Boolean> BELTS_ARE_FASTEN_KEY = Key.create("BELTS_ARE_FASTEN_KEY");
private final Project myProject;
@@ -43,13 +43,13 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
myProject = project;
}
public static void fastenBelts(PsiFile file) {
public static void fastenBelts(PsiFile file, int offset) {
synchronized (file) {
if (areBeltsFastened(file)) return;
file.putUserData(BELTS_ARE_FASTEN_KEY, Boolean.TRUE);
ArrayList<WeakReference<SmartPointerEx>> pointers = file.getUserData(SMART_POINTERS_IN_PSI_FILE_KEY);
List<WeakReference<SmartPointerEx>> pointers = getPointers(file);
if (pointers == null) return;
int index = 0;
@@ -57,7 +57,7 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
WeakReference<SmartPointerEx> reference = pointers.get(i);
SmartPointerEx pointer = reference.get();
if (pointer != null) {
pointer.fastenBelt();
pointer.fastenBelt(offset);
pointers.set(index++, reference);
}
}
@@ -66,22 +66,46 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
for(Document document:InjectedLanguageUtil.getCachedInjectedDocuments(file)) {
PsiFile injectedfile = psiDocumentManager.getPsiFile(document);
if (injectedfile == null) continue;
fastenBelts(injectedfile);
fastenBelts(injectedfile, 0);
}
int size = pointers.size();
for (int i = size - 1; i >= index; i--) {
pointers.remove(i);
}
int i = 0;
}
}
public static void unfastenBelts(PsiFile file) {
public static void unfastenBelts(PsiFile file, int offset) {
final Set<Language> languages = file.getViewProvider().getLanguages();
for (Language language : languages) {
final PsiFile f = file.getViewProvider().getPsi(language);
synchronized (f) {
f.putUserData(BELTS_ARE_FASTEN_KEY, null);
f.putUserData(BELTS_ARE_FASTEN_KEY, null);
List<WeakReference<SmartPointerEx>> pointers = getPointers(file);
if (pointers == null) return;
int index = 0;
for (int i = 0; i < pointers.size(); i++) {
WeakReference<SmartPointerEx> reference = pointers.get(i);
SmartPointerEx pointer = reference.get();
if (pointer != null) {
pointer.unfastenBelt(offset);
pointers.set(index++, reference);
}
}
final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(file.getProject());
for(Document document:InjectedLanguageUtil.getCachedInjectedDocuments(file)) {
PsiFile injectedfile = psiDocumentManager.getPsiFile(document);
if (injectedfile == null) continue;
unfastenBelts(injectedfile, 0);
}
int size = pointers.size();
for (int i = size - 1; i >= index; i--) {
pointers.remove(i);
}
}
}
@@ -97,7 +121,7 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
}
private static void _synchronizePointers(final PsiFile file) {
ArrayList<WeakReference<SmartPointerEx>> pointers = file.getUserData(SMART_POINTERS_IN_PSI_FILE_KEY);
List<WeakReference<SmartPointerEx>> pointers = getPointers(file);
if (pointers == null) return;
int index = 0;
@@ -124,64 +148,71 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
}
@NotNull
public <E extends PsiElement> SmartPsiElementPointer<E> createSmartPsiElementPointer(E element) {
public <E extends PsiElement> SmartPsiElementPointer<E> createSmartPsiElementPointer(@NotNull E element) {
if (!element.isValid()) {
LOG.error("Invalid element:" + element);
}
PsiFile file = element.getContainingFile();
if (isSafeReparseable(file)) {
return new IdentitySmartPointer<E>(element);
}
SmartPointerEx<E> pointer = new SmartPsiElementPointerImpl<E>(myProject, element);
initPointer(element, pointer);
PsiFile containingFile = element.getContainingFile();
SmartPointerEx<E> pointer = new SmartPsiElementPointerImpl<E>(myProject, element, containingFile);
initPointer(element, pointer, containingFile);
return pointer;
}
private static boolean isSafeReparseable(final PsiFile file) {
return false;
//return file != null && !(file instanceof CodeFragmentElement); TODO: needs proper processing of foreign chameleons on reparse (like javascript in HTML).
}
private <E extends PsiElement> void initPointer(E element, SmartPointerEx<E> pointer, PsiFile containingFile) {
if (containingFile == null) return;
synchronized (containingFile) {
Document document = PsiDocumentManager.getInstance(myProject).getCachedDocument(containingFile);
//todo
//if (document != null) {
// //[ven] this is a really NASTY hack; when no smart pointer is kept on UsageInfo then remove this conditional
// if (!(element instanceof PsiFile)) {
// PsiDocumentManagerImpl documentManager = (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(myProject);
// LOG.assertTrue(!documentManager.isUncommited(document) || documentManager.isCommittingDocument(document), "Document for : " +
// containingFile + " is not committed");
// }
//}
private <E extends PsiElement> void initPointer(E element, SmartPointerEx<E> pointer) {
PsiFile file = element.getContainingFile();
if (file != null) {
synchronized (file) {
Document document = PsiDocumentManager.getInstance(myProject).getCachedDocument(file);
if (document != null) {
//[ven] this is a really NASTY hack; when no smart pointer is kept on UsageInfo then remove this conditional
if (!(element instanceof PsiFile)) {
PsiDocumentManagerImpl documentManager = (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(myProject);
LOG.assertTrue(!documentManager.isUncommited(document) || documentManager.isCommittingDocument(document), "Document for : " + file + " is not committed");
}
}
List<WeakReference<SmartPointerEx>> pointers = getPointers(containingFile);
if (pointers == null) {
pointers = new ArrayList<WeakReference<SmartPointerEx>>();
containingFile.putUserData(SMART_POINTERS_IN_PSI_FILE_KEY, pointers);
}
pointers.add(new WeakReference<SmartPointerEx>(pointer));
ArrayList<WeakReference<SmartPointerEx>> pointers = file.getUserData(SMART_POINTERS_IN_PSI_FILE_KEY);
if (pointers == null) {
pointers = new ArrayList<WeakReference<SmartPointerEx>>();
file.putUserData(SMART_POINTERS_IN_PSI_FILE_KEY, pointers);
}
pointers.add(new WeakReference<SmartPointerEx>(pointer));
if (areBeltsFastened(file)) {
pointer.fastenBelt();
}
if (areBeltsFastened(containingFile)) {
pointer.fastenBelt(0);
}
}
}
private static List<WeakReference<SmartPointerEx>> getPointers(@NotNull PsiFile containingFile) {
return containingFile.getUserData(SMART_POINTERS_IN_PSI_FILE_KEY);
}
private static boolean areBeltsFastened(final PsiFile file) {
return file.getUserData(BELTS_ARE_FASTEN_KEY) == Boolean.TRUE;
}
@NotNull
public <E extends PsiElement> SmartPsiElementPointer<E> createLazyPointer(E element) {
LazyPointerImpl<E> pointer = new LazyPointerImpl<E>(element);
initPointer(element, pointer);
return pointer;
public <E extends PsiElement> SmartPsiElementPointer<E> createLazyPointer(@NotNull E element) {
return createSmartPsiElementPointer(element);
//LazyPointerImpl<E> pointer = new LazyPointerImpl<E>(element);
//initPointer(element, pointer, element.getContainingFile());
//return pointer;
}
@Override
public boolean pointToTheSameElement(@NotNull SmartPsiElementPointer pointer1, @NotNull SmartPsiElementPointer pointer2) {
if (pointer1 instanceof SmartPsiElementPointerImpl && pointer2 instanceof SmartPsiElementPointerImpl) {
SmartPointerElementInfo elementInfo1 = ((SmartPsiElementPointerImpl)pointer1).getElementInfo();
SmartPointerElementInfo elementInfo2 = ((SmartPsiElementPointerImpl)pointer2).getElementInfo();
return elementInfo1.pointsToTheSameElementAs(elementInfo2);
}
if (pointer1 instanceof LazyPointerImpl && pointer2 instanceof LazyPointerImpl) {
return ((LazyPointerImpl)pointer1).pointsToTheSameElementAs((LazyPointerImpl)pointer2);
}
return Comparing.equal(pointer1.getElement(), pointer2.getElement());
}
}
@@ -21,50 +21,54 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiDocumentManagerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx<E> {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPsiElementPointerImpl");
private E myElement;
private SmartPointerElementInfo myElementInfo;
private Reference<E> myElement;
private final SmartPointerElementInfo myElementInfo;
private final Project myProject;
private final Class<? extends PsiElement> myElementClass;
public SmartPsiElementPointerImpl(Project project, E element) {
public SmartPsiElementPointerImpl(@NotNull Project project, @NotNull E element, PsiFile containingFile) {
myProject = project;
ApplicationManager.getApplication().assertReadAccessAllowed();
myElement = element;
myElementInfo = null;
myElement = new WeakReference<E>(element);
myElementInfo = createElementInfo(element, containingFile);
myElementClass = element.getClass();
// Assert document committed.
PsiFile file = element.getContainingFile();
if (file != null) {
final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
if (psiDocumentManager instanceof PsiDocumentManagerImpl) {
Document doc = psiDocumentManager.getCachedDocument(file);
if (doc != null) {
//[ven] this is a really NASTY hack; when no smart pointer is kept on UsageInfo then remove this conditional
if (!(element instanceof PsiFile)) {
LOG.assertTrue(!psiDocumentManager.isUncommited(doc) || ((PsiDocumentManagerImpl)psiDocumentManager).isCommittingDocument(doc));
}
}
}
}
//todo
//if (containingFile != null) {
// final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
// if (psiDocumentManager instanceof PsiDocumentManagerImpl) {
// Document doc = psiDocumentManager.getCachedDocument(containingFile);
// if (doc != null) {
// //[ven] this is a really NASTY hack; when no smart pointer is kept on UsageInfo then remove this conditional
// if (!(element instanceof PsiFile)) {
// LOG.assertTrue(!psiDocumentManager.isUncommited(doc) || ((PsiDocumentManagerImpl)psiDocumentManager).isCommittingDocument(doc));
// }
// }
// }
//}
}
public boolean equals(Object obj) {
if (!(obj instanceof SmartPsiElementPointer)) return false;
SmartPsiElementPointer pointer = (SmartPsiElementPointer)obj;
return Comparing.equal(pointer.getElement(), getElement());
return SmartPointerManager.getInstance(myProject).pointToTheSameElement(this, pointer);
}
public int hashCode() {
PsiElement element = getElement();
return element != null ? element.hashCode() : 0;
return myElementInfo.elementHashCode();
}
@NotNull
@@ -74,35 +78,38 @@ class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx
@Nullable
public E getElement() {
if (myElement != null && !myElement.isValid()) {
if (myElementInfo == null) {
myElement = null;
PsiElement element = getCachedElement();
if (element != null && !element.isValid()) {
element = null;
}
if (element == null && myElementInfo != null) {
element = myElementInfo.restoreElement();
if (element != null && (!element.getClass().equals(myElementClass) || !element.isValid())) {
element = null;
}
else {
PsiElement restored = myElementInfo.restoreElement();
if (restored != null && (!areElementKindEqual(restored, myElement) || !restored.isValid())) {
restored = null;
}
myElement = (E) restored;
}
myElement = element == null ? null : new WeakReference(element);
}
//if (myElementInfo != null && myElement != null) {
// Document document = myElementInfo.getDocumentToSynchronize();
// if (document != null && PsiDocumentManager.getInstance(myProject).isUncommited(document)) return myElement; // keep element info if document is modified
//}
// myElementInfo = null;
return (E)element;
}
return myElement;
private E getCachedElement() {
return myElement == null ? null : myElement.get();
}
public PsiFile getContainingFile() {
if (myElement != null) {
return myElement.getContainingFile();
E element = getCachedElement();
if (element != null) {
return element.getContainingFile();
}
VirtualFile virtualFile = myElementInfo.getVirtualFile();
if (virtualFile != null && virtualFile.isValid()) {
PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(virtualFile);
if (psiFile != null) return psiFile;
}
final Document doc = myElementInfo == null ? null : myElementInfo.getDocumentToSynchronize();
final Document doc = myElementInfo.getDocumentToSynchronize();
if (doc == null) {
final E resolved = getElement();
return resolved != null ? resolved.getContainingFile() : null;
@@ -110,37 +117,37 @@ class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx
return PsiDocumentManager.getInstance(myProject).getPsiFile(doc);
}
@Nullable
private SmartPointerElementInfo createElementInfo() {
if (myElement instanceof PsiCompiledElement) return null;
public VirtualFile getVirtualFile() {
return myElementInfo.getVirtualFile();
}
final PsiFile containingFile = myElement.getContainingFile();
if (containingFile == null) return null;
if (!myElement.isPhysical()) return null;
@Override
public Segment getSegment() {
return myElementInfo.getSegment();
}
@NotNull
private SmartPointerElementInfo createElementInfo(@NotNull E element, PsiFile containingFile) {
if (element instanceof PsiCompiledElement || !element.isPhysical() || containingFile == null || element.getTextRange() == null) {
return new HardElementInfo(element);
}
for(SmartPointerElementInfoFactory factory: Extensions.getExtensions(SmartPointerElementInfoFactory.EP_NAME)) {
final SmartPointerElementInfo result = factory.createElementInfo(myElement);
final SmartPointerElementInfo result = factory.createElementInfo(element);
if (result != null) {
return result;
}
}
if (myElement instanceof PsiFile) {
return new FileElementInfo((PsiFile)myElement);
if (element instanceof PsiFile) {
return new FileElementInfo((PsiFile)element);
}
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject());
Document document = documentManager.getDocument(containingFile);
if (document == null) return null; // must be non-text file
if (containingFile.getContext() != null) {
return new InjectedSelfElementInfo(myElement, document);
return new InjectedSelfElementInfo(element, containingFile);
}
return new SelfElementInfo(myElement, document);
}
private static boolean areElementKindEqual(PsiElement element1, PsiElement element2) {
return element1.getClass().equals(element2.getClass()); //?
return new SelfElementInfo(element, containingFile);
}
public void documentAndPsiInSync() {
@@ -153,16 +160,20 @@ class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx
public void dispose() {
if (myElementInfo != null) {
myElementInfo.dispose();
myElementInfo = null;
myElement = null;
}
}
public void fastenBelt() {
if (myElementInfo != null && myElement != null && myElement.isValid()) return;
@Override
public void unfastenBelt(int offset) {
myElementInfo.unfastenBelt(offset);
}
if (myElementInfo == null && myElement != null && myElement.isValid()) {
myElementInfo = createElementInfo();
}
public void fastenBelt(int offset) {
myElementInfo.fastenBelt(offset);
}
SmartPointerElementInfo getElementInfo() {
return myElementInfo;
}
}
@@ -32,7 +32,6 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.impl.source.DummyHolderFactory;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
@@ -192,9 +191,9 @@ public class ChangeUtil {
final PsiManagerEx psiManager = (PsiManagerEx) manager;
final PsiFile file = (PsiFile)changedFile.getPsi();
if (file.isPhysical()) {
SmartPointerManagerImpl.fastenBelts(file);
}
//if (file.isPhysical()) {
// SmartPointerManagerImpl.fastenBelts(file, changedElement.getStartOffset());
//}
action.makeChange(destinationTreeChange);
@@ -18,9 +18,9 @@ package com.intellij.util.ui;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Segment;
import com.intellij.util.Alarm;
import com.intellij.util.ArrayUtil;
@@ -33,7 +33,7 @@ import java.util.List;
public class RangeBlinker {
private final Editor myEditor;
private int myTimeToLive;
private final List<RangeMarker> myMarkers = new ArrayList<RangeMarker>();
private final List<Segment> myMarkers = new ArrayList<Segment>();
private boolean show = true;
private final Alarm myBlinkingAlarm = new Alarm();
private final TextAttributes myAttributes;
@@ -44,7 +44,8 @@ public class RangeBlinker {
myEditor = editor;
myTimeToLive = timeToLive;
}
public void resetMarkers(final List<RangeMarker> markers) {
public void resetMarkers(final List<Segment> markers) {
removeHighlights();
myMarkers.clear();
stopBlinking();
@@ -72,8 +73,7 @@ public class RangeBlinker {
MarkupModel markupModel = myEditor.getMarkupModel();
if (show) {
for (final RangeMarker rangeMarker : myMarkers) {
if (!rangeMarker.isValid()) continue;
for (Segment rangeMarker : myMarkers) {
RangeHighlighter highlighter = markupModel.addRangeHighlighter(rangeMarker.getStartOffset(), rangeMarker.getEndOffset(),
HighlighterLayer.ADDITIONAL_SYNTAX, myAttributes,
HighlighterTargetArea.EXACT_RANGE);
@@ -17,7 +17,6 @@ package com.intellij.openapi.fileEditor.impl;
import com.intellij.AppTopics;
import com.intellij.codeStyle.CodeStyleFacade;
import com.intellij.ide.IdeEventQueue;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.components.ApplicationComponent;
@@ -608,6 +607,6 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl
}
private void fireFileContentLoaded(final VirtualFile file, final DocumentEx document) {
//myBus.asyncPublisher(AppTopics.FILE_DOCUMENT_SYNC).fileContentLoaded(file, document);
myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC).fileContentLoaded(file, document);
}
}
@@ -43,7 +43,7 @@ public class FileContentQueue {
private long myTotalSize;
private final ArrayBlockingQueue<FileContent> myQueue = new ArrayBlockingQueue<FileContent>(256);
private Queue<FileContent> myPushbackBuffer = new ArrayDeque<FileContent>();
private final Queue<FileContent> myPushbackBuffer = new ArrayDeque<FileContent>();
public void queue(final Collection<VirtualFile> files, @Nullable final ProgressIndicator indicator) {
final Runnable contentLoadingRunnable = new Runnable() {
@@ -18,7 +18,7 @@ import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.util.Disposer;
import com.intellij.testFramework.LeakedProjectHunter;
import com.intellij.testFramework.LeakHunter;
import com.intellij.testFramework.LightPlatformTestCase;
import com.intellij.util.ui.UIUtil;
import junit.framework.TestCase;
@@ -37,7 +37,7 @@ public class _LastInSuiteTest extends TestCase {
}.execute().throwException();
final Application application = ApplicationManager.getApplication();
LeakedProjectHunter.checkProjectLeak(application);
LeakHunter.checkProjectLeak(application);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
@@ -15,11 +15,12 @@
*/
package com.intellij.testFramework;
import com.intellij.openapi.application.impl.ApplicationImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.impl.ProjectImpl;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.util.Processor;
import com.intellij.util.containers.Stack;
import com.intellij.util.io.PersistentEnumerator;
@@ -27,6 +28,7 @@ import com.intellij.util.ui.UIUtil;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
@@ -40,7 +42,7 @@ import java.util.Set;
/**
* User: cdr
*/
public class LeakedProjectHunter {
public class LeakHunter {
private static final Map<Class, List<Field>> allFields = new THashMap<Class, List<Field>>();
private static List<Field> getAllFields(Class aClass) {
List<Field> cached = allFields.get(aClass);
@@ -65,13 +67,13 @@ public class LeakedProjectHunter {
}
private static final Set<Object> visited = new THashSet<Object>(TObjectHashingStrategy.IDENTITY);
static class BackLink {
Class aClass;
Object value;
Field field;
BackLink backLink;
private static class BackLink {
private final Class aClass;
private final Object value;
private final Field field;
private final BackLink backLink;
BackLink(Class aClass, Object value, Field field, BackLink backLink) {
private BackLink(Class aClass, Object value, Field field, BackLink backLink) {
this.aClass = aClass;
this.value = value;
this.field = field;
@@ -89,11 +91,15 @@ public class LeakedProjectHunter {
Class rootClass = backLink.aClass;
List<Field> fields = getAllFields(rootClass);
for (Field field : fields) {
if (root instanceof Reference && "referent".equals(field.getName())) continue; // do not follow weak/soft refs
String fieldName = field.getName();
if (root instanceof Reference && "referent".equals(fieldName)) continue; // do not follow weak/soft refs
Object value = field.get(root);
if (value == null) continue;
if (value instanceof PsiElement || value instanceof TreeElement) {
int i = 0;
}
Class valueClass = value.getClass();
if (valueClass == lookFor) {
if (lookFor.isAssignableFrom(valueClass)) {
BackLink newBackLink = new BackLink(valueClass, value, field, backLink);
processor.process(newBackLink);
}
@@ -146,7 +152,7 @@ public class LeakedProjectHunter {
checkLeak(root, ProjectImpl.class);
}
@TestOnly
public static void checkLeak(Object root, Class suspectClass) throws Exception {
public static void checkLeak(@NotNull Object root, @NotNull Class suspectClass) throws Exception {
if (SwingUtilities.isEventDispatchThread()) {
UIUtil.dispatchAllInvocationEvents();
}
@@ -154,7 +160,9 @@ public class LeakedProjectHunter {
UIUtil.pump();
}
PersistentEnumerator.clearCacheForTests();
toVisit.push(new BackLink(ApplicationImpl.class, root, null,null));
toVisit.clear();
visited.clear();
toVisit.push(new BackLink(root.getClass(), root, null,null));
try {
walkObjects(new Processor<BackLink>() {
@Override
@@ -26,20 +26,20 @@ import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.PlainSyntaxHighlighter;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.usageView.UsageTreeColors;
import com.intellij.usageView.UsageTreeColorsScheme;
import com.intellij.util.Processor;
import com.intellij.util.containers.FactoryMap;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -60,7 +60,6 @@ public class ChunkExtractor {
private final Lexer myLexer;
private abstract static class WeakFactory<T> {
private WeakReference<T> myRef;
@NotNull
@@ -74,7 +73,6 @@ public class ChunkExtractor {
myRef = new WeakReference<T>(result);
return result;
}
}
private static final ThreadLocal<WeakFactory<Map<PsiFile, ChunkExtractor>>> ourExtractors = new ThreadLocal<WeakFactory<Map<PsiFile, ChunkExtractor>>>() {
@@ -95,19 +93,18 @@ public class ChunkExtractor {
}
};
public static TextChunk[] extractChunks(PsiElement element, List<RangeMarker> rangeMarkers) {
return ourExtractors.get().getValue().get(element.getContainingFile()).extractChunks(rangeMarkers);
public static TextChunk[] extractChunks(PsiFile file, UsageInfo2UsageAdapter usageAdapter) {
return ourExtractors.get().getValue().get(file).extractChunks(usageAdapter);
}
private ChunkExtractor(PsiFile file) {
private ChunkExtractor(@NotNull PsiFile file) {
myColorsScheme = UsageTreeColorsScheme.getInstance().getScheme();
myDocument = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
LOG.assertTrue(myDocument != null);
final FileType fileType = file.getFileType();
final SyntaxHighlighter highlighter =
SyntaxHighlighter.PROVIDER.create(fileType, file.getProject(), file.getVirtualFile());
final SyntaxHighlighter highlighter = SyntaxHighlighter.PROVIDER.create(fileType, file.getProject(), file.getVirtualFile());
myHighlighter = highlighter == null ? new PlainSyntaxHighlighter() : highlighter;
myLexer = myHighlighter.getHighlightingLexer();
myLexer.start(myDocument.getCharsSequence());
@@ -124,18 +121,13 @@ public class ChunkExtractor {
return minStart == Integer.MAX_VALUE ? -1 : minStart;
}
public TextChunk[] extractChunks(List<RangeMarker> rangeMarkers) {
final ArrayList<RangeMarker> markers = new ArrayList<RangeMarker>(rangeMarkers.size());
for (RangeMarker rangeMarker : rangeMarkers) {
if (rangeMarker.isValid()) markers.add(rangeMarker);
}
int absoluteStartOffset = getStartOffset(markers);
private TextChunk[] extractChunks(UsageInfo2UsageAdapter usageInfo2UsageAdapter) {
int absoluteStartOffset = usageInfo2UsageAdapter.getNavigationOffset();
assert absoluteStartOffset != -1;
final int lineNumber = myDocument.getLineNumber(absoluteStartOffset);
final int columnNumber = absoluteStartOffset - myDocument.getLineStartOffset(lineNumber);
Collections.sort(markers, RangeMarker.BY_START_OFFSET);
int lineStartOffset = myDocument.getLineStartOffset(lineNumber);
int lineEndOffset = lineStartOffset < myDocument.getTextLength() ? myDocument.getLineEndOffset(lineNumber) : 0;
if (lineStartOffset > lineEndOffset) return TextChunk.EMPTY_ARRAY;
@@ -150,10 +142,10 @@ public class ChunkExtractor {
lineStartOffset = Math.max(lineStartOffset, absoluteStartOffset - OFFSET_BEFORE_TO_SHOW_WHEN_LONG_LINE);
lineEndOffset = Math.min(lineEndOffset, absoluteStartOffset + OFFSET_AFTER_TO_SHOW_WHEN_LONG_LINE);
}
return createTextChunks(markers, chars, lineStartOffset, lineEndOffset, result);
return createTextChunks(usageInfo2UsageAdapter, chars, lineStartOffset, lineEndOffset, result);
}
private TextChunk[] createTextChunks(final List<RangeMarker> markers,
private TextChunk[] createTextChunks(final UsageInfo2UsageAdapter usageInfo2UsageAdapter,
final CharSequence chars,
int start,
int end,
@@ -185,7 +177,7 @@ public class ChunkExtractor {
IElementType tokenType = lexer.getTokenType();
TextAttributesKey[] tokenHighlights = highlighter.getTokenHighlights(tokenType);
processIntersectingRange(markers, chars, hiStart, hiEnd, tokenHighlights, result);
processIntersectingRange(usageInfo2UsageAdapter, chars, hiStart, hiEnd, tokenHighlights, result);
}
finally {
lexer.advance();
@@ -195,28 +187,32 @@ public class ChunkExtractor {
return result.toArray(new TextChunk[result.size()]);
}
private void processIntersectingRange(List<RangeMarker> markers,
CharSequence chars,
private void processIntersectingRange(UsageInfo2UsageAdapter usageInfo2UsageAdapter,
final CharSequence chars,
int hiStart,
int hiEnd,
final int hiEnd,
TextAttributesKey[] tokenHighlights,
List<TextChunk> result) {
TextAttributes originalAttrs = convertAttributes(tokenHighlights);
int lastOffset = hiStart;
for(RangeMarker rangeMarker: markers) {
int usageStart = rangeMarker.getStartOffset();
int usageEnd = rangeMarker.getEndOffset();
if (rangeMarker.isValid() && rangeIntersect(lastOffset, hiEnd, usageStart, usageEnd)) {
addChunk(chars, lastOffset, Math.max(lastOffset, usageStart), originalAttrs, false, result);
addChunk(chars, Math.max(lastOffset, usageStart), Math.min(hiEnd, usageEnd), originalAttrs, true, result);
if (usageEnd > hiEnd) {
return;
final List<TextChunk> result) {
final TextAttributes originalAttrs = convertAttributes(tokenHighlights);
final int[] lastOffset = {hiStart};
usageInfo2UsageAdapter.processRangeMarkers(new Processor<Segment>() {
@Override
public boolean process(Segment segment) {
int usageStart = segment.getStartOffset();
int usageEnd = segment.getEndOffset();
if (rangeIntersect(lastOffset[0], hiEnd, usageStart, usageEnd)) {
addChunk(chars, lastOffset[0], Math.max(lastOffset[0], usageStart), originalAttrs, false, result);
addChunk(chars, Math.max(lastOffset[0], usageStart), Math.min(hiEnd, usageEnd), originalAttrs, true, result);
lastOffset[0] = usageEnd;
if (usageEnd > hiEnd) {
return false;
}
}
lastOffset = usageEnd;
return true;
}
}
if (lastOffset < hiEnd) {
addChunk(chars, lastOffset, hiEnd, originalAttrs, false, result);
});
if (lastOffset[0] < hiEnd) {
addChunk(chars, lastOffset[0], hiEnd, originalAttrs, false, result);
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2000-2011 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.usages;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.usages.rules.UsageInFile;
import com.intellij.usages.rules.UsageInFiles;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
/**
* User: cdr
*/
public class UsageDataUtil {
@Nullable
public static VirtualFile[] provideVirtualFileArray(Usage[] usages, UsageTarget[] usageTargets) {
if (usages == null && usageTargets == null) {
return null;
}
final Set<VirtualFile> result = new THashSet<VirtualFile>();
if (usages != null) {
for (Usage usage : usages) {
if (usage instanceof UsageInFile) {
VirtualFile file = ((UsageInFile)usage).getFile();
if (file.isValid()) {
result.add(file);
}
}
if (usage instanceof UsageInFiles) {
VirtualFile[] files = ((UsageInFiles)usage).getFiles();
for (VirtualFile file : files) {
if (file.isValid()) {
result.add(file);
}
}
}
}
}
if (usageTargets != null) {
for (UsageTarget usageTarget : usageTargets) {
if (usageTarget.isValid()) {
final VirtualFile[] files = usageTarget.getFiles();
if (files != null) {
ContainerUtil.addAll(result, files);
}
}
}
}
return VfsUtil.toVirtualFileArray(result);
}
}
@@ -20,10 +20,8 @@ import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.actionSystem.DataSink;
import com.intellij.openapi.actionSystem.TypeSafeDataProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.module.Module;
@@ -31,28 +29,33 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.reference.SoftReference;
import com.intellij.ui.ComputableIcon;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.rules.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.NotNullFunction;
import com.intellij.util.Processor;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.lang.ref.Reference;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @author max
*/
public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, UsageInFile, PsiElementUsage, MergeableUsage, Comparable<UsageInfo2UsageAdapter>, RenameableUsage,
TypeSafeDataProvider {
private static final Logger LOG = Logger.getInstance("#com.intellij.usages.UsageInfo2UsageAdapter");
public class UsageInfo2UsageAdapter implements UsageInModule,
UsageInLibrary, UsageInFile, PsiElementUsage,
MergeableUsage, Comparable<UsageInfo2UsageAdapter>,
RenameableUsage, TypeSafeDataProvider, UsagePresentation {
public static final NotNullFunction<UsageInfo, Usage> CONVERTER = new NotNullFunction<UsageInfo, Usage>() {
@NotNull
public Usage fun(UsageInfo usageInfo) {
@@ -61,40 +64,33 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
};
private final UsageInfo myUsageInfo;
private final List<UsageInfo> myMergedUsageInfos = new SmartList<UsageInfo>(); // contains all merged infos, including myUsageInfo
private int myLineNumber;
private int myOffset = -1;
protected ComputableIcon myIcon;
private String myTooltipText;
private List<RangeMarker> myRangeMarkers = new ArrayList<RangeMarker>();
private TextChunk[] myTextChunks;
private final UsagePresentation myUsagePresentation;
private Reference<TextChunk[]> myTextChunks; // allow to be gced and recreated on-demand because it requires a lot of memory
public UsageInfo2UsageAdapter(final UsageInfo usageInfo) {
public UsageInfo2UsageAdapter(@NotNull final UsageInfo usageInfo) {
myUsageInfo = usageInfo;
myMergedUsageInfos.add(usageInfo);
myUsagePresentation = ApplicationManager.getApplication().runReadAction(new Computable<UsagePresentation>() {
public UsagePresentation compute() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
PsiElement element = getElement();
PsiFile psiFile = element.getContainingFile();
Document document = PsiDocumentManager.getInstance(element.getProject()).getDocument(psiFile);
TextRange range = element.getTextRange();
int startOffset = range.getStartOffset() + myUsageInfo.startOffset;
int endOffset = range.getStartOffset() + myUsageInfo.endOffset;
Document document = getDocument();
int startOffset = myUsageInfo.getNavigationOffset();
if (document != null) {
myLineNumber = getLineNumber(document, startOffset);
if (endOffset > document.getTextLength()) {
LOG.error("Invalid usage info, psiElement:" + element + " end offset: " + endOffset + " psiFile: " + psiFile.getName());
}
myRangeMarkers.add(document.createRangeMarker(startOffset, endOffset));
initChunks();
} else { // element over light virtual file
myTextChunks = new TextChunk[] {
}
else { // element over light virtual file
TextChunk[] chunks = {
new TextChunk(new TextAttributes(), element.getText())
};
myTextChunks = new SoftReference<TextChunk[]>(chunks);
myOffset = element.getTextOffset();
}
@@ -111,11 +107,10 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
});
}
myTooltipText = myUsageInfo.getTooltipText();
return new MyUsagePresentation();
myTooltipText = usageInfo.getTooltipText();
}
});
myModificationStamp = getCurrentModificationStamp();
}
private static int getLineNumber(final Document document, final int startOffset) {
@@ -124,31 +119,20 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
return document.getLineNumber(startOffset);
}
private void initChunks() {
myTextChunks = ChunkExtractor.extractChunks(getElement(), myRangeMarkers);
private TextChunk[] initChunks() {
TextChunk[] chunks = ChunkExtractor.extractChunks(getPsiFile(), this);
myTextChunks = new SoftReference<TextChunk[]>(chunks);
return chunks;
}
@NotNull
public UsagePresentation getPresentation() {
return myUsagePresentation;
return this;
}
public boolean isValid() {
PsiElement element = getElement();
if (element == null || !element.isValid()) {
return false;
}
return markersValid();
}
private boolean markersValid() {
for (RangeMarker rangeMarker : myRangeMarkers) {
if (!rangeMarker.isValid()) {
return false;
}
}
return true;
return element != null && element.isValid();
}
public boolean isReadOnly() {
@@ -162,31 +146,44 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
FileEditor editor = FileEditorManager.getInstance(getProject()).getSelectedEditor(virtualFile);
if (!(editor instanceof TextEditor)) return null;
return new TextEditorLocation(myUsageInfo.startOffset + getElement().getTextRange().getStartOffset(), (TextEditor)editor);
return new TextEditorLocation(getUsageInfo().startOffset + getElement().getTextRange().getStartOffset(), (TextEditor)editor);
}
public void selectInEditor() {
if (!isValid()) return;
Editor editor = openTextEditor(true);
RangeMarker marker = getRangeMarker();
Segment marker = getFirstSegment();
editor.getSelectionModel().setSelection(marker.getStartOffset(), marker.getEndOffset());
}
public void highlightInEditor() {
if (!isValid()) return;
RangeMarker marker = getRangeMarker();
Segment marker = getFirstSegment();
SelectInEditorManager.getInstance(getProject()).selectInEditor(getFile(), marker.getStartOffset(), marker.getEndOffset(), false, false);
}
public final RangeMarker getRangeMarker() {
return myRangeMarkers.get(0);
private Segment getFirstSegment() {
return getUsageInfo().getSegment();
}
public List<RangeMarker> getRangeMarkers() {
return myRangeMarkers;
// must iterate in start offset order
public boolean processRangeMarkers(Processor<Segment> processor) {
for (UsageInfo usageInfo : myMergedUsageInfos) {
if (!processor.process(usageInfo.getSegment())) {
return false;
}
}
return true;
}
public Document getDocument() {
PsiFile file = getUsageInfo().getFile();
if (file == null) return null;
return PsiDocumentManager.getInstance(getProject()).getDocument(file);
}
public void navigate(boolean focus) {
if (canNavigate()) {
openTextEditor(focus);
@@ -207,18 +204,22 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
@Nullable
private OpenFileDescriptor getDescriptor() {
if (markersValid()) {
return new OpenFileDescriptor(getProject(), getFile(), !myRangeMarkers.isEmpty() ? getRangeMarker().getStartOffset() : myOffset);
VirtualFile file = getFile();
if(file == null) return null;
int offset = getNavigationOffset();
return new OpenFileDescriptor(getProject(), file, offset);
}
int getNavigationOffset() {
Document document = getDocument();
if (document == null) return -1;
int offset = getUsageInfo().getNavigationOffset();
if (offset == -1) offset = myOffset;
if (offset >= document.getTextLength()) {
int line = Math.max(0, Math.min(myLineNumber, document.getLineCount()));
offset = document.getLineStartOffset(line);
}
else if (getFile().isValid()) {
final Document doc = FileDocumentManager.getInstance().getDocument(getFile());
if (doc != null) {
int line = Math.max(0, Math.min(myLineNumber, doc.getLineCount()));
return new OpenFileDescriptor(getProject(), getFile(), line, 0);
}
}
return null;
return offset;
}
private Project getProject() {
@@ -239,27 +240,25 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
public Module getModule() {
if (!isValid()) return null;
PsiElement element = getElement();
VirtualFile virtualFile = getFile();
if (virtualFile == null) return null;
ProjectRootManager projectRootManager = ProjectRootManager.getInstance(element.getProject());
ProjectRootManager projectRootManager = ProjectRootManager.getInstance(getProject());
ProjectFileIndex fileIndex = projectRootManager.getFileIndex();
return fileIndex.getModuleForFile(virtualFile);
}
public OrderEntry getLibraryEntry() {
if (!isValid()) return null;
PsiElement element = getElement();
PsiFile psiFile = element.getContainingFile();
PsiFile psiFile = getPsiFile();
VirtualFile virtualFile = getFile();
if (virtualFile == null) return null;
ProjectRootManager projectRootManager = ProjectRootManager.getInstance(element.getProject());
ProjectRootManager projectRootManager = ProjectRootManager.getInstance(getProject());
ProjectFileIndex fileIndex = projectRootManager.getFileIndex();
if (psiFile instanceof PsiCompiledElement || fileIndex.isInLibrarySource(virtualFile)) {
List<OrderEntry> orders = fileIndex.getOrderEntriesForFile(psiFile.getVirtualFile());
List<OrderEntry> orders = fileIndex.getOrderEntriesForFile(virtualFile);
for (OrderEntry order : orders) {
if (order instanceof LibraryOrderEntry || order instanceof JdkOrderEntry) {
return order;
@@ -271,12 +270,10 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
}
public VirtualFile getFile() {
if (isValid()) {
return getElement().getContainingFile().getVirtualFile();
}
else {
return myUsageInfo.getVirtualFile();
}
return getUsageInfo().getVirtualFile();
}
private PsiFile getPsiFile() {
return getUsageInfo().getFile();
}
public int getLine() {
@@ -287,22 +284,27 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
if (!(other instanceof UsageInfo2UsageAdapter)) return false;
UsageInfo2UsageAdapter u2 = (UsageInfo2UsageAdapter)other;
if (myLineNumber != u2.myLineNumber || getFile() != u2.getFile()) return false;
myRangeMarkers.addAll(u2.myRangeMarkers);
myMergedUsageInfos.addAll(u2.myMergedUsageInfos);
Collections.sort(myMergedUsageInfos, new Comparator<UsageInfo>() {
@Override
public int compare(UsageInfo o1, UsageInfo o2) {
return o1.getNavigationOffset() - o2.getNavigationOffset();
}
});
initChunks();
return true;
}
public void reset() {
if (myRangeMarkers.size() > 1) {
RangeMarker marker = getRangeMarker();
myRangeMarkers = new ArrayList<RangeMarker>();
myRangeMarkers.add(marker);
if (!myMergedUsageInfos.isEmpty()) {
myMergedUsageInfos.clear();
myMergedUsageInfos.add(myUsageInfo);
initChunks();
}
}
public final PsiElement getElement() {
return myUsageInfo.getElement();
return getUsageInfo().getElement();
}
public PsiReference getReference() {
@@ -310,7 +312,7 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
}
public boolean isNonCodeUsage() {
return myUsageInfo.isNonCodeUsage;
return getUsageInfo().isNonCodeUsage;
}
public UsageInfo getUsageInfo() {
@@ -318,86 +320,20 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
}
public int compareTo(final UsageInfo2UsageAdapter o) {
final PsiElement element = getElement();
final PsiFile containingFile = element == null ? null : element.getContainingFile();
final PsiElement oElement = o.getElement();
final PsiFile oContainingFile = oElement == null ? null : oElement.getContainingFile();
if (containingFile == null && oContainingFile == null
|| !Comparing.equal(containingFile, oContainingFile)) {
VirtualFile containingFile = getFile();
VirtualFile oContainingFile = o.getFile();
if (containingFile == null && oContainingFile == null || !Comparing.equal(containingFile, oContainingFile)) {
return 0;
}
return getRangeMarker().getStartOffset() - o.getRangeMarker().getStartOffset();
return getFirstSegment().getStartOffset() - o.getFirstSegment().getStartOffset();
}
public void rename(String newName) throws IncorrectOperationException {
final PsiReference reference = myUsageInfo.getReference();
final PsiReference reference = getUsageInfo().getReference();
assert reference != null : this;
reference.handleElementRename(newName);
}
private class MyUsagePresentation implements UsagePresentation {
private long myModificationStamp;
private MyUsagePresentation() {
myModificationStamp = getCurrentModificationStamp();
}
private long getCurrentModificationStamp() {
final PsiFile containingFile = getElement().getContainingFile();
return containingFile == null? -1L : containingFile.getModificationStamp();
}
@NotNull
public TextChunk[] getText() {
if (isValid()) {
// the check below makes sense only for valid PsiElement
final long currentModificationStamp = getCurrentModificationStamp();
if (currentModificationStamp != myModificationStamp) {
initChunks();
myModificationStamp = currentModificationStamp;
}
}
return myTextChunks;
}
@NotNull
public String getPlainText() {
if (myRangeMarkers.isEmpty()) { // element over light virtual file
return myTextChunks[0].getText();
}
int startOffset = ChunkExtractor.getStartOffset(myRangeMarkers);
final PsiElement element = getElement();
if (element != null && startOffset != -1) {
final Document document = PsiDocumentManager.getInstance(element.getProject()).getDocument(element.getContainingFile());
if (document != null) {
int lineNumber = document.getLineNumber(startOffset);
int lineStart = document.getLineStartOffset(lineNumber);
int lineEnd = document.getLineEndOffset(lineNumber);
String prefixSuffix = null;
if (lineEnd - lineStart > ChunkExtractor.MAX_LINE_TO_SHOW) {
prefixSuffix = "...";
lineStart = Math.max(startOffset - ChunkExtractor.OFFSET_BEFORE_TO_SHOW_WHEN_LONG_LINE, lineStart);
lineEnd = Math.min(startOffset + ChunkExtractor.OFFSET_AFTER_TO_SHOW_WHEN_LONG_LINE, lineEnd);
}
String s = document.getCharsSequence().subSequence(lineStart, lineEnd).toString();
if (prefixSuffix != null) s = prefixSuffix + s + prefixSuffix;
return s;
}
}
return "";
}
public Icon getIcon() {
return myIcon != null ? myIcon.getIcon() : null;
}
public String getTooltipText() {
return myTooltipText;
}
}
public static UsageInfo2UsageAdapter[] convert(UsageInfo[] usageInfos) {
UsageInfo2UsageAdapter[] result = new UsageInfo2UsageAdapter[usageInfos.length];
for (int i = 0; i < result.length; i++) {
@@ -412,23 +348,78 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us
sink.put(UsageView.USAGE_INFO_KEY, getUsageInfo());
}
if (key == UsageView.USAGE_INFO_LIST_KEY) {
ArrayList<UsageInfo> list = getSelectedInfoList();
List<UsageInfo> list = getSelectedInfoList();
sink.put(UsageView.USAGE_INFO_LIST_KEY, list);
}
}
private ArrayList<UsageInfo> getSelectedInfoList() {
ArrayList<UsageInfo> list = new ArrayList<UsageInfo>();
UsageInfo first = getUsageInfo();
list.add(first);
for (int i = 1; i < myRangeMarkers.size(); i++) {
RangeMarker rangeMarker = myRangeMarkers.get(i);
PsiElement element = first.getElement();
if (element == null) continue;
PsiFile file = element.getContainingFile();
UsageInfo usageInfo = new UsageInfo(file, rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
list.add(usageInfo);
private List<UsageInfo> getSelectedInfoList() {
return myMergedUsageInfos;
}
public void dispose() {
for (UsageInfo usageInfo : myMergedUsageInfos) {
usageInfo.dispose();
}
return list;
}
private long myModificationStamp;
private long getCurrentModificationStamp() {
final PsiFile containingFile = getPsiFile();
return containingFile == null ? -1L : containingFile.getModificationStamp();
}
@NotNull
public TextChunk[] getText() {
TextChunk[] chunks = myTextChunks.get();
if (chunks == null) {
myModificationStamp = -1;
}
if (chunks == null || isValid()) {
// the check below makes sense only for valid PsiElement
final long currentModificationStamp = getCurrentModificationStamp();
if (currentModificationStamp != myModificationStamp) {
chunks = initChunks();
myModificationStamp = currentModificationStamp;
}
}
return chunks;
}
@NotNull
public String getPlainText() {
//if (myRangeMarkers.isEmpty()) { // element over light virtual file
// return myTextChunks[0].getText();
//}
int startOffset = getNavigationOffset();
final PsiElement element = getElement();
if (element != null && startOffset != -1) {
final Document document = getDocument();
if (document != null) {
int lineNumber = document.getLineNumber(startOffset);
int lineStart = document.getLineStartOffset(lineNumber);
int lineEnd = document.getLineEndOffset(lineNumber);
String prefixSuffix = null;
if (lineEnd - lineStart > ChunkExtractor.MAX_LINE_TO_SHOW) {
prefixSuffix = "...";
lineStart = Math.max(startOffset - ChunkExtractor.OFFSET_BEFORE_TO_SHOW_WHEN_LONG_LINE, lineStart);
lineEnd = Math.min(startOffset + ChunkExtractor.OFFSET_AFTER_TO_SHOW_WHEN_LONG_LINE, lineEnd);
}
String s = document.getCharsSequence().subSequence(lineStart, lineEnd).toString();
if (prefixSuffix != null) s = prefixSuffix + s + prefixSuffix;
return s;
}
}
return "";
}
public Icon getIcon() {
return myIcon != null ? myIcon.getIcon() : null;
}
public String getTooltipText() {
return myTooltipText;
}
}
@@ -23,10 +23,10 @@ import com.intellij.usages.UsageGroup;
import com.intellij.usages.UsageView;
import com.intellij.usages.UsageViewSettings;
import com.intellij.usages.rules.MergeableUsage;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import java.util.*;
@@ -67,18 +67,23 @@ public class GroupNode extends Node implements Navigatable, Comparable<GroupNode
final GroupNode node1 = node = new GroupNode(group, ruleIndex, getBuilder());
mySubgroupNodes.put(group, node);
if (!getBuilder().isDetachedMode()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myTreeModel.insertNodeInto(node1, GroupNode.this, getNodeInsertionIndex(node1));
}
});
}
addNode(node1);
}
return node;
}
}
void addNode(final DefaultMutableTreeNode node) {
if (!getBuilder().isDetachedMode()) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myTreeModel.insertNodeInto(node, GroupNode.this, getNodeInsertionIndex(node));
}
});
}
}
private UsageViewTreeModelBuilder getBuilder() {
return (UsageViewTreeModelBuilder)myTreeModel;
}
@@ -150,7 +155,7 @@ public class GroupNode extends Node implements Navigatable, Comparable<GroupNode
}
if (!getBuilder().isDetachedMode()) {
SwingUtilities.invokeLater(new Runnable() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
myTreeModel.insertNodeInto(node, GroupNode.this, getNodeIndex(node));
incrementUsageCount();
@@ -241,7 +246,7 @@ public class GroupNode extends Node implements Navigatable, Comparable<GroupNode
return false;
}
int getNodeInsertionIndex(DefaultMutableTreeNode node) {
private int getNodeInsertionIndex(DefaultMutableTreeNode node) {
Enumeration children = children();
int idx = 0;
while (children.hasMoreElements()) {
@@ -29,6 +29,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.SimpleToolWindowPanel;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.IconLoader;
@@ -48,8 +49,8 @@ import com.intellij.usages.rules.*;
import com.intellij.util.Alarm;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.Processor;
import com.intellij.util.containers.ConcurrentHashSet;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.TransferToEDTQueue;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.DialogUtil;
import com.intellij.util.ui.UIUtil;
@@ -67,9 +68,7 @@ import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* @author max
@@ -91,15 +90,12 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
private ExporterToTextFile myTextFileExporter;
private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
private final Alarm myFlushAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
private final UsageModelTracker myModelTracker;
private final Set<Usage> myUsages = new ConcurrentHashSet<Usage>();
private final Map<Usage, UsageNode> myUsageNodes = new ConcurrentHashMap<Usage, UsageNode>();
public static final UsageNode NULL_NODE = new UsageNode(NullUsage.INSTANCE, new UsageViewTreeModelBuilder(new UsageViewPresentation(), UsageTarget.EMPTY_ARRAY));
private final ButtonPanel myButtonPanel = new ButtonPanel();
private volatile boolean isDisposed;
private volatile boolean myChangesDetected = false;
private final Queue<Usage> myUsagesToFlush = new ConcurrentLinkedQueue<Usage>();
static final Comparator<Usage> USAGE_COMPARATOR = new Comparator<Usage>() {
public int compare(final Usage o1, final Usage o2) {
if (o1 == NULL_NODE || o2 == NULL_NODE) return -1;
@@ -130,7 +126,7 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
private final GroupNode myRoot;
private final UsageViewTreeModelBuilder myModel;
public UsageViewImpl(@NotNull Project project,
public UsageViewImpl(@NotNull final Project project,
@NotNull UsageViewPresentation presentation,
@NotNull UsageTarget[] targets,
Factory<UsageSearcher> usageSearcherFactory) {
@@ -173,7 +169,7 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
});
if (!myPresentation.isDetachedMode()) {
SwingUtilities.invokeLater(new Runnable() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
if (isDisposed) return;
myTree.setModel(myModel);
@@ -225,6 +221,18 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
}
});
}
myTransferToEDTQueue = new TransferToEDTQueue<Usage>(new Processor<Usage>() {
@Override
public boolean process(Usage usage) {
appendUsage(usage);
return true;
}
}, new Condition<Object>() {
@Override
public boolean value(Object o) {
return isDisposed || project.isDisposed() || com.intellij.usages.UsageViewManager.getInstance(project).searchHasBeenCancelled();
}
});
}
private void setupCentralPanel() {
@@ -645,35 +653,13 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
}
});
}
myUsages.clear();
}
public void appendUsageLater(final Usage usage) {
myUsagesToFlush.offer(usage);
if (myUsagesToFlush.size() > 50) {
flush();
}
myFlushAlarm.cancelAllRequests();
myFlushAlarm.addRequest(new Runnable() {
public void run() {
flush();
}
}, 300);
private final TransferToEDTQueue<Usage> myTransferToEDTQueue;
public void appendUsageLater(Usage usage) {
myTransferToEDTQueue.offer(usage);
}
private void flush() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
Usage usage;
while ((usage = myUsagesToFlush.poll()) != null) {
appendUsage(usage);
}
}
});
}
private volatile boolean myIsFirstVisibleUsageFound = false;
public void appendUsage(@NotNull Usage usage) {
@@ -684,10 +670,9 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
// invoke in ReadAction to be be sure that usages are not invalidated while the tree is being built
ApplicationManager.getApplication().assertReadAccessAllowed();
if (!usage.isValid()) {
// because the view is built incrementally with Alarm, the usage may be already invalid, so need filter such cases
// because the view is built incrementally, the usage may be already invalid, so need to filter such cases
return null;
}
myUsages.add(usage);
UsageNode node = myBuilder.appendUsage(usage);
myUsageNodes.put(usage, node == null ? NULL_NODE : node);
if (!myIsFirstVisibleUsageFound && node != null) { //first visible usage found;
@@ -700,7 +685,7 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
public void removeUsage(@NotNull Usage usage) {
final UsageNode node = myUsageNodes.remove(usage);
if (node != NULL_NODE && node != null && !myPresentation.isDetachedMode()) {
SwingUtilities.invokeLater(new Runnable() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
if (isDisposed) return;
TreeModel treeModel = myTree.getModel();
@@ -803,6 +788,11 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
UsageViewSettings.getInstance().PREVIEW_USAGES_SPLITTER_PROPORTIONS = ((Splitter)myUsagePreviewPanel.getParent()).getProportion();
myUsagePreviewPanel = null;
}
for (Usage usage : getUsages()) {
if (usage instanceof UsageInfo2UsageAdapter) {
((UsageInfo2UsageAdapter)usage).dispose();
}
}
}
public boolean isSearchInProgress() {
@@ -811,13 +801,12 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
public void setSearchInProgress(boolean searchInProgress) {
mySearchInProgress = searchInProgress;
flush();
if (!myPresentation.isDetachedMode()) {
SwingUtilities.invokeLater(new Runnable() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
if (isDisposed) return;
final UsageNode firstUsageNode = myModel.getFirstUsageNode();
if (firstUsageNode != null) { //first usage;
if (firstUsageNode != null) {
showNode(firstUsageNode);
}
}
@@ -827,7 +816,7 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
private void showNode(final UsageNode node) {
if (!myPresentation.isDetachedMode()) {
SwingUtilities.invokeLater(new Runnable() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
if (isDisposed) return;
TreePath usagePath = new TreePath(node.getPath());
@@ -840,10 +829,8 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
public void addButtonToLowerPane(@NotNull Runnable runnable, @NotNull String text) {
int index = myButtonPanel.getComponentCount();
if (index > 0 && myPresentation.isShowCancelButton()) index--;
myButtonPanel.add(index, runnable, text);
myButtonPanel.addButtonRunnable(index, runnable, text);
}
public void addButtonToLowerPane(@NotNull final Runnable runnable, @NotNull String text, char mnemonic) {
@@ -981,12 +968,12 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
@NotNull
public Set<Usage> getUsages() {
return myUsages;
return myUsageNodes.keySet();
}
@NotNull
public List<Usage> getSortedUsages() {
List<Usage> usages = new ArrayList<Usage>(myUsages);
List<Usage> usages = new ArrayList<Usage>(getUsages());
Collections.sort(usages, USAGE_COMPARATOR);
return usages;
}
@@ -995,9 +982,7 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
if (node instanceof UsageNode) {
UsageNode usageNode = (UsageNode)node;
final Usage usage = usageNode.getUsage();
if (usage.isValid()) {
usages.add(usage);
}
usages.add(usage);
}
Enumeration enumeration = node.children();
@@ -1042,7 +1027,7 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
if (nodes == null) {
return null;
}
final ArrayList<Navigatable> result = new ArrayList<Navigatable>();
final List<Navigatable> result = new ArrayList<Navigatable>();
for (final Node node : nodes) {
/*
if (!node.isDataValid()) {
@@ -1138,7 +1123,8 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
else if (key == PlatformDataKeys.VIRTUAL_FILE_ARRAY) {
final Set<Usage> usages = getSelectedUsages();
VirtualFile[] data = provideVirtualFileArray(usages != null ? usages.toArray(new Usage[usages.size()]) : null, getSelectedUsageTargets());
Usage[] ua = usages != null ? usages.toArray(new Usage[usages.size()]) : null;
VirtualFile[] data = UsageDataUtil.provideVirtualFileArray(ua, getSelectedUsageTargets());
sink.put(PlatformDataKeys.VIRTUAL_FILE_ARRAY, data);
}
@@ -1160,43 +1146,6 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
}
}
}
@Nullable
private VirtualFile[] provideVirtualFileArray(Usage[] usages, UsageTarget[] usageTargets) {
if (usages == null && usageTargets == null) {
return null;
}
final Set<VirtualFile> result = new THashSet<VirtualFile>();
if (usages != null) {
for (Usage usage : usages) {
if (usage.isValid()) {
if (usage instanceof UsageInFile) {
result.add(((UsageInFile)usage).getFile());
}
if (usage instanceof UsageInFiles) {
ContainerUtil.addAll(result, ((UsageInFiles)usage).getFiles());
}
}
}
}
if (usageTargets != null) {
for (UsageTarget usageTarget : usageTargets) {
if (usageTarget.isValid()) {
final VirtualFile[] files = usageTarget.getFiles();
if (files != null) {
ContainerUtil.addAll(result, files);
}
}
}
}
return VfsUtil.toVirtualFileArray(result);
}
}
private static class MyAutoScrollToSourceOptionProvider implements AutoScrollToSourceOptionProvider {
@@ -1214,7 +1163,7 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
setLayout(new FlowLayout(FlowLayout.LEFT, 8, 0));
}
public void add(int index, final Runnable runnable, String text) {
public void addButtonRunnable(int index, final Runnable runnable, String text) {
if (getBorder() == null) setBorder(IdeBorderFactory.createBorder(SideBorder.TOP));
final JButton button = new JButton(UIUtil.replaceMnemonicAmpersand(text));
@@ -1328,5 +1277,4 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
public boolean isVisible(Usage usage) {
return myBuilder != null && myBuilder.isVisible(usage);
}
}
@@ -30,10 +30,7 @@ import com.intellij.openapi.project.DumbModeAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
@@ -42,6 +39,7 @@ import com.intellij.ui.content.Content;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.ui.RangeBlinker;
import com.intellij.util.ui.UIUtil;
@@ -404,7 +402,10 @@ public class UsageViewManagerImpl extends UsageViewManager {
TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.BLINKING_HIGHLIGHTS_ATTRIBUTES);
RangeBlinker rangeBlinker = new RangeBlinker(editor, attributes, 6);
rangeBlinker.resetMarkers(usageInfo.getRangeMarkers());
List<Segment> segments = new ArrayList<Segment>();
CommonProcessors.CollectProcessor<Segment> processor = new CommonProcessors.CollectProcessor<Segment>(segments);
usageInfo.processRangeMarkers(processor);
rangeBlinker.resetMarkers(segments);
rangeBlinker.startBlinking();
}
+1
View File
@@ -10,6 +10,7 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="lang-api" />
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
<orderEntry type="module" module-name="testFramework" scope="TEST" />
</component>
</module>
@@ -18,7 +18,7 @@ package com.intellij.openapi.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class TextRange {
public class TextRange implements Segment{
private final int myStartOffset;
private final int myEndOffset;
@@ -0,0 +1,92 @@
/*
* Copyright 2000-2011 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.util.containers;
import com.intellij.openapi.util.Condition;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Allows to process elements in the EDT.
* Processes elements in batches, no longer than 200ms per batch, and reschedules processing later for longer batches.
* Usage: {@link TransferToEDTQueue#offer(Object)} } : schedules element for processing in EDT (via invokeLater)
*/
public class TransferToEDTQueue<T> {
private final Processor<T> myProcessor;
private volatile boolean stopped;
private final Condition<?> myShutUpCondition;
private final Queue<T> myQueue = new ConcurrentLinkedQueue<T>();
private final AtomicBoolean invokeLaterScheduled = new AtomicBoolean();
private static final long MAX_UNIT_OF_WORK_THRESHOLD_MS = 200; // no more than 200 ms delay
private final Runnable myUpdateRunnable = new Runnable() {
@Override
public void run() {
boolean b = invokeLaterScheduled.compareAndSet(true, false);
assert b;
if (stopped || myShutUpCondition.value(null)) {
stop();
return;
}
long start = System.currentTimeMillis();
int processed = 0;
while (true) {
T thing = myQueue.poll();
if (thing == null) break;
if (!myProcessor.process(thing)) {
myQueue.clear();
return;
}
processed++;
long finish = System.currentTimeMillis();
if (finish - start > MAX_UNIT_OF_WORK_THRESHOLD_MS) break;
}
if (!myQueue.isEmpty()) {
scheduleUpdate();
}
}
};
public TransferToEDTQueue(@NotNull Processor<T> processorInEDT, @NotNull Condition<?> shutUpCondition) {
myProcessor = processorInEDT;
myShutUpCondition = shutUpCondition;
}
public void offer(final T thing) {
myQueue.offer(thing);
scheduleUpdate();
}
private void scheduleUpdate() {
if (invokeLaterScheduled.compareAndSet(false, true)) {
schedule(myUpdateRunnable);
}
}
protected void schedule(Runnable updateRunnable) {
SwingUtilities.invokeLater(updateRunnable);
}
public void stop() {
stopped = true;
myQueue.clear();
}
}
@@ -60,7 +60,7 @@ public class NavigationGutterIconBuilder<T> {
};
private final Icon myIcon;
private final NotNullFunction<T,Collection<? extends PsiElement>> myConvertor;
private final NotNullFunction<T,Collection<? extends PsiElement>> myConverter;
private NotNullLazyValue<Collection<? extends T>> myTargets;
private boolean myLazy;
@@ -70,7 +70,7 @@ public class NavigationGutterIconBuilder<T> {
private String myTooltipTitle;
private GutterIconRenderer.Alignment myAlignment = GutterIconRenderer.Alignment.CENTER;
private PsiElementListCellRenderer myCellRenderer;
private NullableFunction<T,String> myNamer = (NullableFunction<T, String>)ElementPresentationManager.NAMER;
private NullableFunction<T,String> myNamer = ElementPresentationManager.namer();
public static final NotNullFunction<DomElement,Collection<? extends PsiElement>> DEFAULT_DOM_CONVERTOR = new NotNullFunction<DomElement, Collection<? extends PsiElement>>() {
@NotNull
public Collection<? extends PsiElement> fun(final DomElement o) {
@@ -78,17 +78,17 @@ public class NavigationGutterIconBuilder<T> {
}
};
protected NavigationGutterIconBuilder(@NotNull final Icon icon, @NotNull NotNullFunction<T,Collection<? extends PsiElement>> convertor) {
protected NavigationGutterIconBuilder(@NotNull final Icon icon, @NotNull NotNullFunction<T,Collection<? extends PsiElement>> converter) {
myIcon = icon;
myConvertor = convertor;
myConverter = converter;
}
public static NavigationGutterIconBuilder<PsiElement> create(@NotNull final Icon icon) {
return create(icon, DEFAULT_PSI_CONVERTOR);
}
public static <T> NavigationGutterIconBuilder<T> create(@NotNull final Icon icon, @NotNull NotNullFunction<T, Collection<? extends PsiElement>> convertor) {
return new NavigationGutterIconBuilder<T>(icon, convertor);
public static <T> NavigationGutterIconBuilder<T> create(@NotNull final Icon icon, @NotNull NotNullFunction<T, Collection<? extends PsiElement>> converter) {
return new NavigationGutterIconBuilder<T>(icon, converter);
}
public NavigationGutterIconBuilder<T> setTarget(@Nullable T target) {
@@ -193,9 +193,10 @@ public class NavigationGutterIconBuilder<T> {
@NotNull
public List<SmartPsiElementPointer> compute() {
Set<PsiElement> elements = new THashSet<PsiElement>();
final ArrayList<SmartPsiElementPointer> list = new ArrayList<SmartPsiElementPointer>();
for (final T target : myTargets.getValue()) {
for (final PsiElement psiElement : myConvertor.fun(target)) {
Collection<? extends T> targets = myTargets.getValue();
final List<SmartPsiElementPointer> list = new ArrayList<SmartPsiElementPointer>(targets.size());
for (final T target : targets) {
for (final PsiElement psiElement : myConverter.fun(target)) {
if (elements.add(psiElement)) {
list.add(manager.createSmartPsiElementPointer(psiElement));
}
@@ -205,7 +206,7 @@ public class NavigationGutterIconBuilder<T> {
}
};
final boolean empty = !myLazy && pointers.getValue().isEmpty();
final boolean empty = isEmpty();
if (myTooltipText == null && !myLazy) {
final SortedSet<String> names = new TreeSet<String>();
@@ -233,6 +234,23 @@ public class NavigationGutterIconBuilder<T> {
return new MyNavigationGutterIconRenderer(this, myAlignment, myIcon, myTooltipText, pointers, empty);
}
private boolean isEmpty() {
if (myLazy) {
return false;
}
Set<PsiElement> elements = new THashSet<PsiElement>();
Collection<? extends T> targets = myTargets.getValue();
for (final T target : targets) {
for (final PsiElement psiElement : myConverter.fun(target)) {
if (elements.add(psiElement)) {
return false;
}
}
}
return true;
}
private static class MyNavigationGutterIconRenderer extends NavigationGutterIconRenderer {
private final Alignment myAlignment;
private final Icon myIcon;
@@ -132,6 +132,10 @@ public abstract class ElementPresentationManager {
return getElementName(o);
}
};
public static <T> NullableFunction<T, String> namer() {
//noinspection unchecked
return (NullableFunction<T, String>)NAMER;
}
@Nullable
public static String getElementName(Object element) {