[navigation] IJPL-206689 "Jump to source" ignores symbols present in code

GitOrigin-RevId: e2d4509041af391c1f6b153b56deec54d71734ce
This commit is contained in:
Piotr Tomiak
2025-09-24 16:28:46 +00:00
committed by intellij-monorepo-bot
parent 23edec04f2
commit 64cc996dff
6 changed files with 130 additions and 8 deletions
@@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.platform.backend.navigation.NavigationTarget;
import com.intellij.platform.backend.presentation.TargetPresentation;
import com.intellij.platform.backend.presentation.TargetPresentationBuilder;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.*;
@@ -42,6 +43,10 @@ public interface SymbolNavigationService {
@Contract("_ -> new")
@NotNull NavigationTarget psiElementNavigationTarget(@NotNull PsiElement element);
@ApiStatus.Internal
@ApiStatus.Experimental
@NotNull Navigatable getNavigatable(@NotNull Project project, @NotNull NavigationTarget target);
/**
* Please use {@link TargetPresentation#builder(String)}
*/
@@ -0,0 +1,69 @@
// 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.codeInsight.navigation.impl
import com.intellij.ide.DataManager
import com.intellij.ide.IdeBundle
import com.intellij.navigation.ItemPresentation
import com.intellij.navigation.NavigationItem
import com.intellij.openapi.application.readAction
import com.intellij.openapi.components.serviceAsync
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.platform.backend.navigation.NavigationRequest
import com.intellij.platform.backend.navigation.NavigationTarget
import com.intellij.platform.ide.navigation.NavigationOptions
import com.intellij.platform.ide.navigation.NavigationService
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import javax.swing.Icon
internal class NavigationTargetNavigatable(
private val project: Project,
navigationTarget: NavigationTarget,
) : NavigationItem, ItemPresentation {
private val targetPresentation = navigationTarget.computePresentation()
private val navigationTargetPointer = navigationTarget.createPointer()
private val navigationTarget: NavigationTarget?
get() = navigationTargetPointer.dereference()
override fun navigationRequest(): NavigationRequest? = navigationTarget?.navigationRequest()
override fun navigate(requestFocus: Boolean) {
val component = IdeFocusManager.getInstance(project).getFocusOwner()
val dataContext = component?.let { DataManager.getInstance().getDataContext(it) }
runWithModalProgressBlocking(project, IdeBundle.message("progress.title.preparing.navigation")) {
val request = readAction { navigationTarget?.navigationRequest() }
request?.let {
project.serviceAsync<NavigationService>().navigate(
it, NavigationOptions.defaultOptions().requestFocus(requestFocus), dataContext
)
}
}
}
override fun canNavigate(): Boolean = true
override fun canNavigateToSource(): Boolean = false
override fun getName(): String = targetPresentation.presentableText
override fun getPresentation(): ItemPresentation = this
override fun getPresentableText(): String = targetPresentation.presentableText
override fun getIcon(unused: Boolean): Icon? = targetPresentation.icon
override fun getLocationString(): String? {
val container = targetPresentation.containerText
val location = targetPresentation.locationText
return if (container != null || location != null) {
sequenceOf(container, location).joinToString(", ", "(", ")")
}
else null
}
override fun toString(): String =
"NavigationTargetNavigatable[$navigationTarget]"
}
@@ -10,6 +10,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ClassExtension
import com.intellij.platform.backend.navigation.NavigationTarget
import com.intellij.platform.backend.presentation.TargetPresentationBuilder
import com.intellij.pom.Navigatable
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.util.SmartList
@@ -50,4 +51,7 @@ class SymbolNavigationServiceImpl : SymbolNavigationService {
override fun presentationBuilder(presentableText: @Nls String): TargetPresentationBuilder {
return TargetPresentationBuilderImpl(presentableText = presentableText)
}
override fun getNavigatable(project: Project, target: NavigationTarget): Navigatable =
NavigationTargetNavigatable(project, target)
}
@@ -3,6 +3,8 @@
package com.intellij.ide.impl.dataRules;
import com.intellij.ide.util.EditSourceUtil;
import com.intellij.model.psi.PsiSymbolService;
import com.intellij.navigation.SymbolNavigationService;
import com.intellij.openapi.actionSystem.DataMap;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.pom.Navigatable;
@@ -10,8 +12,7 @@ import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.openapi.actionSystem.CommonDataKeys.NAVIGATABLE;
import static com.intellij.openapi.actionSystem.CommonDataKeys.PSI_ELEMENT;
import static com.intellij.openapi.actionSystem.CommonDataKeys.*;
import static com.intellij.openapi.actionSystem.PlatformCoreDataKeys.SELECTED_ITEM;
final class NavigatableRule {
@@ -22,6 +23,22 @@ final class NavigatableRule {
return o;
}
}
var symbols = dataProvider.get(SYMBOLS);
var project = dataProvider.get(PROJECT);
if (project != null && symbols != null && !symbols.isEmpty()
// For PsiElement symbols use logic below
&& (symbols.size() > 1 || PsiSymbolService.getInstance().extractElementFromSymbol(symbols.getFirst()) == null)
) {
var navigatables = symbols.stream()
.flatMap(symbol -> SymbolNavigationService.getInstance().getNavigationTargets(project, symbol).stream())
.map(target -> SymbolNavigationService.getInstance().getNavigatable(project, target))
.toList();
if (navigatables.size() == 1) {
return navigatables.getFirst();
}
}
PsiElement element = dataProvider.get(PSI_ELEMENT);
if (element instanceof Navigatable o) {
return o;
@@ -1,15 +1,34 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.impl.dataRules;
import com.intellij.model.Symbol;
import com.intellij.navigation.SymbolNavigationService;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataMap;
import com.intellij.openapi.project.Project;
import com.intellij.pom.Navigatable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
final class NavigatableArrayRule {
static Navigatable @Nullable [] getData(@NotNull DataMap dataProvider) {
Navigatable element = dataProvider.get(CommonDataKeys.NAVIGATABLE);
return element == null ? null : new Navigatable[]{element};
if (element != null) {
return new Navigatable[]{element};
}
List<Symbol> symbolList = dataProvider.get(CommonDataKeys.SYMBOLS);
Project project = dataProvider.get(CommonDataKeys.PROJECT);
if (symbolList != null && project != null) {
var navigatables = symbolList.stream()
.flatMap(symbol -> SymbolNavigationService.getInstance().getNavigationTargets(project, symbol).stream())
.map(target -> SymbolNavigationService.getInstance().getNavigatable(project, target))
.toArray(Navigatable[]::new);
if (navigatables.length > 0) {
return navigatables;
}
}
return null;
}
}
@@ -540,8 +540,16 @@ fun CodeInsightTestFixture.checkGTDUOutcome(expectedOutcome: GotoDeclarationOrUs
fun CodeInsightTestFixture.checkGotoDeclaration(fromSignature: String?, declarationSignature: String, expectedFileName: String? = null) {
checkGTDUOutcome(GotoDeclarationOrUsageHandler2.GTDUOutcome.GTD, fromSignature)
checkEditorNavigation("GotoDeclaration", fromSignature, declarationSignature, expectedFileName)
}
fun CodeInsightTestFixture.checkJumpToSource(fromSignature: String?, sourceSignature: String, expectedFileName: String? = null) {
checkEditorNavigation("EditSource", fromSignature, sourceSignature, expectedFileName)
}
private fun CodeInsightTestFixture.checkEditorNavigation(action: String, fromSignature: String?, targetSignature: String, expectedFileName: String?) {
val actualSignature = fromSignature ?: editor.currentPositionSignature
performEditorAction("GotoDeclaration")
performEditorAction(action)
val targetEditor = FileEditorManager.getInstance(project).selectedTextEditor?.topLevelEditor
if (targetEditor == null) throw NullPointerException(actualSignature)
val targetFile = PsiDocumentManager.getInstance(project).getPsiFile(targetEditor.document)!!
@@ -551,12 +559,12 @@ fun CodeInsightTestFixture.checkGotoDeclaration(fromSignature: String?, declarat
else {
assertEquals(actualSignature, targetEditor, editor.topLevelEditor)
}
if (!declarationSignature.contains("<caret>") || targetFile.findOffsetBySignature(
declarationSignature) != targetEditor.caretModel.offset) {
if (!targetSignature.contains("<caret>") || targetFile.findOffsetBySignature(
targetSignature) != targetEditor.caretModel.offset) {
assertEquals("For go to from: $actualSignature",
declarationSignature + if (!declarationSignature.contains("<caret>")) ""
targetSignature + if (!targetSignature.contains("<caret>")) ""
else (" [" + InjectedLanguageManager.getInstance(project).getTopLevelFile(file)
.findOffsetBySignature(declarationSignature) + "]"),
.findOffsetBySignature(targetSignature) + "]"),
targetEditor.currentPositionSignature + "[${targetEditor.caretModel.offset}]")
}
}