diff --git a/platform/core-api/src/com/intellij/navigation/SymbolNavigationService.java b/platform/core-api/src/com/intellij/navigation/SymbolNavigationService.java index ea2e08fcc4d6..9521476c01ea 100644 --- a/platform/core-api/src/com/intellij/navigation/SymbolNavigationService.java +++ b/platform/core-api/src/com/intellij/navigation/SymbolNavigationService.java @@ -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)} */ diff --git a/platform/lang-impl/src/com/intellij/codeInsight/navigation/impl/NavigationTargetNavigatable.kt b/platform/lang-impl/src/com/intellij/codeInsight/navigation/impl/NavigationTargetNavigatable.kt new file mode 100644 index 000000000000..0a3af24ae1c6 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/navigation/impl/NavigationTargetNavigatable.kt @@ -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().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]" + +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/navigation/impl/SymbolNavigationServiceImpl.kt b/platform/lang-impl/src/com/intellij/codeInsight/navigation/impl/SymbolNavigationServiceImpl.kt index c4f68458c1ca..16b4080372fd 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/navigation/impl/SymbolNavigationServiceImpl.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/navigation/impl/SymbolNavigationServiceImpl.kt @@ -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) } diff --git a/platform/lang-impl/src/com/intellij/ide/impl/dataRules/NavigatableRule.java b/platform/lang-impl/src/com/intellij/ide/impl/dataRules/NavigatableRule.java index 803976229622..c2463ce3e401 100644 --- a/platform/lang-impl/src/com/intellij/ide/impl/dataRules/NavigatableRule.java +++ b/platform/lang-impl/src/com/intellij/ide/impl/dataRules/NavigatableRule.java @@ -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; diff --git a/platform/platform-impl/src/com/intellij/ide/impl/dataRules/NavigatableArrayRule.java b/platform/platform-impl/src/com/intellij/ide/impl/dataRules/NavigatableArrayRule.java index 1ed12f1105e5..6ee3246897f4 100644 --- a/platform/platform-impl/src/com/intellij/ide/impl/dataRules/NavigatableArrayRule.java +++ b/platform/platform-impl/src/com/intellij/ide/impl/dataRules/NavigatableArrayRule.java @@ -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 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; } } diff --git a/platform/polySymbols/testFramework/com/intellij/polySymbols/testFramework/WebTestUtil.kt b/platform/polySymbols/testFramework/com/intellij/polySymbols/testFramework/WebTestUtil.kt index a972c21ca7c2..31ad99fb04a7 100644 --- a/platform/polySymbols/testFramework/com/intellij/polySymbols/testFramework/WebTestUtil.kt +++ b/platform/polySymbols/testFramework/com/intellij/polySymbols/testFramework/WebTestUtil.kt @@ -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("") || targetFile.findOffsetBySignature( - declarationSignature) != targetEditor.caretModel.offset) { + if (!targetSignature.contains("") || targetFile.findOffsetBySignature( + targetSignature) != targetEditor.caretModel.offset) { assertEquals("For go to from: $actualSignature", - declarationSignature + if (!declarationSignature.contains("")) "" + targetSignature + if (!targetSignature.contains("")) "" else (" [" + InjectedLanguageManager.getInstance(project).getTopLevelFile(file) - .findOffsetBySignature(declarationSignature) + "]"), + .findOffsetBySignature(targetSignature) + "]"), targetEditor.currentPositionSignature + "[${targetEditor.caretModel.offset}]") } }