[javadoc] Add navigation for fragment references

#IDEA-376337 Fixed

GitOrigin-RevId: e56328231c555d8cf4a75a93b107a1e284f15c73
This commit is contained in:
Louis Vignier
2025-09-17 19:25:18 +00:00
committed by intellij-monorepo-bot
parent f421f79436
commit bfe0d574ab
5 changed files with 89 additions and 3 deletions
@@ -1,10 +1,12 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator;
import com.intellij.codeInsight.javadoc.JavaSuperTypeSearchUtil;
import com.intellij.codeInsight.javadoc.SnippetMarkup;
import com.intellij.codeInsight.javadoc.index.JavaDocFragmentAnchorCacheKt;
import com.intellij.codeInsight.javadoc.index.JavaDocFragmentData;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.JavaTemplateUtil;
@@ -45,6 +47,7 @@ import com.intellij.psi.codeStyle.arrangement.MemberOrderService;
import com.intellij.psi.impl.compiled.ClsClassImpl;
import com.intellij.psi.impl.compiled.ClsElementImpl;
import com.intellij.psi.impl.source.codeStyle.ImportHelper;
import com.intellij.psi.impl.source.javadoc.PsiDocFragmentName;
import com.intellij.psi.impl.source.javadoc.PsiSnippetAttributeValueImpl;
import com.intellij.psi.javadoc.*;
import com.intellij.psi.search.GlobalSearchScope;
@@ -53,9 +56,11 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.IncorrectOperationException;
import kotlin.Pair;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.util.*;
import java.util.function.Function;
@@ -445,6 +450,37 @@ public class JavaPsiImplementationHelperImpl extends JavaPsiImplementationHelper
};
}
@Override
public @Nullable PsiSymbolReference getFragmentNameSymbol(@NotNull PsiDocFragmentName fragmentName) {
if (DumbService.getInstance(myProject).isDumb()) return null;
final Pair<PsiClass, JavaDocFragmentData> fragmentData = JavaDocFragmentAnchorCacheKt.getAnchor(myProject, fragmentName);
if (fragmentData == null) return null;
final int offset = fragmentData.getSecond().getOffset();
final PsiElement target = fragmentData.getFirst().getContainingFile().getViewProvider().findElementAt(offset);
if (!(target instanceof PsiDocToken)) return null;
return new PsiSymbolReference() {
@Override
public @NotNull PsiElement getElement() {
return fragmentName;
}
@Override
public @NotNull TextRange getRangeInElement() {
return new TextRange(0, fragmentName.getText().length());
}
@Override
public @NotNull @Unmodifiable Collection<? extends Symbol> resolveReference() {
return List.of(
new SnippetRegionSymbol(target.getContainingFile(), new TextRange(offset, offset + fragmentName.getText().length()))
);
}
};
}
public static final class SnippetRegionSymbol implements Symbol, DocumentationTarget, NavigationTarget {
private final @NotNull PsiFile myFile;
@@ -3,8 +3,11 @@ package com.intellij.codeInsight.javadoc.index
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.impl.source.javadoc.PsiDocFragmentName
import com.intellij.psi.javadoc.PsiDocComment
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.indexing.ID
@@ -64,4 +67,16 @@ private class JavaDocFragmentCacheService {
fun getAnchors(project: Project, fqnOrPackage: String): LinkedHashSet<JavaDocFragmentData> {
return project.service<JavaDocFragmentCacheService>().getAnchors(project, fqnOrPackage)
}
fun getAnchor(project: Project, fragmentName: PsiDocFragmentName): Pair<PsiClass, JavaDocFragmentData>? {
if (DumbService.getInstance(project).isDumb) return null
val psiClass = fragmentName.getScope()
val fqn = psiClass?.qualifiedName ?: return null
val data = getAnchors(project, fqn).firstOrNull { data: JavaDocFragmentData -> data.name == fragmentName.text }
?: return null
return psiClass to data
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.core;
import com.intellij.lang.ASTNode;
@@ -9,6 +9,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaPsiImplementationHelper;
import com.intellij.psi.impl.source.javadoc.PsiDocFragmentName;
import com.intellij.psi.javadoc.PsiDocToken;
import com.intellij.psi.javadoc.PsiSnippetAttributeValue;
import com.intellij.psi.util.PsiUtil;
@@ -85,6 +86,11 @@ public class CoreJavaPsiImplementationHelper extends JavaPsiImplementationHelper
throw new UnsupportedOperationException();
}
@Override
public @Nullable PsiSymbolReference getFragmentNameSymbol(@NotNull PsiDocFragmentName token) {
throw new UnsupportedOperationException();
}
public @NotNull Project getProject() {
return myProject;
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl;
import com.intellij.lang.ASTNode;
@@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.javadoc.PsiDocFragmentName;
import com.intellij.psi.javadoc.PsiDocToken;
import com.intellij.psi.javadoc.PsiSnippetAttributeValue;
import org.jetbrains.annotations.NotNull;
@@ -42,4 +43,9 @@ public abstract class JavaPsiImplementationHelper {
* This makes it possible to navigate to the inherited doc.
*/
public abstract @NotNull PsiSymbolReference getInheritDocSymbol(@NotNull PsiDocToken token);
/**
* Returns a PsiSymbolReference mapping a fragment reference ({@code ##fragment-id}) to its source ({@code <p id="fragment-id">…</p>}).
*/
public abstract @Nullable PsiSymbolReference getFragmentNameSymbol(@NotNull PsiDocFragmentName token);
}
@@ -1,12 +1,20 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.source.javadoc;
import com.intellij.model.psi.PsiSymbolReference;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.impl.JavaPsiImplementationHelper;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.javadoc.PsiDocTagValue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
public class PsiDocFragmentName extends CompositePsiElement implements PsiDocTagValue, Constants {
public PsiDocFragmentName() {
@@ -22,4 +30,19 @@ public class PsiDocFragmentName extends CompositePsiElement implements PsiDocTag
visitor.visitElement(this);
}
}
public @Nullable PsiClass getScope() {
final PsiElement parent = getParent();
return parent instanceof PsiDocFragmentRef ? ((PsiDocFragmentRef)parent).getScope() : null;
}
@Override
public @NotNull Collection<? extends @NotNull PsiSymbolReference> getOwnReferences() {
final PsiSymbolReference symbolReference = JavaPsiImplementationHelper
.getInstance(getProject())
.getFragmentNameSymbol(this);
if (symbolReference != null) return Collections.singleton(symbolReference);
return super.getOwnReferences();
}
}