diff --git a/platform/lang-impl/src/com/intellij/codeInsight/navigation/DocPreviewUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/navigation/DocPreviewUtil.java index 77994a63d9ae..70c485b6b1cf 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/navigation/DocPreviewUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/navigation/DocPreviewUtil.java @@ -19,6 +19,7 @@ import com.intellij.codeInsight.documentation.DocumentationManager; import com.intellij.lang.documentation.DocumentationProvider; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; +import gnu.trove.TIntHashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,6 +37,23 @@ import java.util.regex.Pattern; */ public class DocPreviewUtil { + private static final TIntHashSet ALLOWED_LINK_SEPARATORS = new TIntHashSet(); + static { + ALLOWED_LINK_SEPARATORS.add(','); + ALLOWED_LINK_SEPARATORS.add(' '); + ALLOWED_LINK_SEPARATORS.add('.'); + ALLOWED_LINK_SEPARATORS.add(';'); + ALLOWED_LINK_SEPARATORS.add('&'); + ALLOWED_LINK_SEPARATORS.add('\t'); + ALLOWED_LINK_SEPARATORS.add('\n'); + ALLOWED_LINK_SEPARATORS.add('['); + ALLOWED_LINK_SEPARATORS.add(']'); + ALLOWED_LINK_SEPARATORS.add('('); + ALLOWED_LINK_SEPARATORS.add(')'); + ALLOWED_LINK_SEPARATORS.add('<'); + ALLOWED_LINK_SEPARATORS.add('>'); + } + /** * We shorten links text from fully qualified name to short names (e.g. from 'java.lang.String' to 'String'). * There is a possible situation then that we have two replacements where one key is a simple name and another one is a fully qualified @@ -90,13 +108,16 @@ public class DocPreviewUtil { Map links = new HashMap(); process(fullText, new LinksCollector(links)); - // Add short names. + // Add derived names. Map toAdd = new HashMap(); for (Map.Entry entry : links.entrySet()) { - String key = entry.getKey(); - int i = key.lastIndexOf('.'); - if (i > 0 && i < key.length() - 1) { - toAdd.put(key.substring(i + 1), entry.getValue()); + String shortName = parseShortName(entry.getKey()); + if (shortName != null) { + toAdd.put(shortName, entry.getValue()); + } + String longName = parseLongName(entry.getKey(), entry.getValue()); + if (longName != null) { + toAdd.put(longName, entry.getValue()); } } links.putAll(toAdd); @@ -121,6 +142,40 @@ public class DocPreviewUtil { return buffer.toString(); } + /** + * Tries to build a short name form the given name assuming that it is a full name. + *

+ * Example: return {@code 'String'} for a given {@code 'java.lang.String'}. + * + * @param name name to process + * @return short name derived from the given full name if possible; null otherwise + */ + @Nullable + private static String parseShortName(@NotNull String name) { + int i = name.lastIndexOf('.'); + return i > 0 && i < name.length() - 1 ? name.substring(i + 1) : null; + } + + /** + * Tries to build a long name from the given short name and a link. + *

+ * Example: return {@code 'java.lang.String'} for a given pair (name {@code 'String'}; address: {@code 'psi_element://java.lang.String'}. + * + * @param shortName short name to process + * @param address address to process + * @return long name derived from the given arguments (if any); null otherwise + */ + @Nullable + private static String parseLongName(@NotNull String shortName, @NotNull String address) { + String pureAddress = address; + int i = pureAddress.lastIndexOf("//"); + if (i > 0 && i < pureAddress.length() - 2) { + pureAddress = pureAddress.substring(i + 2); + } + + return (pureAddress.equals(shortName) || !pureAddress.endsWith(shortName)) ? null : pureAddress; + } + private static void replace(@NotNull StringBuilder text, @NotNull String replaceFrom, @NotNull String replaceTo, @@ -131,6 +186,11 @@ public class DocPreviewUtil { if (intersects(readOnlyChanges, i, end)) { continue; } + if (end - i > 1 && end < text.length() && !ALLOWED_LINK_SEPARATORS.contains(text.charAt(end))) { + // Consider a situation when we have, say, replacement from text 'PsiType' and encounter a 'PsiTypeParameter' in the text. + // We don't want to perform the replacement then. + continue; + } text.replace(i, end, replaceTo); int diff = replaceTo.length() - replaceFrom.length(); for (int j = 0; j < readOnlyChanges.size(); j++) { diff --git a/platform/lang-impl/testSources/com/intellij/codeInsight/navigation/DocPreviewUtilTest.groovy b/platform/lang-impl/testSources/com/intellij/codeInsight/navigation/DocPreviewUtilTest.groovy index a7799d0b61a0..31b235dcd1ca 100644 --- a/platform/lang-impl/testSources/com/intellij/codeInsight/navigation/DocPreviewUtilTest.groovy +++ b/platform/lang-impl/testSources/com/intellij/codeInsight/navigation/DocPreviewUtilTest.groovy @@ -145,4 +145,23 @@ Qname''' assertEquals(expected, DocPreviewUtil.buildPreview(header, null, fullText)) } + + @Test + void substrings() { + def header = '''\ +PsiResolveHelperImpl +public static com.intellij.openapi.util.Pair<com.intellij.psi.PsiType, com.intellij.psi.ConstraintType> getSubstitutionForTypeParameterConstraint (com.intellij.psi.PsiTypeParameter typeParam, com.intellij.psi.PsiType param, com.intellij.psi.PsiType arg, boolean isContraVariantPosition, com.intellij.pom.java.LanguageLevel languageLevel)''' + + def fullText = '''\ + com.intellij.psi.impl.source.resolve.PsiResolveHelperImpl

@Nullable public static Pair<PsiType, ConstraintType> getSubstitutionForTypeParameterConstraint(PsiTypeParameter typeParam,
+                                                                                  PsiType param,
+                                                                                  PsiType arg,
+                                                                                  boolean isContraVariantPosition,
+                                                                                  LanguageLevel languageLevel)
''' + + def expected = '''\ +PsiResolveHelperImpl
public static Pair<PsiType, ConstraintType> getSubstitutionForTypeParameterConstraint (PsiTypeParameter typeParam, PsiType param, PsiType arg, boolean isContraVariantPosition, LanguageLevel languageLevel)''' + + assertEquals(expected, DocPreviewUtil.buildPreview(header, null, fullText)) + } }