usage view for text usages; smart pointers to the range inside PsiFile; use stub index to restore psiElement for files without tree loaded

This commit is contained in:
Alexey Kudravtsev
2011-02-11 17:12:49 +03:00
parent a97db74043
commit 901d9c5048
36 changed files with 420 additions and 171 deletions
@@ -16,10 +16,12 @@
package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.impl.source.PsiFileWithStubSupport;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.IStubFileElementType;
import com.intellij.psi.xml.XmlToken;
import com.intellij.psi.xml.XmlTokenType;
import org.jetbrains.annotations.NotNull;
@@ -29,12 +31,31 @@ import org.jetbrains.annotations.Nullable;
* User: cdr
*/
class AnchorElementInfo extends SelfElementInfo {
AnchorElementInfo(@NotNull PsiElement anchor, PsiFile containingFile) {
super(anchor, containingFile);
private int stubId = -1;
private IStubElementType myStubElementType;
AnchorElementInfo(@NotNull PsiElement anchor, @NotNull PsiFile containingFile) {
super(containingFile.getProject(), anchor.getTextRange(), anchor.getClass(), containingFile);
}
// will restore by stub index until file tree get loaded
AnchorElementInfo(@NotNull PsiElement anchor,
@NotNull PsiFileWithStubSupport containingFile,
int stubId,
@NotNull IStubElementType stubElementType) {
super(containingFile.getProject(), new TextRange(0,0), anchor.getClass(), containingFile);
this.stubId = stubId;
myStubElementType = stubElementType;
IElementType contentElementType = ((PsiFileImpl)containingFile).getContentElementType();
assert contentElementType instanceof IStubFileElementType : contentElementType;
}
@Nullable
public PsiElement restoreElement() {
if (stubId != -1) {
PsiFile file = SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject);
if (!(file instanceof PsiFileWithStubSupport)) return null;
return PsiAnchor.restoreFromStubIndex((PsiFileWithStubSupport)file, stubId, myStubElementType);
}
if (!mySyncMarkerIsValid) return null;
PsiFile file = SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject);
if (file == null) return null;
@@ -60,4 +81,17 @@ class AnchorElementInfo extends SelfElementInfo {
}
return null;
}
@Override
public void fastenBelt(int offset) {
PsiElement element = restoreElement();
if (element != null) {
// switch to tree
stubId = -1;
myStubElementType = null;
PsiElement anchor = AnchorElementInfoFactory.getAnchor(element);
setRange((anchor == null ? element : anchor).getTextRange());
}
super.fastenBelt(offset);
}
}
@@ -17,6 +17,9 @@ package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiFileWithStubSupport;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.stubs.StubTree;
import com.intellij.psi.xml.XmlTag;
import com.intellij.xml.util.XmlTagUtil;
import org.jetbrains.annotations.NotNull;
@@ -27,9 +30,24 @@ public class AnchorElementInfoFactory implements SmartPointerElementInfoFactory
@Nullable
public SmartPointerElementInfo createElementInfo(@NotNull PsiElement element) {
PsiFile containingFile = element.getContainingFile();
if (element instanceof StubBasedPsiElement && containingFile instanceof PsiFileWithStubSupport) {
PsiFileWithStubSupport stubFile = (PsiFileWithStubSupport)containingFile;
StubTree stubTree = stubFile.getStubTree();
if (stubTree != null) {
// use stubs when tree is not loaded
StubBasedPsiElement stubPsi = (StubBasedPsiElement)element;
int stubId = PsiAnchor.calcStubIndex(stubPsi);
IStubElementType myStubElementType = stubPsi.getElementType();
if (stubId != -1) {
return new AnchorElementInfo(element, stubFile, stubId, myStubElementType);
}
}
}
PsiElement anchor = getAnchor(element);
if (anchor != null) {
return new AnchorElementInfo(anchor, element.getContainingFile());
return new AnchorElementInfo(anchor, containingFile);
}
return null;
}
@@ -55,8 +73,7 @@ public class AnchorElementInfoFactory implements SmartPointerElementInfoFactory
else if (element instanceof XmlTag) {
anchor = XmlTagUtil.getStartTagNameElement((XmlTag)element);
}
if (anchor != null && (!anchor.isPhysical() || anchor.getTextRange()==null)) return null;
if (anchor != null && (!anchor.isPhysical() /*|| anchor.getTextRange()==null*/)) return null;
return anchor;
}
}
@@ -16,6 +16,7 @@
package com.intellij.psi.impl.smartPointers;
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.vfs.VirtualFile;
@@ -29,17 +30,19 @@ import org.jetbrains.annotations.Nullable;
public class ImplicitVariableElementInfoFactory implements SmartPointerElementInfoFactory {
@Nullable
public SmartPointerElementInfo createElementInfo(@NotNull final PsiElement element) {
if (element instanceof ImplicitVariable) {
return new ImplicitVariableInfo((ImplicitVariable) element);
if (element instanceof ImplicitVariable && element.isValid()) {
return new ImplicitVariableInfo((ImplicitVariable) element, element.getProject());
}
return null;
}
private static class ImplicitVariableInfo implements SmartPointerElementInfo {
private final ImplicitVariable myVar;
private final Project myProject;
public ImplicitVariableInfo(ImplicitVariable var) {
public ImplicitVariableInfo(@NotNull ImplicitVariable var, @NotNull Project project) {
myVar = var;
myProject = project;
}
public PsiElement restoreElement() {
@@ -87,10 +90,16 @@ public class ImplicitVariableElementInfoFactory implements SmartPointerElementIn
}
@Override
public Segment getSegment() {
public Segment getRange() {
PsiIdentifier psiIdentifier = myVar.getNameIdentifier();
if (psiIdentifier == null || !psiIdentifier.isValid()) return null;
return psiIdentifier.getTextRange();
}
@NotNull
@Override
public Project getProject() {
return myProject;
}
}
}
@@ -151,8 +151,8 @@ class MigrationProcessor extends BaseRefactoringProcessor {
public MigrationMapEntry mapEntry;
public MigrationUsageInfo(UsageInfo info, MigrationMapEntry mapEntry) {
super(info.getElement(), info.startOffset, info.endOffset);
super(info.getElement(), info.getRangeInElement().getStartOffset(), info.getRangeInElement().getEndOffset());
this.mapEntry = mapEntry;
}
}
}
}
@@ -66,7 +66,7 @@ public class MigrationUtil {
}
private static void bindNonJavaReference(PsiElement bindTo, PsiElement element, UsageInfo usage) {
final TextRange range = usage.getRange();
final TextRange range = usage.getRangeInElement();
for (PsiReference reference : element.getReferences()) {
if (reference instanceof JavaClassReference) {
final JavaClassReference classReference = (JavaClassReference)reference;
@@ -4,7 +4,6 @@
*/
package com.intellij.refactoring.typeMigration.usageInfo;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiElement;
import com.intellij.usageView.UsageInfo;
import org.jetbrains.annotations.NotNull;
@@ -17,19 +16,6 @@ public class TypeMigrationUsageInfo extends UsageInfo {
super(element);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof TypeMigrationUsageInfo)) return false;
final UsageInfo usageInfo = (UsageInfo)o;
if (endOffset != usageInfo.endOffset) return false;
if (isNonCodeUsage != usageInfo.isNonCodeUsage) return false;
if (startOffset != usageInfo.startOffset) return false;
return Comparing.equal(getElement(), usageInfo.getElement());
}
public void setExcluded(final boolean excluded) {
myExcluded = excluded;
}
@@ -38,4 +24,4 @@ public class TypeMigrationUsageInfo extends UsageInfo {
return myExcluded;
}
}
}
@@ -28,7 +28,7 @@ public class DuplicateMap {
private static final TObjectHashingStrategy<SliceUsage> USAGEINFO_EQUALITY = new TObjectHashingStrategy<SliceUsage>() {
public int computeHashCode(SliceUsage object) {
UsageInfo info = object.getUsageInfo();
return info.startOffset + info.endOffset * 31;
return info.getRangeInElement().hashCode();
}
public boolean equals(SliceUsage o1, SliceUsage o2) {
@@ -151,8 +151,8 @@ public class MethodGroupingRule implements UsageGroupingRule {
return 0;
}
if (!UsageViewSettings.getInstance().IS_SORT_MEMBERS_ALPHABETICALLY) {
Segment segment1 = myMethodPointer.getSegment();
Segment segment2 = other.myMethodPointer.getSegment();
Segment segment1 = myMethodPointer.getRange();
Segment segment2 = other.myMethodPointer.getRange();
if (segment1 != null && segment2 != null) {
return segment1.getStartOffset() - segment2.getStartOffset();
}
@@ -70,7 +70,7 @@ public class IdentitySmartPointer<T extends PsiElement> implements SmartPsiEleme
}
@Override
public Segment getSegment() {
public Segment getRange() {
T element = myElement;
return element == null ? null : element.getTextRange();
}
@@ -17,6 +17,7 @@ package com.intellij.psi;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
/**
@@ -24,6 +25,9 @@ import org.jetbrains.annotations.NotNull;
* element in the PSI tree after the reparse.
*/
public abstract class SmartPointerManager {
@NotNull
public abstract SmartPsiFileRange createSmartPsiFileRangePointer(@NotNull PsiFile file, @NotNull TextRange range);
public static SmartPointerManager getInstance(Project project) {
return ServiceManager.getService(project, SmartPointerManager.class);
}
@@ -46,5 +46,5 @@ public interface SmartPsiElementPointer<E extends PsiElement> {
VirtualFile getVirtualFile();
Segment getSegment();
Segment getRange();
}
@@ -0,0 +1,29 @@
/*
* 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;
import com.intellij.openapi.util.Segment;
/**
* pointer to a PsiFile + range inside the file.
* {@link #getRange()} will return the range inside the PsiFile
*/
public interface SmartPsiFileRange extends SmartPsiElementPointer<PsiFile> {
/**
* @return the range inside the PsiFile, or null if the range or PsiFile became invalid
*/
Segment getRange();
}
@@ -61,6 +61,6 @@ public class NonCodeUsageInfo extends MoveRenameUsageInfo{
}
public NonCodeUsageInfo replaceElement(PsiElement newElement) {
return new NonCodeUsageInfo(newElement, startOffset, endOffset, getReferencedElement(), newText);
return new NonCodeUsageInfo(newElement, getRangeInElement().getStartOffset(), getRangeInElement().getEndOffset(), getReferencedElement(), newText);
}
}
@@ -20,6 +20,7 @@ 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;
@@ -31,8 +32,9 @@ 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;
public final int startOffset; // in navigation element
public final int endOffset; // in navigation element
private final int startOffset; // in navigation element
private final int endOffset; // in navigation element
public final boolean isNonCodeUsage;
@@ -40,7 +42,6 @@ public class UsageInfo {
LOG.assertTrue(element.isValid());
element = element.getNavigationElement();
SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(element.getProject());
mySmartPointer = smartPointerManager.createSmartPsiElementPointer(element);
if (startOffset == -1 && endOffset == -1) {
// calculate natural element range
@@ -51,11 +52,22 @@ public class UsageInfo {
startOffset = element.getTextOffset() - range.getStartOffset();
endOffset = range.getEndOffset() - range.getStartOffset();
}
this.startOffset = startOffset;
this.endOffset = endOffset;
this.isNonCodeUsage = isNonCodeUsage;
LOG.assertTrue(startOffset >= 0, startOffset);
LOG.assertTrue(endOffset >= startOffset, endOffset-startOffset);
if (element instanceof PsiFile) {
PsiFile file = (PsiFile)element;
mySmartPointer = smartPointerManager.createSmartPsiFileRangePointer(file, TextRange.create(startOffset, endOffset));
this.startOffset = -1;
this.endOffset = -1;
}
else {
this.startOffset = startOffset;
this.endOffset = endOffset;
mySmartPointer = smartPointerManager.createSmartPsiElementPointer(element);
}
this.isNonCodeUsage = isNonCodeUsage;
}
public UsageInfo(@NotNull PsiElement element, boolean isNonCodeUsage) {
@@ -85,10 +97,27 @@ public class UsageInfo {
@Nullable
public PsiReference getReference() {
return getElement().getReference();
PsiElement element = getElement();
return element == null ? null : element.getReference();
}
/**
* @deprecated for the range in element use {@link #getRangeInElement} instead,
* for the whole text range in the file covered by this usage info, use {@link #getSegment()}
*/
public TextRange getRange() {
return getRangeInElement();
}
/**
* @return range in element
*/
@Nullable("null means range is invalid")
public TextRange getRangeInElement() {
if (mySmartPointer instanceof SmartPsiFileRange) {
return TextRange.create(mySmartPointer.getRange());
}
return new TextRange(startOffset, endOffset);
}
@@ -110,13 +139,20 @@ public class UsageInfo {
PsiElement element = getElement();
if (element == null) return -1;
TextRange range = element.getTextRange();
return range.getStartOffset() + startOffset;
TextRange rangeInElement = getRangeInElement();
if (rangeInElement == null) return -1;
return range.getStartOffset() + rangeInElement.getStartOffset();
}
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));
TextRange rangeInElement = getRangeInElement();
if (rangeInElement == null) return null;
return new TextRange(range.getStartOffset() + rangeInElement.getStartOffset(),
Math.min(range.getEndOffset(), range.getStartOffset() + rangeInElement.getEndOffset()));
}
public Project getProject() {
@@ -136,15 +172,15 @@ public class UsageInfo {
if (endOffset != usageInfo.endOffset) return false;
if (isNonCodeUsage != usageInfo.isNonCodeUsage) return false;
if (startOffset != usageInfo.startOffset) return false;
if (!Comparing.equal(getRangeInElement(), usageInfo.getRangeInElement())) return false;
return SmartPointerManager.getInstance(getProject()).pointToTheSameElement(mySmartPointer, usageInfo.mySmartPointer);
}
public int hashCode() {
int result = mySmartPointer != null ? mySmartPointer.hashCode() : 0;
result = 29 * result + startOffset;
result = 29 * result + endOffset;
TextRange rangeInElement = getRangeInElement();
result = 29 * result + (rangeInElement == null ? 0 : rangeInElement.hashCode());
result = 29 * result + (isNonCodeUsage ? 1 : 0);
return result;
}
@@ -202,8 +202,7 @@ public class ShowUsagesAction extends AnAction {
final List<Usage> usages = new ArrayList<Usage>();
final Set<UsageNode> visibleNodes = new LinkedHashSet<UsageNode>();
Processor<Usage> collect = new Processor<Usage>() {
final UsageTarget[] myUsageTarget = {new PsiElement2UsageTargetAdapter(handler.getPsiElement())};
private final UsageTarget[] myUsageTarget = {new PsiElement2UsageTargetAdapter(handler.getPsiElement())};
public boolean process(@NotNull Usage usage) {
synchronized (usages) {
if (visibleNodes.size() > maxUsages) return false;
@@ -95,7 +95,7 @@ public abstract class PsiAnchor {
return new TreeRangeReference(file, textRange.getStartOffset(), textRange.getEndOffset(), element.getClass(), lang, virtualFile);
}
private static int calcStubIndex(StubBasedPsiElement psi) {
public static int calcStubIndex(StubBasedPsiElement psi) {
if (psi instanceof PsiFile) {
return 0;
}
@@ -238,6 +238,41 @@ public abstract class PsiAnchor {
}
}
public static PsiElement restoreFromStubIndex(PsiFileWithStubSupport fileImpl,
int index,
IStubElementType elementType) {
if (fileImpl == null) return null;
StubTree tree = fileImpl.getStubTree();
boolean foreign = tree == null;
if (foreign) {
if (fileImpl instanceof PsiFileImpl) {
// Note: as far as this is a realization of StubIndexReference fileImpl#getContentElementType() must be instance of IStubFileElementType
tree = ((PsiFileImpl)fileImpl).calcStubTree();
}
else {
return null;
}
}
List<StubElement<?>> list = tree.getPlainList();
if (index >= list.size()) return null;
StubElement stub = list.get(index);
if (stub.getStubType() != elementType) return null;
if (foreign) {
final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
if (cachedPsi != null) return cachedPsi;
final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
return ast != null ? ast.getPsi() : null;
}
else {
return stub.getPsi();
}
}
private static class StubIndexReference extends PsiAnchor {
private final VirtualFile myVirtualFile;
private final Project myProject;
@@ -271,37 +306,7 @@ public abstract class PsiAnchor {
public PsiElement retrieve() {
return ApplicationManager.getApplication().runReadAction(new NullableComputable<PsiElement>() {
public PsiElement compute() {
PsiFileWithStubSupport fileImpl = (PsiFileWithStubSupport)getFile();
if (fileImpl == null) return null;
StubTree tree = fileImpl.getStubTree();
boolean foreign = tree == null;
if (foreign) {
if (fileImpl instanceof PsiFileImpl) {
// Note: as far as this is a realization of StubIndexReference fileImpl#getContentElementType() must be instance of IStubFileElementType
tree = ((PsiFileImpl)fileImpl).calcStubTree();
}
else {
return null;
}
}
List<StubElement<?>> list = tree.getPlainList();
if (myIndex >= list.size()) return null;
StubElement stub = list.get(myIndex);
if (stub.getStubType() != myElementType) return null;
if (foreign) {
final PsiElement cachedPsi = ((StubBase)stub).getCachedPsi();
if (cachedPsi != null) return cachedPsi;
final ASTNode ast = fileImpl.findTreeForStub(tree, stub);
return ast != null ? ast.getPsi() : null;
}
else {
return stub.getPsi();
}
return restoreFromStubIndex((PsiFileWithStubSupport)getFile(), myIndex, myElementType);
}
});
}
@@ -79,7 +79,13 @@ class FileElementInfo implements SmartPointerElementInfo {
}
@Override
public Segment getSegment() {
public Segment getRange() {
return new TextRange(0, (int)myVirtualFile.getLength());
}
@NotNull
@Override
public Project getProject() {
return myProject;
}
}
@@ -16,6 +16,7 @@
package com.intellij.psi.impl.smartPointers;
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.vfs.VirtualFile;
@@ -28,9 +29,11 @@ import org.jetbrains.annotations.NotNull;
*/
class HardElementInfo implements SmartPointerElementInfo {
private final PsiElement myElement;
private final Project myProject;
public HardElementInfo(@NotNull PsiElement element) {
public HardElementInfo(@NotNull Project project, @NotNull PsiElement element) {
myElement = element;
myProject = project;
}
public Document getDocumentToSynchronize() {
@@ -72,7 +75,13 @@ class HardElementInfo implements SmartPointerElementInfo {
}
@Override
public Segment getSegment() {
public Segment getRange() {
return myElement.getTextRange();
}
@NotNull
@Override
public Project getProject() {
return myProject;
}
}
@@ -17,6 +17,7 @@ package com.intellij.psi.impl.smartPointers;
import com.intellij.injected.editor.DocumentWindow;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
@@ -29,14 +30,14 @@ import org.jetbrains.annotations.NotNull;
class InjectedSelfElementInfo extends SelfElementInfo {
private DocumentWindow myDocument;
InjectedSelfElementInfo(@NotNull PsiElement anchor, PsiFile containingFile) {
super(anchor, containingFile);
InjectedSelfElementInfo(@NotNull Project project, @NotNull PsiElement anchor, @NotNull PsiFile containingFile) {
super(project, anchor.getTextRange(), anchor.getClass(), containingFile);
assert containingFile.getContext() != null;
}
protected TextRange getPersistentAnchorRange(final PsiElement anchor) {
final TextRange textRange = super.getPersistentAnchorRange(anchor);
Document document = PsiDocumentManager.getInstance(anchor.getProject()).getDocument(anchor.getContainingFile());
protected TextRange getPersistentAnchorRange(final TextRange anchor, PsiFile containingFile) {
final TextRange textRange = super.getPersistentAnchorRange(anchor, containingFile);
Document document = PsiDocumentManager.getInstance(containingFile.getProject()).getDocument(containingFile);
// must be non-text file
if (!(document instanceof DocumentWindow)) return textRange;
@@ -141,11 +141,11 @@ public class LazyPointerImpl<E extends PsiElement> implements SmartPointerEx<E>
}
@Override
public Segment getSegment() {
public Segment getRange() {
E element = myElement;
if (element != null && element.isValid()) return element.getTextRange();
SmartPsiElementPointer pointer = myPointer;
if (pointer != null) return pointer.getSegment();
if (pointer != null) return pointer.getRange();
PsiAnchor anchor = myAnchor;
if (anchor != null) {
final PsiElement psiElement = anchor.retrieve();
@@ -44,22 +44,21 @@ public class SelfElementInfo implements SmartPointerElementInfo {
private int mySyncStartOffset;
private int mySyncEndOffset;
protected boolean mySyncMarkerIsValid;
private Class myType;
private final Class myType;
protected final Project myProject;
@SuppressWarnings({"UnusedDeclaration"})
private RangeMarker myRangeMarker; //maintain hard reference during modification
public SelfElementInfo(@NotNull PsiElement anchor, PsiFile containingFile) {
LOG.assertTrue(anchor.isPhysical());
LOG.assertTrue(anchor.isValid());
PsiFile file = anchor.getContainingFile();
myVirtualFile = file.getVirtualFile();
TextRange range = anchor.getTextRange();
public SelfElementInfo(@NotNull Project project, @NotNull TextRange anchor, @NotNull Class anchorClass, @NotNull PsiFile containingFile) {
myVirtualFile = containingFile.getVirtualFile();
myType = anchorClass;
TextRange range = anchor;//.getTextRange();
LOG.assertTrue(range != null, anchor);
range = getPersistentAnchorRange(anchor);
range = getPersistentAnchorRange(range, containingFile);
myProject = file.getProject();
myProject = project;
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
Document document = documentManager.getDocument(file);
Document document = documentManager.getDocument(containingFile);
if (document == null || documentManager.isUncommited(document)) {
mySyncMarkerIsValid = false;
return;
@@ -82,19 +81,17 @@ public class SelfElementInfo implements SmartPointerElementInfo {
// LOG.error("File=" + file);
//}
if (documentManager.isUncommited(document)) {
mySyncMarkerIsValid = false;
}
else {
mySyncMarkerIsValid = true;
myType = anchor.getClass();
}
mySyncMarkerIsValid = true;
setRange(range);
}
protected void setRange(TextRange range) {
mySyncStartOffset = range.getStartOffset();
mySyncEndOffset = range.getEndOffset();
}
protected TextRange getPersistentAnchorRange(final PsiElement anchor) {
return anchor.getTextRange();
protected TextRange getPersistentAnchorRange(final TextRange anchor, PsiFile containingFile) {
return anchor;
}
public Document getDocumentToSynchronize() {
@@ -128,14 +125,15 @@ public class SelfElementInfo implements SmartPointerElementInfo {
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);
marker = null;
}
myRangeMarker = marker; //make sure marker wont be gced
}
// after change
@@ -267,8 +265,14 @@ public class SelfElementInfo implements SmartPointerElementInfo {
}
@Override
public Segment getSegment() {
public Segment getRange() {
if (!mySyncMarkerIsValid) return null;
return new TextRange(getSyncStartOffset(), getSyncEndOffset());
}
@NotNull
@Override
public Project getProject() {
return myProject;
}
}
@@ -17,9 +17,11 @@
package com.intellij.psi.impl.smartPointers;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
interface SmartPointerElementInfo {
@@ -39,5 +41,6 @@ interface SmartPointerElementInfo {
boolean pointsToTheSameElementAs(SmartPointerElementInfo other);
VirtualFile getVirtualFile();
Segment getSegment();
Segment getRange();
@NotNull Project getProject();
}
@@ -22,6 +22,7 @@ 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.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import org.jetbrains.annotations.NotNull;
@@ -154,15 +155,28 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
}
PsiFile containingFile = element.getContainingFile();
SmartPointerEx<E> pointer = new SmartPsiElementPointerImpl<E>(myProject, element, containingFile);
initPointer(element, pointer, containingFile);
initPointer(pointer, containingFile);
return pointer;
}
private <E extends PsiElement> void initPointer(E element, SmartPointerEx<E> pointer, PsiFile containingFile) {
@Override
@NotNull
public SmartPsiFileRange createSmartPsiFileRangePointer(@NotNull PsiFile file, @NotNull TextRange range) {
if (!file.isValid()) {
LOG.error("Invalid element:" + file);
}
SmartPsiFileRangePointerImpl pointer = new SmartPsiFileRangePointerImpl(file, range);
initPointer(pointer, file);
return pointer;
}
private <E extends PsiElement> void initPointer(SmartPointerEx<E> pointer, PsiFile containingFile) {
if (containingFile == null) return;
synchronized (containingFile) {
Document document = PsiDocumentManager.getInstance(myProject).getCachedDocument(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
@@ -28,23 +28,25 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.ref.SoftReference;
class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx<E> {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPsiElementPointerImpl");
private Reference<E> myElement;
private final SmartPointerElementInfo myElementInfo;
private final Project myProject;
private final Class<? extends PsiElement> myElementClass;
public SmartPsiElementPointerImpl(@NotNull Project project, @NotNull E element, PsiFile containingFile) {
myProject = project;
public SmartPsiElementPointerImpl(@NotNull Project project, @NotNull E element, @Nullable PsiFile containingFile) {
this(element, createElementInfo(project, element, containingFile), element.getClass());
}
public SmartPsiElementPointerImpl(@NotNull E element,
@NotNull SmartPointerElementInfo elementInfo,
@NotNull Class<? extends PsiElement> elementClass) {
ApplicationManager.getApplication().assertReadAccessAllowed();
myElement = new WeakReference<E>(element);
myElementInfo = createElementInfo(element, containingFile);
myElementClass = element.getClass();
cacheElement(element);
myElementClass = elementClass;
myElementInfo = elementInfo;
// Assert document committed.
//todo
//if (containingFile != null) {
@@ -64,7 +66,7 @@ class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx
public boolean equals(Object obj) {
if (!(obj instanceof SmartPsiElementPointer)) return false;
SmartPsiElementPointer pointer = (SmartPsiElementPointer)obj;
return SmartPointerManager.getInstance(myProject).pointToTheSameElement(this, pointer);
return SmartPointerManager.getInstance(getProject()).pointToTheSameElement(this, pointer);
}
public int hashCode() {
@@ -73,29 +75,34 @@ class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx
@NotNull
public Project getProject() {
return myProject;
return myElementInfo.getProject();
}
@Nullable
public E getElement() {
PsiElement element = getCachedElement();
E element = getCachedElement();
if (element != null && !element.isValid()) {
element = null;
}
if (element == null && myElementInfo != null) {
element = myElementInfo.restoreElement();
element = (E)myElementInfo.restoreElement();
if (element != null && (!element.getClass().equals(myElementClass) || !element.isValid())) {
element = null;
}
myElement = element == null ? null : new WeakReference(element);
cacheElement(element);
}
return (E)element;
return element;
}
private void cacheElement(E element) {
myElement = element == null ? null : new SoftReference<E>(element);
}
private E getCachedElement() {
return myElement == null ? null : myElement.get();
Reference<E> ref = myElement;
return ref == null ? null : ref.get();
}
public PsiFile getContainingFile() {
@@ -114,7 +121,7 @@ class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx
final E resolved = getElement();
return resolved != null ? resolved.getContainingFile() : null;
}
return PsiDocumentManager.getInstance(myProject).getPsiFile(doc);
return PsiDocumentManager.getInstance(getProject()).getPsiFile(doc);
}
public VirtualFile getVirtualFile() {
@@ -122,21 +129,23 @@ class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx
}
@Override
public Segment getSegment() {
return myElementInfo.getSegment();
public Segment getRange() {
return myElementInfo.getRange();
}
@NotNull
private SmartPointerElementInfo createElementInfo(@NotNull E element, PsiFile containingFile) {
if (element instanceof PsiCompiledElement || !element.isPhysical() || containingFile == null || element.getTextRange() == null) {
return new HardElementInfo(element);
private static <E extends PsiElement> SmartPointerElementInfo createElementInfo(@NotNull Project project, @NotNull E element, PsiFile containingFile) {
if (element instanceof PsiCompiledElement || !element.isPhysical() || containingFile == null) {
return new HardElementInfo(project, element);
}
for(SmartPointerElementInfoFactory factory: Extensions.getExtensions(SmartPointerElementInfoFactory.EP_NAME)) {
final SmartPointerElementInfo result = factory.createElementInfo(element);
if (result != null) {
return result;
}
if (result != null) return result;
}
if (element.getTextRange() == null) {
return new HardElementInfo(project, element);
}
if (element instanceof PsiFile) {
@@ -144,10 +153,13 @@ class SmartPsiElementPointerImpl<E extends PsiElement> implements SmartPointerEx
}
if (containingFile.getContext() != null) {
return new InjectedSelfElementInfo(element, containingFile);
return new InjectedSelfElementInfo(project, element, containingFile);
}
return new SelfElementInfo(element, containingFile);
LOG.assertTrue(element.isPhysical());
LOG.assertTrue(element.isValid());
return new SelfElementInfo(project, element.getTextRange(), element.getClass(), containingFile);
}
public void documentAndPsiInSync() {
@@ -0,0 +1,41 @@
/*
* 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.util.TextRange;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SmartPsiFileRange;
import org.jetbrains.annotations.NotNull;
/**
* User: cdr
*/
public class SmartPsiFileRangePointerImpl extends SmartPsiElementPointerImpl<PsiFile> implements SmartPsiFileRange {
public SmartPsiFileRangePointerImpl(@NotNull PsiFile containingFile, @NotNull TextRange range) {
super(containingFile, createElementInfo(containingFile, range), PsiFile.class);
}
@NotNull
private static SmartPointerElementInfo createElementInfo(@NotNull PsiFile containingFile, @NotNull TextRange range) {
return new SelfElementInfo(containingFile.getProject(), range, PsiFile.class, containingFile);
}
@Override
public PsiFile getElement() {
if (getRange() == null) return null; // range is invalid
return super.getElement();
}
}
@@ -168,7 +168,7 @@ public class MoveFilesOrDirectoriesProcessor extends BaseRefactoringProcessor {
// sort by offset descending to process correctly several usages in one PsiElement [IDEADEV-33013]
Arrays.sort(usages, new Comparator<UsageInfo>() {
public int compare(final UsageInfo o1, final UsageInfo o2) {
return o1.getElement() == o2.getElement() ? o2.startOffset - o1.startOffset : 0;
return o1.getElement() == o2.getElement() ? o2.getRangeInElement().getStartOffset() - o1.getRangeInElement().getStartOffset() : 0;
}
});
@@ -274,14 +274,14 @@ public class RenameUtil {
if (element == null) continue;
final PsiFile containingFile = element.getContainingFile();
final Document document = psiDocumentManager.getDocument(containingFile);
int fileOffset = element.getTextRange().getStartOffset() + usage.startOffset;
int fileOffset = usage.getSegment().getStartOffset();
ArrayList<UsageOffset> list = docsToOffsetsMap.get(document);
if (list == null) {
list = new ArrayList<UsageOffset>();
docsToOffsetsMap.put(document, list);
}
list.add(new UsageOffset(fileOffset, fileOffset + usage.endOffset - usage.startOffset, usage.newText));
list.add(new UsageOffset(fileOffset, fileOffset + usage.getRangeInElement().getLength(), usage.newText));
}
for (Document document : docsToOffsetsMap.keySet()) {
@@ -21,6 +21,7 @@ import com.intellij.lang.findUsages.FindUsagesProvider;
import com.intellij.lang.findUsages.LanguageFindUsages;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.ElementDescriptionUtil;
import com.intellij.psi.PsiElement;
@@ -120,11 +121,12 @@ public class UsageViewUtil {
if (psiReference == null) continue;
int injectionOffsetInMasterFile = InjectedLanguageManager.getInstance(usageElement.getProject()).injectedToHost(usageElement, usageElement.getTextOffset());
TextRange range = usage.getRangeInElement().shiftRight(injectionOffsetInMasterFile);
set.remove(
NonCodeUsageInfo.create(
context.getContainingFile(),
usage.startOffset + injectionOffsetInMasterFile,
usage.endOffset + injectionOffsetInMasterFile,
range.getStartOffset(),
range.getEndOffset(),
((MoveRenameUsageInfo)usage).getReferencedElement(),
newTextInNonCodeUsage
)
@@ -135,4 +137,4 @@ public class UsageViewUtil {
}
return set.toArray(new UsageInfo[set.size()]);
}
}
}
@@ -123,7 +123,7 @@ public class ChunkExtractor {
private TextChunk[] extractChunks(UsageInfo2UsageAdapter usageInfo2UsageAdapter) {
int absoluteStartOffset = usageInfo2UsageAdapter.getNavigationOffset();
assert absoluteStartOffset != -1;
if (absoluteStartOffset == -1) return TextChunk.EMPTY_ARRAY;
final int lineNumber = myDocument.getLineNumber(absoluteStartOffset);
final int columnNumber = absoluteStartOffset - myDocument.getLineStartOffset(lineNumber);
@@ -146,7 +146,7 @@ public class UsageInfo2UsageAdapter implements UsageInModule,
FileEditor editor = FileEditorManager.getInstance(getProject()).getSelectedEditor(virtualFile);
if (!(editor instanceof TextEditor)) return null;
return new TextEditorLocation(getUsageInfo().startOffset + getElement().getTextRange().getStartOffset(), (TextEditor)editor);
return new TextEditorLocation(getUsageInfo().getSegment().getStartOffset(), (TextEditor)editor);
}
public void selectInEditor() {
@@ -283,6 +283,7 @@ public class UsageInfo2UsageAdapter implements UsageInModule,
public boolean merge(MergeableUsage other) {
if (!(other instanceof UsageInfo2UsageAdapter)) return false;
UsageInfo2UsageAdapter u2 = (UsageInfo2UsageAdapter)other;
assert u2 != this;
if (myLineNumber != u2.myLineNumber || getFile() != u2.getFile()) return false;
myMergedUsageInfos.addAll(u2.myMergedUsageInfos);
Collections.sort(myMergedUsageInfos, new Comparator<UsageInfo>() {
@@ -296,6 +297,7 @@ public class UsageInfo2UsageAdapter implements UsageInModule,
}
public void reset() {
ApplicationManager.getApplication().assertIsDispatchThread();
if (!myMergedUsageInfos.isEmpty()) {
myMergedUsageInfos.clear();
myMergedUsageInfos.add(myUsageInfo);
@@ -372,16 +374,12 @@ public class UsageInfo2UsageAdapter implements UsageInModule,
@NotNull
public TextChunk[] getText() {
TextChunk[] chunks = myTextChunks.get();
if (chunks == null) {
myModificationStamp = -1;
}
if (chunks == null || isValid()) {
final long currentModificationStamp = getCurrentModificationStamp();
boolean isModified = currentModificationStamp != myModificationStamp;
if (chunks == null || isValid() && isModified) {
// the check below makes sense only for valid PsiElement
final long currentModificationStamp = getCurrentModificationStamp();
if (currentModificationStamp != myModificationStamp) {
chunks = initChunks();
myModificationStamp = currentModificationStamp;
}
chunks = initChunks();
myModificationStamp = currentModificationStamp;
}
return chunks;
}
@@ -25,12 +25,10 @@ import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.SideBorder;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.util.ui.UIUtil;
@@ -111,7 +109,7 @@ public class UsagePreviewPanel extends JPanel implements Disposable {
TextAttributes attributes = colorManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
TextRange elementRange = psiElement.getTextRange();
TextRange infoRange = info.getRange();
TextRange infoRange = info.getRangeInElement();
TextRange textRange = elementRange.intersection(infoRange);
if (textRange == null) textRange = elementRange;
// hack to determine element range to highlight
@@ -472,6 +472,7 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
}
private void rulesChanged() {
ApplicationManager.getApplication().assertIsDispatchThread();
final ArrayList<UsageState> states = new ArrayList<UsageState>();
captureUsagesExpandState(new TreePath(myTree.getModel().getRoot()), states);
final List<Usage> allUsages = new ArrayList<Usage>(myUsageNodes.keySet());
@@ -640,7 +641,9 @@ public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTra
});
}
public void reset() {
private void reset() {
ApplicationManager.getApplication().assertIsDispatchThread();
myUsageNodes.clear();
myIsFirstVisibleUsageFound = false;
@@ -98,9 +98,11 @@ class UsageViewTreeCellRenderer extends ColoredTreeCellRenderer {
append(UsageViewBundle.message("node.readonly") + " ", patchAttrs(node, ourReadOnlyAttributes));
}
TextChunk[] text = node.getUsage().getPresentation().getText();
for (TextChunk textChunk : text) {
append(textChunk.getText(), patchAttrs(node, SimpleTextAttributes.fromTextAttributes(textChunk.getAttributes())));
if (node.isValid()) {
TextChunk[] text = node.getUsage().getPresentation().getText();
for (TextChunk textChunk : text) {
append(textChunk.getText(), patchAttrs(node, SimpleTextAttributes.fromTextAttributes(textChunk.getAttributes())));
}
}
}
else if (userObject instanceof String) {
@@ -17,8 +17,10 @@ package com.intellij.usages.impl;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.openapi.vfs.encoding.EncodingManagerImpl;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.PsiFileImpl;
@@ -31,7 +33,7 @@ import com.intellij.util.ui.UIUtil;
/**
* User: cdr
*/
public class UsageViewMemoryTest extends LightPlatformCodeInsightTestCase{
public class UsageViewTest extends LightPlatformCodeInsightTestCase{
public void testUsageViewDoesNotHoldPsiFilesOrDocuments() throws Exception {
PsiFile psiFile = createFile("X.java", "public class X{} //iuggjhfg");
Usage[] usages = new Usage[100];
@@ -50,6 +52,35 @@ public class UsageViewMemoryTest extends LightPlatformCodeInsightTestCase{
LeakHunter.checkLeak(usageView, Document.class);
}
public void testUsageViewHandlesDocumentChange() throws Exception {
PsiFile psiFile = createFile("X.java", "public class X{ int xxx; } //comment");
Usage usage = createUsage(psiFile, psiFile.getText().indexOf("xxx"));
UsageView usageView = UsageViewManager.getInstance(getProject()).createUsageView(UsageTarget.EMPTY_ARRAY, new Usage[]{usage}, new UsageViewPresentation(), null);
Disposer.register(getTestRootDisposable(), usageView);
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject());
Document document = documentManager.getDocument(psiFile);
document.insertString(0, "/* sdfsdfsd */");
documentManager.commitAllDocuments();
int navigationOffset = ((UsageInfo2UsageAdapter)usage).getUsageInfo().getNavigationOffset();
assertEquals(psiFile.getText().indexOf("xxx"), navigationOffset);
}
public void testTextUsageInfoHandlesDocumentChange() throws Exception {
PsiFile psiFile = createFile("X.java", "public class X{ int xxx; } //comment");
Usage usage = new UsageInfo2UsageAdapter(new UsageInfo(psiFile, psiFile.getText().indexOf("xxx"), StringUtil.indexOfSubstringEnd(psiFile.getText(),"xxx")));
UsageView usageView = UsageViewManager.getInstance(getProject()).createUsageView(UsageTarget.EMPTY_ARRAY, new Usage[]{usage}, new UsageViewPresentation(), null);
Disposer.register(getTestRootDisposable(), usageView);
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject());
Document document = documentManager.getDocument(psiFile);
document.insertString(0, "/* sdfsdfsd */");
documentManager.commitAllDocuments();
int navigationOffset = ((UsageInfo2UsageAdapter)usage).getUsageInfo().getNavigationOffset();
assertEquals(psiFile.getText().indexOf("xxx"), navigationOffset);
}
private static Usage createUsage(PsiFile psiFile, int offset) {
PsiElement element = psiFile.findElementAt(offset % psiFile.getTextLength());
assertNotNull(element);
@@ -160,7 +160,7 @@ public class XsltStuffProvider implements UsageGroupingRuleProvider, InspectionT
} else {
final PsiReference[] references = u.getElement().getReferences();
for (PsiReference reference : references) {
if (reference.getRangeInElement().equals(usageInfo.getRange())) {
if (reference.getRangeInElement().equals(usageInfo.getRangeInElement())) {
return buildGroup(reference.resolve(), usageInfo, false);
}
}
@@ -122,7 +122,13 @@ public abstract class XmlElementImpl extends CompositePsiElement implements XmlE
}
final XmlElement including = getUserData(INCLUDING_ELEMENT);
return including != null ? including : getAstParent().getNavigationElement();
if (including != null) {
return including;
}
PsiElement astParent = getAstParent();
PsiElement parentNavigation = astParent.getNavigationElement();
if (parentNavigation.getTextOffset() == getTextOffset()) return parentNavigation;
return this;
}
return super.getNavigationElement();
}