diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/AbstractParameterInfoTestCase.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/AbstractParameterInfoTestCase.java index d60ac7440c6e..79ee1b4a4706 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/AbstractParameterInfoTestCase.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/AbstractParameterInfoTestCase.java @@ -11,7 +11,6 @@ import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementPresentation; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.openapi.actionSystem.IdeActions; -import com.intellij.openapi.application.impl.NonBlockingReadActionImpl; import com.intellij.openapi.editor.Editor; import com.intellij.testFramework.fixtures.EditorHintFixture; import com.intellij.util.ui.UIUtil; @@ -46,15 +45,7 @@ public abstract class AbstractParameterInfoTestCase extends LightFixtureCompleti protected void showParameterInfo() { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_SHOW_PARAMETER_INFO); - waitForParameterInfo(); - } - - public static void waitForParameterInfo() { - // effective there is a chain of 3 nonBlockingRead actions - for (int i = 0; i < 3; i++) { - UIUtil.dispatchAllInvocationEvents(); - NonBlockingReadActionImpl.waitForAsyncTaskCompletion(); - } + UIUtil.dispatchAllInvocationEvents(); } protected void checkHintContents(String hintText) { @@ -68,7 +59,6 @@ public abstract class AbstractParameterInfoTestCase extends LightFixtureCompleti public void complete(String partOfItemText) { LookupElement[] elements = myFixture.completeBasic(); selectItem(elements, partOfItemText); - waitForParameterInfo(); } public void completeSmart() { @@ -78,7 +68,6 @@ public abstract class AbstractParameterInfoTestCase extends LightFixtureCompleti public void completeSmart(String partOfItemText) { LookupElement[] lookupElements = myFixture.complete(CompletionType.SMART); selectItem(lookupElements, partOfItemText); - waitForParameterInfo(); } private void selectItem(LookupElement[] elements, String partOfItemText) { @@ -92,7 +81,6 @@ public abstract class AbstractParameterInfoTestCase extends LightFixtureCompleti private void waitForParameterInfoUpdate() throws TimeoutException { ParameterInfoController.waitForDelayedActions(getEditor(), 1, TimeUnit.MINUTES); - waitForParameterInfo(); } public static void waitTillAnimationCompletes(Editor editor) { @@ -118,6 +106,5 @@ public abstract class AbstractParameterInfoTestCase extends LightFixtureCompleti myFixture.doHighlighting(); waitTillAnimationCompletes(getEditor()); waitForAutoPopup(); - waitForParameterInfo(); } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/ParameterInfoTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/ParameterInfoTest.java index 6b61745683f3..744db79a12db 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/ParameterInfoTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/ParameterInfoTest.java @@ -83,7 +83,8 @@ public class ParameterInfoTest extends AbstractParameterInfoTestCase { " }\n" + "}\n"); - showParameterInfo(); + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_SHOW_PARAMETER_INFO); + UIUtil.dispatchAllInvocationEvents(); assertEquals("Supplier<Integer> extractKey, Function<String, Integer> right", hintFixture.getCurrentHintText()); } @@ -132,7 +133,8 @@ public class ParameterInfoTest extends AbstractParameterInfoTestCase { " super(\"a\", 1);\n" + " }\n" + " }"); - showParameterInfo(); + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_SHOW_PARAMETER_INFO); + UIUtil.dispatchAllInvocationEvents(); assertEquals("String s, int... p", hintFixture.getCurrentHintText()); } @@ -147,7 +149,8 @@ public class ParameterInfoTest extends AbstractParameterInfoTestCase { " String[] a = foo(args, args.length);\n" + " }\n" + "}"); - showParameterInfo(); + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_SHOW_PARAMETER_INFO); + UIUtil.dispatchAllInvocationEvents(); assertEquals("String[] args, int l", hintFixture.getCurrentHintText()); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java index 1b91aa5afccb..92e31cefd01b 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java @@ -1699,7 +1699,6 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase { } private void checkResultWithInlays(String text) { - waitForParameterInfo(); myFixture.checkResultWithInlays(text); } @@ -1725,12 +1724,10 @@ public class CompletionHintsTest extends AbstractParameterInfoTestCase { private void methodOverloadUp() { myFixture.performEditorAction(IdeActions.ACTION_METHOD_OVERLOAD_SWITCH_UP); - waitForParameterInfo(); } private void methodOverloadDown() { myFixture.performEditorAction(IdeActions.ACTION_METHOD_OVERLOAD_SWITCH_DOWN); - waitForParameterInfo(); } private void home() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/CancelProgressOnScrolling.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/CancelProgressOnScrolling.java index 8fe1a7013924..697640a21841 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/CancelProgressOnScrolling.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/CancelProgressOnScrolling.java @@ -3,26 +3,24 @@ package com.intellij.codeInsight.hint; import com.intellij.openapi.editor.event.VisibleAreaEvent; import com.intellij.openapi.editor.event.VisibleAreaListener; +import com.intellij.openapi.progress.ProgressIndicator; import org.jetbrains.annotations.NotNull; -import org.jetbrains.concurrency.CancellablePromise; import java.awt.*; -import java.util.concurrent.atomic.AtomicReference; class CancelProgressOnScrolling implements VisibleAreaListener { - private final AtomicReference> myCancellablePromiseRef; + private final ProgressIndicator myProgressIndicator; - CancelProgressOnScrolling(AtomicReference> cancellablePromiseRef) { - myCancellablePromiseRef = cancellablePromiseRef; + CancelProgressOnScrolling(ProgressIndicator indicator) { + myProgressIndicator = indicator; } @Override public void visibleAreaChanged(@NotNull VisibleAreaEvent e) { Rectangle oldRect = e.getOldRectangle(); Rectangle newRect = e.getNewRectangle(); - CancellablePromise promise = myCancellablePromiseRef.get(); - if (oldRect != null && (oldRect.x != newRect.x || oldRect.y != newRect.y) && promise != null) { - promise.cancel(); + if (oldRect != null && (oldRect.x != newRect.x || oldRect.y != newRect.y)) { + myProgressIndicator.cancel(); } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java index b76dfc176484..bf99a3a7e564 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java @@ -21,15 +21,18 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.event.*; import com.intellij.openapi.editor.ex.util.EditorUtil; -import com.intellij.openapi.progress.util.ProgressWindow; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.util.ProgressIndicatorUtils; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.LoadingDecorator; import com.intellij.openapi.ui.popup.Balloon.Position; import com.intellij.openapi.util.*; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -39,16 +42,12 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilBase; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.HintHint; -import com.intellij.ui.HintListener; import com.intellij.ui.LightweightHint; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBLoadingPanel; -import com.intellij.ui.components.panels.NonOpaquePanel; import com.intellij.util.Alarm; +import com.intellij.util.Consumer; +import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.text.CharArrayUtil; -import com.intellij.util.ui.AsyncProcessIcon; -import com.intellij.util.ui.EmptyIcon; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -60,25 +59,18 @@ import java.awt.*; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.LockSupport; -import java.util.function.Consumer; - -import static com.intellij.codeInsight.hint.ParameterInfoTaskRunnerUtil.runTask; public class ParameterInfoController extends UserDataHolderBase implements Disposable { private static final Logger LOG = Logger.getInstance(ParameterInfoController.class); private static final String WHITESPACE = " \t"; - - private static final String LOADING_TAG = "loading"; - private static final String COMPONENT_TAG = "component"; - private final Project myProject; @NotNull private final Editor myEditor; private final RangeMarker myLbraceMarker; - private final JBLoadingPanel myLoadingPanel; private LightweightHint myHint; private final ParameterInfoComponent myComponent; private boolean myKeepOnHintHidden; @@ -103,8 +95,7 @@ public class ParameterInfoController extends UserDataHolderBase implements Dispo for (int i = 0; i < allControllers.size(); ++i) { ParameterInfoController controller = allControllers.get(i); - int lbraceOffset = controller.myLbraceMarker.getStartOffset(); - if (lbraceOffset == offset) { + if (controller.myLbraceMarker.getStartOffset() == offset) { if (controller.myKeepOnHintHidden || controller.myHint.isVisible()) return controller; Disposer.dispose(controller); //noinspection AssignmentToForLoopParameter @@ -151,28 +142,6 @@ public class ParameterInfoController extends UserDataHolderBase implements Dispo myProvider = new MyBestLocationPointProvider(editor); myLbraceMarker = editor.getDocument().createRangeMarker(lbraceOffset, lbraceOffset); myComponent = new ParameterInfoComponent(descriptors, editor, handler, requestFocus, true); - myLoadingPanel = new JBLoadingPanel(null, panel -> new LoadingDecorator(panel, this, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS, false, new AsyncProcessIcon("ShowParameterInfo")){ - protected NonOpaquePanel customizeLoadingLayer(JPanel parent, JLabel text, AsyncProcessIcon icon) { - parent.setLayout(new FlowLayout(FlowLayout.LEFT)); - final NonOpaquePanel result = new NonOpaquePanel(); - result.add(icon); - parent.add(result); - return result; - } - - @Override - protected void _startLoading(boolean takeSnapshot) { - super._startLoading(takeSnapshot); - showHintLoading(true); - } - }) { - @Override - public String toString() { - return myComponent.toString(); - } - }; - myLoadingPanel.add(new JBLabel(EmptyIcon.ICON_18)); - myLoadingPanel.add(new JBLabel(CodeInsightBundle.message("parameter.info.progress.title"))); myHint = createHint(); myKeepOnHintHidden = !showHint; mySingleParameterInfo = !showHint; @@ -236,8 +205,7 @@ public class ParameterInfoController extends UserDataHolderBase implements Dispo private LightweightHint createHint() { JPanel wrapper = new WrapperPanel(); - wrapper.add(myLoadingPanel, LOADING_TAG); - wrapper.add(myComponent, COMPONENT_TAG); + wrapper.add(myComponent); return new LightweightHint(wrapper); } @@ -252,37 +220,9 @@ public class ParameterInfoController extends UserDataHolderBase implements Dispo myEditor.getCaretModel().removeCaretListener(myEditorCaretListener); } - public boolean showLoading(HintListener hintListener) { - if (myKeepOnHintHidden || myHint.isVisible()) { - myLoadingPanel.startLoading(); - myHint.addHintListener(hintListener); - - return true; - } - return false; - } - - public void hideLoading(HintListener hintListener) { - if (myKeepOnHintHidden || myHint.isVisible()) { - myLoadingPanel.stopLoading(); - showHintLoading(false); - } - myHint.removeHintListener(hintListener); - } - - private void showHintLoading(boolean showLoading) { - if (myKeepOnHintHidden || myHint.isVisible()) { - JComponent component = myHint.getComponent(); - CardLayout layout = (CardLayout)component.getLayout(); - layout.show(component, showLoading ? LOADING_TAG : COMPONENT_TAG); - myHint.pack(); - } - } - public void showHint(boolean requestFocus, boolean singleParameterInfo) { if (myHint.isVisible()) { - JComponent myHintComponent = myHint.getComponent(); - myHintComponent.removeAll(); + myHint.getComponent().remove(myComponent); hideHint(); myHint = createHint(); } @@ -425,18 +365,36 @@ public class ParameterInfoController extends UserDataHolderBase implements Dispo private void executeFindElementForUpdatingParameterInfo(UpdateParameterInfoContext context, @NotNull Consumer elementForUpdatingConsumer) { - runTask(myProject, + final Component focusOwner = IdeFocusManager.getInstance(myProject).getFocusOwner(); + ProgressManager.getInstance().run( + new Task.Backgroundable(myProject, CodeInsightBundle.message("parameter.info.progress.title"), true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + assert !ApplicationManager.getApplication().isDispatchThread() : + "Show parameter info on dispatcher thread leads to live lock"; + + final VisibleAreaListener visibleAreaListener = new CancelProgressOnScrolling(indicator); + + myEditor.getScrollingModel().addVisibleAreaListener(visibleAreaListener); + + ProgressIndicatorUtils.awaitWithCheckCanceled( ReadAction .nonBlocking(() -> { return myHandler.findElementForUpdatingParameterInfo(context); }).withDocumentsCommitted(myProject) + .cancelWith(indicator) .expireWhen(() -> getCurrentOffset() != context.getOffset()) - .coalesceBy(this) - .expireWith(this), - elementForUpdatingConsumer, - CodeInsightBundle.message("parameter.info.progress.title"), - myLbraceMarker.getStartOffset(), - myEditor); + .coalesceBy(ParameterInfoController.this) + .expireWith(ParameterInfoController.this) + .finishOnUiThread(ModalityState.defaultModalityState(), elementForUpdating -> { + if (Objects.equals(focusOwner, IdeFocusManager.getInstance(myProject).getFocusOwner())) { + elementForUpdatingConsumer.consume(elementForUpdating); + } + }) + .submit(AppExecutorUtil.getAppExecutorService()) + .onProcessed(ignore -> myEditor.getScrollingModel().removeVisibleAreaListener(visibleAreaListener))); + } + }); } private void executeUpdateParameterInfo(PsiElement elementForUpdating, @@ -448,32 +406,44 @@ public class ParameterInfoController extends UserDataHolderBase implements Dispo return; } - runTask(myProject, - ReadAction.nonBlocking(() -> { - try { - myHandler.updateParameterInfo(elementForUpdating, context); - return elementForUpdating; - } - catch (IndexNotReadyException e) { - DumbService.getInstance(myProject) - .showDumbModeNotification(CodeInsightBundle.message("parameter.info.indexing.mode.not.supported")); - } - return null; - }) - .withDocumentsCommitted(myProject) - .expireWhen(() -> !myKeepOnHintHidden && !myHint.isVisible() || - getCurrentOffset() != context.getOffset() || - !elementForUpdating.isValid()) - .expireWith(this), - element -> { - if (element != null && continuation != null) { + final Component focusOwner = IdeFocusManager.getInstance(myProject).getFocusOwner(); + ProgressManager.getInstance().run( + new Task.Backgroundable(myProject, CodeInsightBundle.message("parameter.info.progress.title"), true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + assert !ApplicationManager.getApplication().isDispatchThread() : + "Show parameter info on dispatcher thread leads to live lock"; + + final VisibleAreaListener visibleAreaListener = new CancelProgressOnScrolling(indicator); + + myEditor.getScrollingModel().addVisibleAreaListener(visibleAreaListener); + + ProgressIndicatorUtils.awaitWithCheckCanceled(ReadAction + .nonBlocking(() -> { + try { + myHandler.updateParameterInfo(elementForUpdating, context); + return elementForUpdating; + } + catch (IndexNotReadyException e) { + DumbService.getInstance(myProject) + .showDumbModeNotification(CodeInsightBundle.message("parameter.info.indexing.mode.not.supported")); + } + return null; + }) + .withDocumentsCommitted(myProject) + .cancelWith(indicator) + .expireWhen(() -> !myKeepOnHintHidden && !myHint.isVisible() && !ApplicationManager.getApplication().isHeadlessEnvironment() || getCurrentOffset() != context.getOffset() || !elementForUpdating.isValid()) + .expireWith(ParameterInfoController.this) + .finishOnUiThread(ModalityState.defaultModalityState(), element -> { + if (element != null && continuation != null && Objects.equals(focusOwner, IdeFocusManager.getInstance(myProject).getFocusOwner())) { context.applyUIChanges(); continuation.run(); } - }, - CodeInsightBundle.message("parameter.info.progress.title"), - myLbraceMarker.getStartOffset(), - myEditor); + }) + .submit(AppExecutorUtil.getAppExecutorService()) + .onProcessed(ignore -> myEditor.getScrollingModel().removeVisibleAreaListener(visibleAreaListener))); + } + }); } @HintManager.PositionFlags @@ -577,7 +547,7 @@ public class ParameterInfoController extends UserDataHolderBase implements Dispo } } - private static int getParameterIndex(@NotNull PsiElement[] parameters, @NotNull IElementType delimiter, int offset) { + private static int getParameterIndex(PsiElement @NotNull [] parameters, @NotNull IElementType delimiter, int offset) { for (int i = 0; i < parameters.length; i++) { PsiElement parameter = parameters[i]; TextRange textRange = parameter.getTextRange(); @@ -967,7 +937,7 @@ public class ParameterInfoController extends UserDataHolderBase implements Dispo private static class WrapperPanel extends JPanel { WrapperPanel() { - super(new CardLayout()); + super(new BorderLayout()); setBorder(JBUI.Borders.empty()); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoTaskRunnerUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoTaskRunnerUtil.java deleted file mode 100644 index ee3a01ce5083..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoTaskRunnerUtil.java +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.codeInsight.hint; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.NonBlockingReadAction; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.event.VisibleAreaListener; -import com.intellij.openapi.progress.util.ProgressWindow; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.LoadingDecorator; -import com.intellij.openapi.ui.popup.ComponentPopupBuilder; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.ui.HintListener; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBLoadingPanel; -import com.intellij.ui.components.panels.NonOpaquePanel; -import com.intellij.util.concurrency.AppExecutorUtil; -import com.intellij.util.concurrency.EdtScheduledExecutorService; -import com.intellij.util.ui.AsyncProcessIcon; -import com.intellij.util.ui.EmptyIcon; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.concurrency.CancellablePromise; - -import javax.swing.*; -import java.awt.*; -import java.util.EventObject; -import java.util.Objects; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; - -class ParameterInfoTaskRunnerUtil { - static void runTask(Project project, - NonBlockingReadAction nonBlockingReadAction, - Consumer continuationConsumer, - String progressTitle, - int offset, - Editor editor) { - AtomicReference> cancellablePromiseRef = new AtomicReference<>(); - Consumer stopAction = - startProgressAndCreateStopAction(editor.getProject(), progressTitle, cancellablePromiseRef, offset, editor); - - final VisibleAreaListener visibleAreaListener = new CancelProgressOnScrolling(cancellablePromiseRef); - - editor.getScrollingModel().addVisibleAreaListener(visibleAreaListener); - - final Component focusOwner = getFocusOwner(project); - - cancellablePromiseRef.set( - nonBlockingReadAction.finishOnUiThread( - ModalityState.defaultModalityState(), - continuation -> { - CancellablePromise promise = cancellablePromiseRef.get(); - if (promise != null && promise.isSucceeded() && Objects.equals(focusOwner, getFocusOwner(project))) { - continuationConsumer.accept(continuation); - } - }) - .submit(AppExecutorUtil.getAppExecutorService()) - .onProcessed(ignore -> { - stopAction.accept(false); - editor.getScrollingModel().removeVisibleAreaListener(visibleAreaListener); - })); - } - - private static Component getFocusOwner(Project project) { - return IdeFocusManager.getInstance(project).getFocusOwner(); - } - - @NotNull - private static Consumer startProgressAndCreateStopAction(Project project, - String progressTitle, - AtomicReference> promiseRef, - int offset, - Editor editor) { - offset = offset > 0 ? offset : editor.getCaretModel().getOffset() - 1; - ParameterInfoController controller = ParameterInfoController.findControllerAtOffset(editor, offset); - - AtomicReference> stopActionRef = new AtomicReference<>(); - - Consumer originalStopAction = (cancel) -> { - stopActionRef.set(null); - if (cancel) { - CancellablePromise promise = promiseRef.get(); - if (promise != null) { - promise.cancel(); - } - } - }; - - HintListener hintListener = new HintListener() { - @Override - public void hintHidden(@NotNull EventObject event) { - Consumer stopAction = stopActionRef.get(); - if (stopAction != null) { - stopAction.accept(true); - } - } - }; - - if (controller != null && controller.showLoading(hintListener)) { - stopActionRef.set((cancel) -> { - try { - controller.hideLoading(hintListener); - } finally { - originalStopAction.accept(cancel); - } - }); - } else { - final Disposable disposable = Disposer.newDisposable(); - Disposer.register(project, disposable); - - JBLoadingPanel loadingPanel = - new JBLoadingPanel(null, panel -> new LoadingDecorator(panel, disposable, 0, false, new AsyncProcessIcon("ShowParameterInfo")) { - @Override - protected NonOpaquePanel customizeLoadingLayer(JPanel parent, JLabel text, AsyncProcessIcon icon) { - parent.setLayout(new FlowLayout(FlowLayout.LEFT)); - final NonOpaquePanel result = new NonOpaquePanel(); - result.add(icon); - parent.add(result); - return result; - } - }); - loadingPanel.add(new JBLabel(EmptyIcon.ICON_18)); - loadingPanel.add(new JBLabel(progressTitle)); - - ComponentPopupBuilder builder = - JBPopupFactory.getInstance().createComponentPopupBuilder(loadingPanel, null) - .setProject(project) - .setCancelCallback(() -> { - Consumer stopAction = stopActionRef.get(); - if (stopAction != null) { - stopAction.accept(true); - } - return true; - }); - JBPopup popup = builder.createPopup(); - Disposer.register(disposable, popup); - ScheduledFuture showPopupFuture = EdtScheduledExecutorService.getInstance().schedule(() -> { - if (!popup.isDisposed() && !popup.isVisible()) { - RelativePoint popupPosition = JBPopupFactory.getInstance().guessBestPopupLocation(editor); - loadingPanel.startLoading(); - popup.show(popupPosition); - } - }, ModalityState.defaultModalityState(), ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS, TimeUnit.MILLISECONDS); - - stopActionRef.set((cancel) -> { - try { - loadingPanel.stopLoading(); - originalStopAction.accept(cancel); - } finally { - showPopupFuture.cancel(false); - UIUtil.invokeLaterIfNeeded(() -> { - if (popup.isVisible()) { - popup.setUiVisible(false); - } - Disposer.dispose(disposable); - }); - } - }); - } - - return stopActionRef.get(); - } -} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoHandler.java index 231db6823166..1159c8f3011c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoHandler.java @@ -11,26 +11,33 @@ import com.intellij.lang.Language; import com.intellij.lang.parameterInfo.LanguageParameterInfo; import com.intellij.lang.parameterInfo.ParameterInfoHandler; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.event.VisibleAreaListener; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.util.ProgressIndicatorUtils; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.LightweightHint; +import com.intellij.util.Consumer; import com.intellij.util.ObjectUtils; +import com.intellij.util.concurrency.AppExecutorUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.LinkedHashSet; +import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; - -import static com.intellij.codeInsight.hint.ParameterInfoTaskRunnerUtil.runTask; public class ShowParameterInfoHandler implements CodeInsightActionHandler { private static final ParameterInfoHandler[] EMPTY_HANDLERS = new ParameterInfoHandler[0]; @@ -75,6 +82,7 @@ public class ShowParameterInfoHandler implements CodeInsightActionHandler { boolean requestFocus, boolean singleParameterHint, String progressTitle, Consumer indexNotReadyExceptionConsumer) { + final Component focusOwner = IdeFocusManager.getInstance(project).getFocusOwner(); final DumbService dumbService = DumbService.getInstance(project); final int initialOffset = editor.getCaretModel().getOffset(); @@ -82,82 +90,98 @@ public class ShowParameterInfoHandler implements CodeInsightActionHandler { Lookup lookup = LookupManager.getInstance(project).getActiveLookup(); LookupElement lookupElement = lookup != null ? lookup.getCurrentItem() : null; - runTask(project, - ReadAction.nonBlocking(() -> { - final int offset = editor.getCaretModel().getOffset(); - final int fileLength = file.getTextLength(); - if (fileLength == 0) return null; + ProgressManager.getInstance().run( + new Task.Backgroundable(project, progressTitle, true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + assert !ApplicationManager.getApplication().isWriteAccessAllowed() : + "Show parameter info under write action could lead to live lock"; - // file.findElementAt(file.getTextLength()) returns null but we may need to show parameter info at EOF offset (for example in SQL) - final int offsetForLangDetection = offset > 0 && offset == fileLength ? offset - 1 : offset; - final Language language = PsiUtilCore.getLanguageAtOffset(file, offsetForLangDetection); + final VisibleAreaListener visibleAreaListener = new CancelProgressOnScrolling(indicator); - final ShowParameterInfoContext context = new ShowParameterInfoContext( - editor, - project, - file, - offset, - lbraceOffset, - requestFocus, - singleParameterHint - ); + editor.getScrollingModel().addVisibleAreaListener(visibleAreaListener); - context.setHighlightedElement(highlightedElement); - context.setRequestFocus(requestFocus); + ProgressIndicatorUtils.awaitWithCheckCanceled(ReadAction + .nonBlocking(() -> { + final int offset = editor.getCaretModel().getOffset(); + final int fileLength = file.getTextLength(); + if (fileLength == 0) return null; - final ParameterInfoHandler[] handlers = - ObjectUtils.notNull(getHandlers(project, language, file.getViewProvider().getBaseLanguage()), EMPTY_HANDLERS); + // file.findElementAt(file.getTextLength()) returns null but we may need to show parameter info at EOF offset (for example in SQL) + final int offsetForLangDetection = offset > 0 && offset == fileLength ? offset - 1 : offset; + final Language language = PsiUtilCore.getLanguageAtOffset(file, offsetForLangDetection); - if (lookup != null) { - if (lookupElement != null) { - for (ParameterInfoHandler handler : handlers) { - if (handler.couldShowInLookup()) { - final Object[] items = handler.getParametersForLookup(lookupElement, context); - if (items != null && items.length > 0) { - return (Runnable)() -> { - showLookupEditorHint(items, editor, handler, requestFocus); - }; - } - return null; + final ShowParameterInfoContext context = new ShowParameterInfoContext( + editor, + project, + file, + offset, + lbraceOffset, + requestFocus, + singleParameterHint + ); + + context.setHighlightedElement(highlightedElement); + context.setRequestFocus(requestFocus); + + final ParameterInfoHandler[] handlers = + ObjectUtils.notNull(getHandlers(project, language, file.getViewProvider().getBaseLanguage()), EMPTY_HANDLERS); + + if (lookup != null) { + if (lookupElement != null) { + for (ParameterInfoHandler handler : handlers) { + if (handler.couldShowInLookup()) { + final Object[] items = handler.getParametersForLookup(lookupElement, context); + if (items != null && items.length > 0) { + return (Runnable)() -> { + showLookupEditorHint(items, editor, handler, requestFocus); + }; } + return null; } } - return null; - } - - dumbService.setAlternativeResolveEnabled(true); - try { - for (int i = 0; i < handlers.length; i++) { - ParameterInfoHandler handler = handlers[i]; - PsiElement element = handler.findElementForParameterInfo(context); - if (element != null) { - return (Runnable)() -> { - if (element.isValid()) { - handler.showParameterInfo(element, context); - } - }; - } - } - } - catch (IndexNotReadyException e) { - indexNotReadyExceptionConsumer.accept(e); - } - finally { - dumbService.setAlternativeResolveEnabled(false); } return null; - }) - .withDocumentsCommitted(project) - .expireWhen(() -> editor.getCaretModel().getOffset() != initialOffset) - .coalesceBy(ShowParameterInfoHandler.class, editor), + } + + dumbService.setAlternativeResolveEnabled(true); + try { + for (int i = 0; i < handlers.length; i++) { + ParameterInfoHandler handler = handlers[i]; + PsiElement element = handler.findElementForParameterInfo(context); + if (element != null) { + return (Runnable)() -> { + if (element.isValid()) { + handler.showParameterInfo(element, context); + } + }; + } + } + } + catch (IndexNotReadyException e) { + indexNotReadyExceptionConsumer.consume(e); + } + finally { + dumbService.setAlternativeResolveEnabled(false); + } + return null; + }) + .withDocumentsCommitted(project) + .finishOnUiThread( + ModalityState.defaultModalityState(), continuation -> { - if (continuation != null) { + if (continuation != null && Objects.equals(focusOwner, IdeFocusManager.getInstance(project).getFocusOwner())) { continuation.run(); } - }, - progressTitle, - initialOffset, - editor); + }) + .cancelWith(indicator) + .expireWhen(() -> editor.getCaretModel().getOffset() != initialOffset) + .coalesceBy(ShowParameterInfoHandler.class, editor) + .submit(AppExecutorUtil.getAppExecutorService()) + .onProcessed(ignore -> editor.getScrollingModel().removeVisibleAreaListener(visibleAreaListener))); + } + } + ); } private static void showLookupEditorHint(Object[] descriptors, @@ -179,8 +203,7 @@ public class ShowParameterInfoHandler implements CodeInsightActionHandler { }); } - @Nullable - public static ParameterInfoHandler[] getHandlers(Project project, final Language... languages) { + public static ParameterInfoHandler @Nullable [] getHandlers(Project project, final Language... languages) { Set handlers = new LinkedHashSet<>(); DumbService dumbService = DumbService.getInstance(project); for (final Language language : languages) {