IDEA-270099 Spring: compute event targets in gutter lazily

Support asynchronous mode for NavigationGutterIconRenderer popup calculation

GitOrigin-RevId: 69dc72c71b9c3e6b80bc64ec70fc219c2e409d1f
This commit is contained in:
Yuriy Artamonov
2021-05-26 15:26:08 +03:00
committed by intellij-monorepo-bot
parent 557da2e0bb
commit a79bb806ad
2 changed files with 111 additions and 11 deletions

View File

@@ -53,7 +53,7 @@ public class NavigationGutterIconBuilder<T> {
private final NotNullFunction<? super T, ? extends Collection<? extends PsiElement>> myConverter;
protected NotNullLazyValue<Collection<? extends T>> myTargets;
private boolean myLazy;
protected boolean myLazy;
protected @Tooltip String myTooltipText;
protected @PopupTitle String myPopupTitle;
protected @PopupContent String myEmptyText;
@@ -288,11 +288,18 @@ public class NavigationGutterIconBuilder<T> {
@NotNull
protected NavigationGutterIconRenderer createGutterIconRenderer(@NotNull NotNullLazyValue<List<SmartPsiElementPointer<?>>> pointers,
@NotNull Computable<PsiElementListCellRenderer<?>> renderer,
boolean empty) {
@NotNull Computable<PsiElementListCellRenderer<?>> renderer,
boolean empty) {
return new MyNavigationGutterIconRenderer(this, myAlignment, myIcon, myTooltipText, pointers, renderer, empty);
}
@NotNull
protected NavigationGutterIconRenderer createLazyGutterIconRenderer(@NotNull NotNullLazyValue<List<SmartPsiElementPointer<?>>> pointers,
@NotNull Computable<PsiElementListCellRenderer<?>> renderer,
boolean empty) {
return new MyNavigationGutterIconRenderer(this, myAlignment, myIcon, myTooltipText, pointers, renderer, empty, true);
}
@NotNull
private static <T> NotNullLazyValue<List<SmartPsiElementPointer<?>>> createPointersThunk(boolean lazy,
final Project project,
@@ -363,6 +370,21 @@ public class NavigationGutterIconBuilder<T> {
myEmpty = empty;
}
MyNavigationGutterIconRenderer(@NotNull NavigationGutterIconBuilder<?> builder,
@NotNull Alignment alignment,
Icon icon,
@Nullable @Tooltip String tooltipText,
@NotNull NotNullLazyValue<List<SmartPsiElementPointer<?>>> pointers,
@NotNull Computable<PsiElementListCellRenderer<?>> cellRenderer,
boolean empty,
boolean computeTargetsInBackground) {
super(builder.myPopupTitle, builder.myEmptyText, cellRenderer, pointers, computeTargetsInBackground);
myAlignment = alignment;
myIcon = icon;
myTooltipText = tooltipText;
myEmpty = empty;
}
@Override
public boolean isNavigateAction() {
return !myEmpty;

View File

@@ -20,8 +20,11 @@ import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.ide.util.PsiElementListCellRenderer;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
@@ -32,23 +35,32 @@ import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.NlsContexts.PopupContent;
import com.intellij.openapi.util.NlsContexts.PopupTitle;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.AnimatedIcon;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.concurrency.EdtScheduledExecutorService;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static com.intellij.openapi.progress.util.ProgressIndicatorUtils.runInReadActionWithWriteActionPriority;
/**
* @author peter
@@ -59,15 +71,25 @@ public abstract class NavigationGutterIconRenderer extends GutterIconRenderer
private final @PopupContent String myEmptyText;
protected final Computable<? extends PsiElementListCellRenderer> myCellRenderer;
private final NotNullLazyValue<? extends List<SmartPsiElementPointer<?>>> myPointers;
private final boolean myComputeTargetsInBackground;
protected NavigationGutterIconRenderer(final @PopupTitle String popupTitle,
final @PopupContent String emptyText,
protected NavigationGutterIconRenderer(@PopupTitle String popupTitle,
@PopupContent String emptyText,
@NotNull Computable<? extends PsiElementListCellRenderer<?>> cellRenderer,
@NotNull NotNullLazyValue<? extends List<SmartPsiElementPointer<?>>> pointers) {
this(popupTitle, emptyText, cellRenderer, pointers, true);
}
protected NavigationGutterIconRenderer(@PopupTitle String popupTitle,
@PopupContent String emptyText,
Computable<? extends PsiElementListCellRenderer> cellRenderer,
NotNullLazyValue<? extends List<SmartPsiElementPointer<?>>> pointers,
boolean computeTargetsInBackground) {
myPopupTitle = popupTitle;
myEmptyText = emptyText;
myCellRenderer = cellRenderer;
myPointers = pointers;
myComputeTargetsInBackground = computeTargetsInBackground;
}
@Override
@@ -117,13 +139,43 @@ public abstract class NavigationGutterIconRenderer extends GutterIconRenderer
}
@Override
public void navigate(@Nullable final MouseEvent event, @Nullable final PsiElement elt) {
List<PsiElement> list = getTargetElements();
if (list.isEmpty()) {
public void navigate(@Nullable MouseEvent event, @Nullable PsiElement elt) {
if (event != null && myComputeTargetsInBackground && event.getComponent() != null) {
navigateTargetsAsync(event);
}
else {
navigateTargets(event, getTargetElements());
}
}
private void navigateTargetsAsync(@NotNull MouseEvent event) {
RelativePoint relativePoint = new RelativePoint(event);
Runnable removeIcon = addLoadingIcon(relativePoint);
AppExecutorUtil.getAppExecutorService().execute(() -> {
ProgressManager.getInstance().computePrioritized(() -> {
ProgressManager.getInstance().executeProcessUnderProgress(() -> {
Ref<List<PsiElement>> targets = Ref.create();
boolean success = runInReadActionWithWriteActionPriority(() -> targets.set(getTargetElements()));
ApplicationManager.getApplication().invokeLater(() -> {
removeIcon.run();
if (success) {
navigateTargets(event, targets.get());
}
});
}, new EmptyProgressIndicator());
return null;
});
});
}
private void navigateTargets(@Nullable MouseEvent event, List<PsiElement> targets) {
if (targets.isEmpty()) {
if (myEmptyText != null) {
if (event != null) {
final JComponent label = HintUtil.createErrorLabel(myEmptyText);
label.setBorder(JBUI.Borders.empty(2, 7, 2, 7));
JComponent label = HintUtil.createErrorLabel(myEmptyText);
label.setBorder(JBUI.Borders.empty(2, 7));
JBPopupFactory.getInstance().createBalloonBuilder(label)
.setFadeoutTime(3000)
.setFillColor(HintUtil.getErrorColor())
@@ -140,7 +192,7 @@ public abstract class NavigationGutterIconRenderer extends GutterIconRenderer
protected void navigateToItems(@Nullable MouseEvent event) {
List<Navigatable> navigatables = new ArrayList<>();
for (SmartPsiElementPointer<?> pointer : myPointers.getValue()) {
ContainerUtil.addIfNotNull(navigatables, getNavigatable(pointer));
ContainerUtil.addIfNotNull(navigatables, getNavigatable(pointer));
}
if (navigatables.size() == 1) {
navigatables.get(0).navigate(true);
@@ -173,4 +225,30 @@ public abstract class NavigationGutterIconRenderer extends GutterIconRenderer
final PsiElement navigationElement = element.getNavigationElement();
return navigationElement instanceof Navigatable ? (Navigatable)navigationElement : null;
}
private static @NotNull Runnable addLoadingIcon(@Nullable RelativePoint point) {
JRootPane rootPane = point == null ? null : UIUtil.getRootPane(point.getComponent());
JComponent glassPane = rootPane == null ? null : (JComponent)rootPane.getGlassPane();
if (glassPane == null) return () -> {};
JLabel icon = new JLabel(AnimatedIcon.Big.INSTANCE);
Dimension size = icon.getPreferredSize();
icon.setSize(size);
Point location = point.getPoint(glassPane);
location.x -= size.width / 2;
location.y -= size.height / 2;
icon.setLocation(location);
EdtScheduledExecutorService.getInstance().schedule(() -> {
if (!icon.isVisible()) return;
glassPane.add(icon);
}, 500, TimeUnit.MILLISECONDS);
return () -> {
if (icon.getParent() != null) {
glassPane.remove(icon);
}
else {
icon.setVisible(false);
}
};
}
}