Delete async updating quick navigation info

The responsibility of putting links inside quick documentation info
moved to DocumentationProviders.

Related to GO-5598
This commit is contained in:
Alexander Zolotov
2018-05-07 17:42:47 +03:00
parent 1bafdfb329
commit 0edcf5a488
7 changed files with 98 additions and 154 deletions
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.completion.CompletionMemory;
import com.intellij.codeInsight.documentation.DocumentationManagerProtocol;
import com.intellij.codeInsight.documentation.PlatformDocumentationUtil;
import com.intellij.codeInsight.documentation.QuickDocUtil;
import com.intellij.codeInsight.editorActions.CodeDocumentationUtil;
import com.intellij.codeInsight.javadoc.JavaDocExternalFilter;
import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator;
@@ -65,6 +66,12 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
@Override
public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
return QuickDocUtil.inferLinkFromFullDocumentation(this, element, originalElement,
getQuickNavigationInfoInner(element, originalElement));
}
@Nullable
private static String getQuickNavigationInfoInner(PsiElement element, PsiElement originalElement) {
if (element instanceof PsiClass) {
return generateClassInfo((PsiClass)element);
}
@@ -1,3 +1 @@
[< java 1.7 >] java.util
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
[&lt; java 1.7 &gt;] java.util<br/> public class ArrayList&lt;E&gt; extends <a href="psi_element://java.util.AbstractList">AbstractList</a>&lt;E&gt;<br/> implements <a href="psi_element://java.util.List">List</a>&lt;E&gt;, <a href="psi_element://java.util.RandomAccess">RandomAccess</a>, <a href="psi_element://java.lang.Cloneable">Cloneable</a>, <a href="psi_element://java.io.Serializable">Serializable</a>
@@ -1,3 +1 @@
E
E A
Enum constant ordinal: 0
<a href="psi_element://E">E</a><br/> <a href="psi_element://E">E</a> A<br/>Enum constant ordinal: 0
@@ -11,6 +11,8 @@ import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import com.intellij.util.ui.UIUtil
/**
* @author peter
*/
@@ -83,7 +85,7 @@ class JavaDocumentationTest extends LightCodeInsightFixtureTestCase {
class Foo {{
new Bar<String>().f<caret>oo();
}}""",
"Bar\n List&lt;String&gt; foo(String param)")
"Bar<br/> <a href=\"psi_element://java.util.List\">List</a>&lt;String&gt; foo(String param)")
}
void testGenericField() {
@@ -93,7 +95,7 @@ class JavaDocumentationTest extends LightCodeInsightFixtureTestCase {
class Foo {{
new Bar<Integer>().fi<caret>eld
}}""",
"Bar\n Integer field")
"Bar<br/> Integer field")
}
void testMethodInAnonymousClass() {
@@ -123,7 +125,7 @@ class JavaDocumentationTest extends LightCodeInsightFixtureTestCase {
class Outer {
class Inner {}
}""",
"C\n Outer.Inner field")
"<a href=\"psi_element://C\">C</a><br/> <a href=\"psi_element://Outer.Inner\">Outer.Inner</a> field")
}
void testAsterisksFiltering() {
@@ -221,6 +223,6 @@ class Bar {
void doTestCtrlHoverDoc(String inputFile, String expectedDoc) {
configure inputFile.stripIndent()
String doc = CtrlMouseHandler.getInfo(myFixture.editor, CtrlMouseHandler.BrowseMode.Declaration)
assert doc == expectedDoc
assert UIUtil.getHtmlBody(doc) == expectedDoc
}
}
@@ -26,6 +26,7 @@ import com.intellij.rt.execution.junit.FileComparisonFailure;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.intellij.lang.annotations.Flow;
import java.io.File;
@@ -186,7 +187,7 @@ public class JavaDocInfoGeneratorTest extends CodeInsightTestCase {
docInfo = new JavaDocumentationProvider().getQuickNavigateInfo(field, field);
assertNotNull(docInfo);
assertEquals(exampleHtmlFileText(getTestName(true) + "_quick"), replaceEnvironmentDependentContent(docInfo));
assertEquals(exampleHtmlFileText(getTestName(true) + "_quick"), replaceEnvironmentDependentContent(UIUtil.getHtmlBody(docInfo)));
}
public void testClickableFieldReference() throws Exception {
@@ -218,7 +219,7 @@ public class JavaDocInfoGeneratorTest extends CodeInsightTestCase {
String docInfo = new JavaDocumentationProvider().getQuickNavigateInfo(superClass, referenceElement);
assertNotNull(docInfo);
assertFileTextEquals(docInfo);
assertFileTextEquals(UIUtil.getHtmlBody(docInfo));
}
void assertFileTextEquals(String docInfo) throws IOException {
@@ -1,22 +1,10 @@
/*
* Copyright 2000-2016 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.
*/
// Copyright 2000-2018 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.documentation;
import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.codeInsight.navigation.DocPreviewUtil;
import com.intellij.concurrency.SensitiveProgressWrapper;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
@@ -26,10 +14,11 @@ import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiQualifiedNamedElement;
import com.intellij.ui.content.Content;
import com.intellij.ui.popup.AbstractPopup;
import com.intellij.util.Producer;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -42,11 +31,6 @@ import static com.intellij.openapi.progress.util.ProgressIndicatorUtils.runInRea
*/
public class QuickDocUtil {
public static void updateQuickDocAsync(@NotNull final PsiElement element, @NotNull final Producer<String> docProducer) {
final Project project = element.getProject();
ApplicationManager.getApplication().executeOnPooledThread(() -> updateQuickDoc(project, element, docProducer.produce()));
}
public static void updateQuickDoc(@NotNull final Project project, @NotNull final PsiElement element, @Nullable final String documentation) {
if (StringUtil.isEmpty(documentation)) return;
// modal dialogs with fragment editors fix: can't guess proper modality state here
@@ -108,4 +92,16 @@ public class QuickDocUtil {
return result;
}
@Contract("_, _, _, null -> null")
public static String inferLinkFromFullDocumentation(@NotNull DocumentationProvider provider,
PsiElement element,
PsiElement originalElement,
@Nullable String navigationInfo) {
if (navigationInfo != null) {
String fqn = element instanceof PsiQualifiedNamedElement ? ((PsiQualifiedNamedElement)element).getQualifiedName() : null;
String fullText = provider.generateDoc(element, originalElement);
return HintUtil.prepareHintText(DocPreviewUtil.buildPreview(navigationInfo, fqn, fullText), HintUtil.getInformationHint());
}
return null;
}
}
@@ -1,6 +1,4 @@
/*
* Copyright 2000-2018 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.
*/
// Copyright 2000-2018 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.navigation;
@@ -8,7 +6,6 @@ import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.codeInsight.documentation.DocumentationManager;
import com.intellij.codeInsight.documentation.DocumentationManagerProtocol;
import com.intellij.codeInsight.documentation.QuickDocUtil;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.codeInsight.hint.HintUtil;
@@ -73,7 +70,6 @@ import com.intellij.ui.components.JBLayeredPane;
import com.intellij.usageView.UsageViewShortNameLocation;
import com.intellij.usageView.UsageViewTypeLocation;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
import com.intellij.util.ui.UIUtil;
import gnu.trove.TIntArrayList;
@@ -92,12 +88,9 @@ import java.util.Collections;
import java.util.EventObject;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class CtrlMouseHandler extends AbstractProjectComponent {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.navigation.CtrlMouseHandler");
private static final long DOC_GENERATION_TIMEOUT_MS = 5000;
private static final long DOC_GENERATION_RETRY_DELAY_MS = 100;
private static final AbstractDocumentationTooltipAction[] ourTooltipActions = {new ShowQuickDocAtPinnedWindowFromTooltipAction()};
private final EditorColorsManager myEditorColorsManager;
@@ -108,7 +101,6 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
private final DocumentationManager myDocumentationManager;
@Nullable private Point myPrevMouseLocation;
private LightweightHint myHint;
private final AtomicReference<ProgressIndicator> myCurrentRequestProgress = new AtomicReference<>();
public enum BrowseMode {None, Declaration, TypeDeclaration, Implementation}
@@ -213,8 +205,6 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
}
}
@NotNull private final Alarm myDocAlarm;
public CtrlMouseHandler(final Project project,
StartupManager startupManager,
EditorColorsManager colorsManager,
@@ -241,7 +231,6 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
});
myFileEditorManager = fileEditorManager;
myDocumentationManager = documentationManager;
myDocAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myProject);
}
@Override
@@ -621,106 +610,74 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
}
}
private void fulfillDocInfo(@NotNull final String header,
@NotNull final DocumentationProvider provider,
@NotNull final PsiElement originalElement,
@NotNull final PsiElement anchorElement,
@NotNull final Consumer<String> newTextConsumer,
@NotNull final LightweightHint hint,
@NotNull final Editor editor)
{
ProgressIndicatorBase progress = new ProgressIndicatorBase();
myCurrentRequestProgress.set(progress);
myDocAlarm.addRequest(() -> {
final Ref<String> fullTextRef = new Ref<>();
final Ref<String> qualifiedNameRef = new Ref<>();
QuickDocUtil.runInReadActionWithWriteActionPriorityWithRetries(() -> {
if (anchorElement.isValid() && originalElement.isValid()) {
try {
fullTextRef.set(provider.generateDoc(anchorElement, originalElement));
}
catch (IndexNotReadyException e) {
fullTextRef.set("Documentation is not available while indexing is in progress");
}
if (anchorElement instanceof PsiQualifiedNamedElement) {
qualifiedNameRef.set(((PsiQualifiedNamedElement)anchorElement).getQualifiedName());
}
}
}, DOC_GENERATION_TIMEOUT_MS, DOC_GENERATION_RETRY_DELAY_MS, progress);
myCurrentRequestProgress.compareAndSet(progress, null);
String fullText = fullTextRef.get();
if (fullText == null) {
private void updateText(@NotNull String updatedText,
@NotNull Consumer<String> newTextConsumer,
@NotNull LightweightHint hint,
@NotNull Editor editor) {
UIUtil.invokeLaterIfNeeded(() -> {
// There is a possible case that quick doc control width is changed, e.g. it contained text
// like 'public final class String implements java.io.Serializable, java.lang.Comparable<java.lang.String>' and
// new text replaces fully-qualified class names by hyperlinks with short name.
// That's why we might need to update the control size. We assume that the hint component is located at the
// layered pane, so, the algorithm is to find an ancestor layered pane and apply new size for the target component.
JComponent component = hint.getComponent();
Dimension oldSize = component.getPreferredSize();
newTextConsumer.consume(updatedText);
final int widthIncrease;
if (component instanceof QuickDocInfoPane) {
int buttonWidth = ((QuickDocInfoPane)component).getButtonWidth();
widthIncrease = calculateWidthIncrease(buttonWidth, updatedText);
}
else {
widthIncrease = 0;
}
if (oldSize == null) {
return;
}
final String updatedText = DocPreviewUtil.buildPreview(header, qualifiedNameRef.get(), fullText);
final String newHtml = HintUtil.prepareHintText(updatedText, HintUtil.getInformationHint());
UIUtil.invokeLaterIfNeeded(() -> {
// There is a possible case that quick doc control width is changed, e.g. it contained text
// like 'public final class String implements java.io.Serializable, java.lang.Comparable<java.lang.String>' and
// new text replaces fully-qualified class names by hyperlinks with short name.
// That's why we might need to update the control size. We assume that the hint component is located at the
// layered pane, so, the algorithm is to find an ancestor layered pane and apply new size for the target component.
Dimension newSize = component.getPreferredSize();
if (newSize.width + widthIncrease == oldSize.width) {
return;
}
component.setPreferredSize(new Dimension(newSize.width + widthIncrease, newSize.height));
JComponent component = hint.getComponent();
Dimension oldSize = component.getPreferredSize();
newTextConsumer.consume(newHtml);
// We're assuming here that there are two possible hint representation modes: popup and layered pane.
if (hint.isRealPopup()) {
final int widthIncrease;
if (component instanceof QuickDocInfoPane) {
int buttonWidth = ((QuickDocInfoPane)component).getButtonWidth();
widthIncrease = calculateWidthIncrease(buttonWidth, updatedText);
TooltipProvider tooltipProvider = myTooltipProvider;
if (tooltipProvider != null) {
// There is a possible case that 'raw' control was rather wide but the 'rich' one is narrower. That's why we try to
// re-show the hint here. Benefits: there is a possible case that we'll be able to show nice layered pane-based balloon;
// the popup will be re-positioned according to the new width.
hint.hide();
tooltipProvider.showHint(new LightweightHint(component), editor);
}
else {
widthIncrease = 0;
component.setPreferredSize(new Dimension(newSize.width + widthIncrease, oldSize.height));
hint.pack();
}
return;
}
if (oldSize == null) {
return;
Container topLevelLayeredPaneChild = null;
boolean adjustBounds = false;
for (Container current = component.getParent(); current != null; current = current.getParent()) {
if (current instanceof JLayeredPane) {
adjustBounds = true;
break;
}
Dimension newSize = component.getPreferredSize();
if (newSize.width + widthIncrease == oldSize.width) {
return;
else {
topLevelLayeredPaneChild = current;
}
component.setPreferredSize(new Dimension(newSize.width + widthIncrease, newSize.height));
}
// We're assuming here that there are two possible hint representation modes: popup and layered pane.
if (hint.isRealPopup()) {
TooltipProvider tooltipProvider = myTooltipProvider;
if (tooltipProvider != null) {
// There is a possible case that 'raw' control was rather wide but the 'rich' one is narrower. That's why we try to
// re-show the hint here. Benefits: there is a possible case that we'll be able to show nice layered pane-based balloon;
// the popup will be re-positioned according to the new width.
hint.hide();
tooltipProvider.showHint(new LightweightHint(component), editor);
}
else {
component.setPreferredSize(new Dimension(newSize.width + widthIncrease, oldSize.height));
hint.pack();
}
return;
}
Container topLevelLayeredPaneChild = null;
boolean adjustBounds = false;
for (Container current = component.getParent(); current != null; current = current.getParent()) {
if (current instanceof JLayeredPane) {
adjustBounds = true;
break;
}
else {
topLevelLayeredPaneChild = current;
}
}
if (adjustBounds && topLevelLayeredPaneChild != null) {
Rectangle bounds = topLevelLayeredPaneChild.getBounds();
topLevelLayeredPaneChild.setBounds(bounds.x, bounds.y, bounds.width + newSize.width + widthIncrease - oldSize.width, bounds.height);
}
});
}, 0);
if (adjustBounds && topLevelLayeredPaneChild != null) {
Rectangle bounds = topLevelLayeredPaneChild.getBounds();
topLevelLayeredPaneChild.setBounds(bounds.x, bounds.y, bounds.width + newSize.width + widthIncrease - oldSize.width, bounds.height);
}
});
}
/**
@@ -915,6 +872,10 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
QuickDocInfoPane quickDocPane = null;
if (docInfo.documentationAnchor != null) {
quickDocPane = new QuickDocInfoPane(docInfo.documentationAnchor, info.myElementAtPointer, label);
int buttonWidth = quickDocPane.getButtonWidth();
Dimension size = quickDocPane.getPreferredSize();
int widthIncrease = calculateWidthIncrease(buttonWidth, docInfo.text);
quickDocPane. setPreferredSize(new Dimension(size.width + widthIncrease, size.height));
quickDocPaneRef.set(quickDocPane);
}
@@ -928,12 +889,6 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
myHint = null;
}
});
myDocAlarm.cancelAllRequests();
ProgressIndicator currentIndicator = myCurrentRequestProgress.getAndSet(null);
if (currentIndicator != null) currentIndicator.cancel();
if (newTextConsumer != null && docInfo.docProvider != null && docInfo.documentationAnchor != null) {
fulfillDocInfo(docInfo.text, docInfo.docProvider, info.myElementAtPointer, docInfo.documentationAnchor, newTextConsumer, hint, editor);
}
showHint(hint, editor);
if (newTextConsumer != null) {
@@ -977,7 +932,7 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
return new Continuation(() -> {
updating.set(false);
if (newDocInfo.text != null && !oldText.equals(newDocInfo.text)) {
updateText(newDocInfo, textConsumer, info, hint, editor);
updateText(newDocInfo.text, textConsumer, hint, editor);
}
});
}
@@ -991,19 +946,6 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
});
}
private void updateText(@NotNull DocInfo docInfo,
@NotNull Consumer<String> textConsumer,
@NotNull Info info,
@NotNull LightweightHint hint,
@NotNull Editor editor) {
if (docInfo.text == null) return;
textConsumer.consume(docInfo.text);
if (docInfo.docProvider != null && docInfo.documentationAnchor != null) {
fulfillDocInfo(docInfo.text, docInfo.docProvider, info.myElementAtPointer,
docInfo.documentationAnchor, textConsumer, hint, editor);
}
}
public void showHint(@NotNull LightweightHint hint, @NotNull Editor editor) {
if (editor.isDisposed()) return;
final HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();