From a79bb806adfe4dcf65a70e5caac005637560ff7b Mon Sep 17 00:00:00 2001 From: Yuriy Artamonov Date: Wed, 26 May 2021 15:26:08 +0300 Subject: [PATCH] IDEA-270099 Spring: compute event targets in gutter lazily Support asynchronous mode for NavigationGutterIconRenderer popup calculation GitOrigin-RevId: 69dc72c71b9c3e6b80bc64ec70fc219c2e409d1f --- .../NavigationGutterIconBuilder.java | 28 +++++- .../NavigationGutterIconRenderer.java | 94 +++++++++++++++++-- 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java b/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java index 61fbcf0eec93..a73542e84674 100644 --- a/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java +++ b/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java @@ -53,7 +53,7 @@ public class NavigationGutterIconBuilder { private final NotNullFunction> myConverter; protected NotNullLazyValue> 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 { @NotNull protected NavigationGutterIconRenderer createGutterIconRenderer(@NotNull NotNullLazyValue>> pointers, - @NotNull Computable> renderer, - boolean empty) { + @NotNull Computable> renderer, + boolean empty) { return new MyNavigationGutterIconRenderer(this, myAlignment, myIcon, myTooltipText, pointers, renderer, empty); } + @NotNull + protected NavigationGutterIconRenderer createLazyGutterIconRenderer(@NotNull NotNullLazyValue>> pointers, + @NotNull Computable> renderer, + boolean empty) { + return new MyNavigationGutterIconRenderer(this, myAlignment, myIcon, myTooltipText, pointers, renderer, empty, true); + } + @NotNull private static NotNullLazyValue>> createPointersThunk(boolean lazy, final Project project, @@ -363,6 +370,21 @@ public class NavigationGutterIconBuilder { myEmpty = empty; } + MyNavigationGutterIconRenderer(@NotNull NavigationGutterIconBuilder builder, + @NotNull Alignment alignment, + Icon icon, + @Nullable @Tooltip String tooltipText, + @NotNull NotNullLazyValue>> pointers, + @NotNull Computable> 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; diff --git a/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconRenderer.java b/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconRenderer.java index 531e39fa4665..a993fd01f7fe 100644 --- a/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconRenderer.java +++ b/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconRenderer.java @@ -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 myCellRenderer; private final NotNullLazyValue>> 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> cellRenderer, @NotNull NotNullLazyValue>> pointers) { + this(popupTitle, emptyText, cellRenderer, pointers, true); + } + + protected NavigationGutterIconRenderer(@PopupTitle String popupTitle, + @PopupContent String emptyText, + Computable cellRenderer, + NotNullLazyValue>> 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 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> 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 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 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); + } + }; + } }