diff --git a/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java b/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java index efaf4626a856..19bc73f98c66 100644 --- a/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java +++ b/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfo.java @@ -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; } } diff --git a/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfoFactory.java b/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfoFactory.java index 7c8385fbfe27..c84d8f341b3c 100644 --- a/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfoFactory.java +++ b/java/java-impl/src/com/intellij/psi/impl/smartPointers/AnchorElementInfoFactory.java @@ -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; } diff --git a/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImplicitVariableElementInfoFactory.java b/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImplicitVariableElementInfoFactory.java index ad95e90b3b6e..403cb17c8e06 100644 --- a/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImplicitVariableElementInfoFactory.java +++ b/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImplicitVariableElementInfoFactory.java @@ -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(); + } } } diff --git a/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImportListElementInfoFactory.java b/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImportListElementInfoFactory.java index 853a82691117..be18abde62b2 100644 --- a/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImportListElementInfoFactory.java +++ b/java/java-impl/src/com/intellij/psi/impl/smartPointers/ImportListElementInfoFactory.java @@ -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(); } } } diff --git a/java/java-impl/src/com/intellij/usages/impl/rules/ClassGroupingRule.java b/java/java-impl/src/com/intellij/usages/impl/rules/ClassGroupingRule.java index 047ce9493305..9008e87eaafe 100644 --- a/java/java-impl/src/com/intellij/usages/impl/rules/ClassGroupingRule.java +++ b/java/java-impl/src/com/intellij/usages/impl/rules/ClassGroupingRule.java @@ -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) { diff --git a/java/java-impl/src/com/intellij/usages/impl/rules/MethodGroupingRule.java b/java/java-impl/src/com/intellij/usages/impl/rules/MethodGroupingRule.java index e009b471f795..c36921b48c70 100644 --- a/java/java-impl/src/com/intellij/usages/impl/rules/MethodGroupingRule.java +++ b/java/java-impl/src/com/intellij/usages/impl/rules/MethodGroupingRule.java @@ -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 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); diff --git a/platform/lang-api/src/com/intellij/psi/IdentitySmartPointer.java b/platform/lang-api/src/com/intellij/psi/IdentitySmartPointer.java index 4f9f45d26faa..03cd2de5a16f 100644 --- a/platform/lang-api/src/com/intellij/psi/IdentitySmartPointer.java +++ b/platform/lang-api/src/com/intellij/psi/IdentitySmartPointer.java @@ -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 implements SmartPsiElementPointer { 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 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 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(); + } } diff --git a/platform/lang-api/src/com/intellij/psi/SmartPointerManager.java b/platform/lang-api/src/com/intellij/psi/SmartPointerManager.java index 850cf4a1622b..b67cbcb28a4c 100644 --- a/platform/lang-api/src/com/intellij/psi/SmartPointerManager.java +++ b/platform/lang-api/src/com/intellij/psi/SmartPointerManager.java @@ -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 SmartPsiElementPointer 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); } diff --git a/platform/lang-api/src/com/intellij/psi/SmartPsiElementPointer.java b/platform/lang-api/src/com/intellij/psi/SmartPsiElementPointer.java index c13bdc0e6ccc..66e847d60439 100644 --- a/platform/lang-api/src/com/intellij/psi/SmartPsiElementPointer.java +++ b/platform/lang-api/src/com/intellij/psi/SmartPsiElementPointer.java @@ -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 { @NotNull Project getProject(); + + VirtualFile getVirtualFile(); + + Segment getSegment(); } diff --git a/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java b/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java index 0f12b53223b3..d3676670a0ca 100644 --- a/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java +++ b/platform/lang-api/src/com/intellij/psi/impl/ElementBase.java @@ -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 ICON_COMPUTE = new NullableFunction() { + 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 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() { - 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() { diff --git a/platform/lang-api/src/com/intellij/usageView/UsageInfo.java b/platform/lang-api/src/com/intellij/usageView/UsageInfo.java index 2976cfe62ea8..b149bf276776 100644 --- a/platform/lang-api/src/com/intellij/usageView/UsageInfo.java +++ b/platform/lang-api/src/com/intellij/usageView/UsageInfo.java @@ -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(); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/IntentionUsagePanel.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/IntentionUsagePanel.java index 3fb0dbc78880..a3ad3a12743f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/IntentionUsagePanel.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/IntentionUsagePanel.java @@ -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 markers = new ArrayList(); + List markers = new ArrayList(); 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("", spotStart); + final int spotEnd = text.indexOf("", 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()) { diff --git a/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java b/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java index 08fe256dfad2..c47475e0cc42 100644 --- a/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java +++ b/platform/lang-impl/src/com/intellij/find/replaceInProject/ReplaceInProjectManager.java @@ -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 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() { + @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); - } - } + }); } }); } diff --git a/platform/lang-impl/src/com/intellij/ide/impl/dataRules/VirtualFileArrayRule.java b/platform/lang-impl/src/com/intellij/ide/impl/dataRules/VirtualFileArrayRule.java index 462c30c6d566..fcd84136349a 100644 --- a/platform/lang-impl/src/com/intellij/ide/impl/dataRules/VirtualFileArrayRule.java +++ b/platform/lang-impl/src/com/intellij/ide/impl/dataRules/VirtualFileArrayRule.java @@ -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 result = new HashSet(); - - 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) { diff --git a/platform/lang-impl/src/com/intellij/psi/PsiAnchor.java b/platform/lang-impl/src/com/intellij/psi/PsiAnchor.java index d53354d09306..808dfd9ec6e4 100644 --- a/platform/lang-impl/src/com/intellij/psi/PsiAnchor.java +++ b/platform/lang-impl/src/com/intellij/psi/PsiAnchor.java @@ -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); } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java index 792c763c64aa..d8aa92c6e0d5 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java @@ -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 myListeners = new ArrayList(); - private Listener[] myCachedListeners = null; //guarded by mylisteners + private final List 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() { + 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 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 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; diff --git a/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java b/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java index 819fb5c75547..0f59c3011580 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/PsiToDocumentSynchronizer.java @@ -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); } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/FileManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/FileManagerImpl.java index bc08a0f12638..855428bd1783 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/FileManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/FileManagerImpl.java @@ -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); + } } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java index c825b9ec7302..67500e1be06b 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java @@ -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()); + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java new file mode 100644 index 000000000000..3627aeb1b0e9 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java @@ -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(); + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/InjectedSelfElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/InjectedSelfElementInfo.java index 6d98f3736a25..88143db18c5c 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/InjectedSelfElementInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/InjectedSelfElementInfo.java @@ -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); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/LazyPointerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/LazyPointerImpl.java index b091bc7de1b2..c22b15b37e3f 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/LazyPointerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/LazyPointerImpl.java @@ -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 implements SmartPointerEx private final Class 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 implements SmartPointerEx 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 implements SmartPointerEx 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 implements SmartPointerEx 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 implements SmartPointerEx } 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 implements SmartPointerEx 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; + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java index 8f338691024f..2975a3c156c3 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SelfElementInfo.java @@ -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 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 ref = myMarkerRef; + return ref == null ? null : ref.get(); + } + + private void setMarker(RangeMarker marker) { + myMarkerRef = marker == null ? null : new SoftReference(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()); + } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfo.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfo.java index 98435ed3f3ca..6c0e37657cbb 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfo.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfo.java @@ -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(); } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfoFactory.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfoFactory.java index e33fbc48cb25..0cc69adb1972 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfoFactory.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerElementInfoFactory.java @@ -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 EP_NAME = ExtensionPointName.create("com.intellij.smartPointerElementInfoFactory"); @Nullable - SmartPointerElementInfo createElementInfo(PsiElement element); + SmartPointerElementInfo createElementInfo(@NotNull PsiElement element); } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerEx.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerEx.java index 4ab980b7ef08..c04ba391837d 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerEx.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerEx.java @@ -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 extends SmartPsiElementPointer { - void fastenBelt(); +public interface SmartPointerEx extends SmartPsiElementPointer, Disposable { + void fastenBelt(int offset); void documentAndPsiInSync(); void dispose(); + + void unfastenBelt(int offset); } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java index 3e1a0f9da35d..70bd6e2f8808 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java @@ -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>> SMART_POINTERS_IN_PSI_FILE_KEY = Key.create( - "SMART_POINTERS_IN_DOCUMENT_KEY"); + private static final Key>> SMART_POINTERS_IN_PSI_FILE_KEY = Key.create("SMART_POINTERS_IN_DOCUMENT_KEY"); private static final Key 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> pointers = file.getUserData(SMART_POINTERS_IN_PSI_FILE_KEY); + List> pointers = getPointers(file); if (pointers == null) return; int index = 0; @@ -57,7 +57,7 @@ public class SmartPointerManagerImpl extends SmartPointerManager { WeakReference 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 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> pointers = getPointers(file); + if (pointers == null) return; + + int index = 0; + for (int i = 0; i < pointers.size(); i++) { + WeakReference 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> pointers = file.getUserData(SMART_POINTERS_IN_PSI_FILE_KEY); + List> pointers = getPointers(file); if (pointers == null) return; int index = 0; @@ -124,64 +148,71 @@ public class SmartPointerManagerImpl extends SmartPointerManager { } @NotNull - public SmartPsiElementPointer createSmartPsiElementPointer(E element) { + public SmartPsiElementPointer createSmartPsiElementPointer(@NotNull E element) { if (!element.isValid()) { LOG.error("Invalid element:" + element); } - - PsiFile file = element.getContainingFile(); - - if (isSafeReparseable(file)) { - return new IdentitySmartPointer(element); - } - - SmartPointerEx pointer = new SmartPsiElementPointerImpl(myProject, element); - initPointer(element, pointer); + PsiFile containingFile = element.getContainingFile(); + SmartPointerEx pointer = new SmartPsiElementPointerImpl(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 void initPointer(E element, SmartPointerEx 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 void initPointer(E element, SmartPointerEx 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> pointers = getPointers(containingFile); + if (pointers == null) { + pointers = new ArrayList>(); + containingFile.putUserData(SMART_POINTERS_IN_PSI_FILE_KEY, pointers); + } + pointers.add(new WeakReference(pointer)); - ArrayList> pointers = file.getUserData(SMART_POINTERS_IN_PSI_FILE_KEY); - if (pointers == null) { - pointers = new ArrayList>(); - file.putUserData(SMART_POINTERS_IN_PSI_FILE_KEY, pointers); - } - pointers.add(new WeakReference(pointer)); - - if (areBeltsFastened(file)) { - pointer.fastenBelt(); - } + if (areBeltsFastened(containingFile)) { + pointer.fastenBelt(0); } } } + private static List> 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 SmartPsiElementPointer createLazyPointer(E element) { - LazyPointerImpl pointer = new LazyPointerImpl(element); - initPointer(element, pointer); - return pointer; + public SmartPsiElementPointer createLazyPointer(@NotNull E element) { + return createSmartPsiElementPointer(element); + //LazyPointerImpl pointer = new LazyPointerImpl(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()); } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java index da8b80f07d38..7893c8f4c496 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java @@ -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 implements SmartPointerEx { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPsiElementPointerImpl"); - private E myElement; - private SmartPointerElementInfo myElementInfo; + private Reference myElement; + private final SmartPointerElementInfo myElementInfo; private final Project myProject; + private final Class 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(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 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 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 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; } } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ChangeUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ChangeUtil.java index d7ec9af5dd73..684d3dae7878 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ChangeUtil.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ChangeUtil.java @@ -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); diff --git a/platform/platform-api/src/com/intellij/util/ui/RangeBlinker.java b/platform/platform-api/src/com/intellij/util/ui/RangeBlinker.java index 3ed4e6d7c1c1..952dc7e64869 100644 --- a/platform/platform-api/src/com/intellij/util/ui/RangeBlinker.java +++ b/platform/platform-api/src/com/intellij/util/ui/RangeBlinker.java @@ -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 myMarkers = new ArrayList(); + private final List myMarkers = new ArrayList(); 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 markers) { + + public void resetMarkers(final List 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); diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java index c667e0e418f2..68d778ffc720 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java @@ -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); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java b/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java index 7b08c9bb603e..fb79f4e906aa 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/FileContentQueue.java @@ -43,7 +43,7 @@ public class FileContentQueue { private long myTotalSize; private final ArrayBlockingQueue myQueue = new ArrayBlockingQueue(256); - private Queue myPushbackBuffer = new ArrayDeque(); + private final Queue myPushbackBuffer = new ArrayDeque(); public void queue(final Collection files, @Nullable final ProgressIndicator indicator) { final Runnable contentLoadingRunnable = new Runnable() { diff --git a/platform/testFramework/src/_LastInSuiteTest.java b/platform/testFramework/src/_LastInSuiteTest.java index e9d79552eb78..13cd6b9eff84 100644 --- a/platform/testFramework/src/_LastInSuiteTest.java +++ b/platform/testFramework/src/_LastInSuiteTest.java @@ -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 diff --git a/platform/testFramework/src/com/intellij/testFramework/LeakedProjectHunter.java b/platform/testFramework/src/com/intellij/testFramework/LeakHunter.java similarity index 87% rename from platform/testFramework/src/com/intellij/testFramework/LeakedProjectHunter.java rename to platform/testFramework/src/com/intellij/testFramework/LeakHunter.java index e726ef17c497..3542b804050f 100644 --- a/platform/testFramework/src/com/intellij/testFramework/LeakedProjectHunter.java +++ b/platform/testFramework/src/com/intellij/testFramework/LeakHunter.java @@ -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> allFields = new THashMap>(); private static List getAllFields(Class aClass) { List cached = allFields.get(aClass); @@ -65,13 +67,13 @@ public class LeakedProjectHunter { } private static final Set visited = new THashSet(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 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() { @Override diff --git a/platform/usageView/src/com/intellij/usages/ChunkExtractor.java b/platform/usageView/src/com/intellij/usages/ChunkExtractor.java index 9ee5e797ab2c..4d6ea46f2d64 100644 --- a/platform/usageView/src/com/intellij/usages/ChunkExtractor.java +++ b/platform/usageView/src/com/intellij/usages/ChunkExtractor.java @@ -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 { - private WeakReference myRef; @NotNull @@ -74,7 +73,6 @@ public class ChunkExtractor { myRef = new WeakReference(result); return result; } - } private static final ThreadLocal>> ourExtractors = new ThreadLocal>>() { @@ -95,19 +93,18 @@ public class ChunkExtractor { } }; - public static TextChunk[] extractChunks(PsiElement element, List 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 rangeMarkers) { - final ArrayList markers = new ArrayList(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 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 markers, - CharSequence chars, + private void processIntersectingRange(UsageInfo2UsageAdapter usageInfo2UsageAdapter, + final CharSequence chars, int hiStart, - int hiEnd, + final int hiEnd, TextAttributesKey[] tokenHighlights, - List 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 result) { + final TextAttributes originalAttrs = convertAttributes(tokenHighlights); + final int[] lastOffset = {hiStart}; + usageInfo2UsageAdapter.processRangeMarkers(new Processor() { + @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); } } diff --git a/platform/usageView/src/com/intellij/usages/UsageDataUtil.java b/platform/usageView/src/com/intellij/usages/UsageDataUtil.java new file mode 100644 index 000000000000..134bee458bae --- /dev/null +++ b/platform/usageView/src/com/intellij/usages/UsageDataUtil.java @@ -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 result = new THashSet(); + + 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); + } +} diff --git a/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java b/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java index 4814f7569d62..ebb303d7a190 100644 --- a/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java +++ b/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java @@ -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, RenameableUsage, - TypeSafeDataProvider { - private static final Logger LOG = Logger.getInstance("#com.intellij.usages.UsageInfo2UsageAdapter"); - +public class UsageInfo2UsageAdapter implements UsageInModule, + UsageInLibrary, UsageInFile, PsiElementUsage, + MergeableUsage, Comparable, + RenameableUsage, TypeSafeDataProvider, UsagePresentation { public static final NotNullFunction CONVERTER = new NotNullFunction() { @NotNull public Usage fun(UsageInfo usageInfo) { @@ -61,40 +64,33 @@ public class UsageInfo2UsageAdapter implements UsageInModule, UsageInLibrary, Us }; private final UsageInfo myUsageInfo; + private final List myMergedUsageInfos = new SmartList(); // contains all merged infos, including myUsageInfo private int myLineNumber; private int myOffset = -1; protected ComputableIcon myIcon; private String myTooltipText; - private List myRangeMarkers = new ArrayList(); - private TextChunk[] myTextChunks; - private final UsagePresentation myUsagePresentation; + private Reference 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() { - 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(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(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 getRangeMarkers() { - return myRangeMarkers; + // must iterate in start offset order + public boolean processRangeMarkers(Processor 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 orders = fileIndex.getOrderEntriesForFile(psiFile.getVirtualFile()); + List 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() { + @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(); - 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 list = getSelectedInfoList(); + List list = getSelectedInfoList(); sink.put(UsageView.USAGE_INFO_LIST_KEY, list); } } - private ArrayList getSelectedInfoList() { - ArrayList list = new ArrayList(); - 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 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; } } diff --git a/platform/usageView/src/com/intellij/usages/impl/GroupNode.java b/platform/usageView/src/com/intellij/usages/impl/GroupNode.java index aa5a2bdb3328..cd256daa4a35 100644 --- a/platform/usageView/src/com/intellij/usages/impl/GroupNode.java +++ b/platform/usageView/src/com/intellij/usages/impl/GroupNode.java @@ -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 myUsages = new ConcurrentHashSet(); private final Map myUsageNodes = new ConcurrentHashMap(); 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 myUsagesToFlush = new ConcurrentLinkedQueue(); static final Comparator USAGE_COMPARATOR = new Comparator() { 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 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(new Processor() { + @Override + public boolean process(Usage usage) { + appendUsage(usage); + return true; + } + }, new Condition() { + @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 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 getUsages() { - return myUsages; + return myUsageNodes.keySet(); } @NotNull public List getSortedUsages() { - List usages = new ArrayList(myUsages); + List usages = new ArrayList(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 result = new ArrayList(); + final List result = new ArrayList(); 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 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 result = new THashSet(); - - 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); } - } diff --git a/platform/usageView/src/com/intellij/usages/impl/UsageViewManagerImpl.java b/platform/usageView/src/com/intellij/usages/impl/UsageViewManagerImpl.java index 1b04bb297082..d7c3db66c7ea 100644 --- a/platform/usageView/src/com/intellij/usages/impl/UsageViewManagerImpl.java +++ b/platform/usageView/src/com/intellij/usages/impl/UsageViewManagerImpl.java @@ -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 segments = new ArrayList(); + CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor(segments); + usageInfo.processRangeMarkers(processor); + rangeBlinker.resetMarkers(segments); rangeBlinker.startBlinking(); } diff --git a/platform/usageView/usageView.iml b/platform/usageView/usageView.iml index 8c425e159519..a29a428de6af 100644 --- a/platform/usageView/usageView.iml +++ b/platform/usageView/usageView.iml @@ -10,6 +10,7 @@ + diff --git a/platform/util/src/com/intellij/openapi/util/TextRange.java b/platform/util/src/com/intellij/openapi/util/TextRange.java index e0d555ba26af..a38fa5788b25 100644 --- a/platform/util/src/com/intellij/openapi/util/TextRange.java +++ b/platform/util/src/com/intellij/openapi/util/TextRange.java @@ -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; diff --git a/platform/util/src/com/intellij/util/containers/TransferToEDTQueue.java b/platform/util/src/com/intellij/util/containers/TransferToEDTQueue.java new file mode 100644 index 000000000000..8c51af670634 --- /dev/null +++ b/platform/util/src/com/intellij/util/containers/TransferToEDTQueue.java @@ -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 { + private final Processor myProcessor; + private volatile boolean stopped; + private final Condition myShutUpCondition; + + private final Queue myQueue = new ConcurrentLinkedQueue(); + 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 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(); + } +} diff --git a/xml/dom-openapi/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java b/xml/dom-openapi/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java index 84fa5318bb1b..e3270f208fdf 100644 --- a/xml/dom-openapi/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java +++ b/xml/dom-openapi/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java @@ -60,7 +60,7 @@ public class NavigationGutterIconBuilder { }; private final Icon myIcon; - private final NotNullFunction> myConvertor; + private final NotNullFunction> myConverter; private NotNullLazyValue> myTargets; private boolean myLazy; @@ -70,7 +70,7 @@ public class NavigationGutterIconBuilder { private String myTooltipTitle; private GutterIconRenderer.Alignment myAlignment = GutterIconRenderer.Alignment.CENTER; private PsiElementListCellRenderer myCellRenderer; - private NullableFunction myNamer = (NullableFunction)ElementPresentationManager.NAMER; + private NullableFunction myNamer = ElementPresentationManager.namer(); public static final NotNullFunction> DEFAULT_DOM_CONVERTOR = new NotNullFunction>() { @NotNull public Collection fun(final DomElement o) { @@ -78,17 +78,17 @@ public class NavigationGutterIconBuilder { } }; - protected NavigationGutterIconBuilder(@NotNull final Icon icon, @NotNull NotNullFunction> convertor) { + protected NavigationGutterIconBuilder(@NotNull final Icon icon, @NotNull NotNullFunction> converter) { myIcon = icon; - myConvertor = convertor; + myConverter = converter; } public static NavigationGutterIconBuilder create(@NotNull final Icon icon) { return create(icon, DEFAULT_PSI_CONVERTOR); } - public static NavigationGutterIconBuilder create(@NotNull final Icon icon, @NotNull NotNullFunction> convertor) { - return new NavigationGutterIconBuilder(icon, convertor); + public static NavigationGutterIconBuilder create(@NotNull final Icon icon, @NotNull NotNullFunction> converter) { + return new NavigationGutterIconBuilder(icon, converter); } public NavigationGutterIconBuilder setTarget(@Nullable T target) { @@ -193,9 +193,10 @@ public class NavigationGutterIconBuilder { @NotNull public List compute() { Set elements = new THashSet(); - final ArrayList list = new ArrayList(); - for (final T target : myTargets.getValue()) { - for (final PsiElement psiElement : myConvertor.fun(target)) { + Collection targets = myTargets.getValue(); + final List list = new ArrayList(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 { } }; - final boolean empty = !myLazy && pointers.getValue().isEmpty(); + final boolean empty = isEmpty(); if (myTooltipText == null && !myLazy) { final SortedSet names = new TreeSet(); @@ -233,6 +234,23 @@ public class NavigationGutterIconBuilder { return new MyNavigationGutterIconRenderer(this, myAlignment, myIcon, myTooltipText, pointers, empty); } + private boolean isEmpty() { + if (myLazy) { + return false; + } + + Set elements = new THashSet(); + Collection 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; diff --git a/xml/dom-openapi/src/com/intellij/util/xml/ElementPresentationManager.java b/xml/dom-openapi/src/com/intellij/util/xml/ElementPresentationManager.java index d3ee30f083a3..393abf90ccc7 100644 --- a/xml/dom-openapi/src/com/intellij/util/xml/ElementPresentationManager.java +++ b/xml/dom-openapi/src/com/intellij/util/xml/ElementPresentationManager.java @@ -132,6 +132,10 @@ public abstract class ElementPresentationManager { return getElementName(o); } }; + public static NullableFunction namer() { + //noinspection unchecked + return (NullableFunction)NAMER; + } @Nullable public static String getElementName(Object element) {