diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index 2d0266db0d53..f4b8d7748a6c 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -1504,6 +1504,7 @@ + diff --git a/platform/lang-api/resources/messages/CodeInsightBundle.properties b/platform/lang-api/resources/messages/CodeInsightBundle.properties index d4082f32cf5c..9e87ef8080d2 100644 --- a/platform/lang-api/resources/messages/CodeInsightBundle.properties +++ b/platform/lang-api/resources/messages/CodeInsightBundle.properties @@ -584,6 +584,7 @@ command.completion.psi.element.uncomment.block.text=Uncomment command.completion.copy.reference.description=Copy reference for ''{0}'' command.completion.copy.reference.text=Copy reference command.completion.comment.line.with.line.comment.text=Comment line with line comment +command.completion.view.structure.text=Go to members command.completion.GotoDeclaration.text=Go to declaration command.completion.surround.with.text=Surround with ''{0}'' options.command.completion.display.name=Command Completion diff --git a/platform/lang-impl/api-dump-experimental.txt b/platform/lang-impl/api-dump-experimental.txt index f4082316366a..e96d01087ceb 100644 --- a/platform/lang-impl/api-dump-experimental.txt +++ b/platform/lang-impl/api-dump-experimental.txt @@ -388,6 +388,9 @@ c:com.intellij.codeInsight.actions.VcsFacade - *sf:Companion:com.intellij.codeInsight.completion.command.commands.IntentionCommandSkipper$Companion - skip(com.intellij.codeInsight.intention.CommonIntentionAction,com.intellij.psi.PsiFile,I):Z *f:com.intellij.codeInsight.completion.command.commands.IntentionCommandSkipper$Companion +*f:com.intellij.codeInsight.completion.command.commands.ViewStructureCompletionCommandProvider +- com.intellij.codeInsight.completion.command.commands.ActionCommandProvider +- ():V *:com.intellij.codeInsight.daemon.impl.focusMode.FocusModeProvider - a:calcFocusZones(com.intellij.psi.PsiFile):java.util.List *:com.intellij.codeInsight.editorActions.TypingActionsExtension diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandInsertHandler.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandInsertHandler.kt index 164d624faa01..a228cf5f1dbd 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandInsertHandler.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandInsertHandler.kt @@ -78,7 +78,7 @@ internal class CommandInsertHandler(private val completionCommand: CompletionCom startOffset) // Remove the command text after the dots - var commandStart = startOffset - actualIndex + var commandStart = 0.coerceAtLeast(startOffset - actualIndex) // Delete from commandStart to tailOffset document.deleteString(commandStart, tailOffset) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractActionCompletionCommand.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractActionCompletionCommand.kt index 1b43312f3856..154d87d4a8e7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractActionCompletionCommand.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractActionCompletionCommand.kt @@ -20,6 +20,7 @@ import com.intellij.psi.PsiFile import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.util.SlowOperations import org.intellij.lang.annotations.Language +import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls import java.util.Locale.getDefault import javax.swing.Icon @@ -166,7 +167,8 @@ open class ActionCompletionCommand( if (it.isLowerCase()) it.titlecase(getDefault()) else it.toString() } - private val action: AnAction? = ActionManager.getInstance().getAction(actionId) + @ApiStatus.Internal + protected open val action: AnAction? = ActionManager.getInstance().getAction(actionId) override val additionalInfo: String? get() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/ViewStructureCompletionCommandProvider.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/ViewStructureCompletionCommandProvider.kt new file mode 100644 index 000000000000..d31ecd100f1c --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/ViewStructureCompletionCommandProvider.kt @@ -0,0 +1,36 @@ +// 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.completion.command.commands + +import com.intellij.codeInsight.CodeInsightBundle +import com.intellij.codeInsight.completion.command.CommandCompletionProviderContext +import com.intellij.ide.actions.ViewStructureAction +import com.intellij.ide.structureView.impl.common.PsiTreeElementBase +import com.intellij.ide.util.treeView.AbstractTreeNode +import com.intellij.idea.ActionsBundle +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiNameIdentifierOwner +import com.intellij.psi.util.PsiTreeUtil + +class ViewStructureCompletionCommandProvider : ActionCommandProvider(actionId = "FileStructurePopup", synonyms = listOf("File Structure", "Go to members", "Show structure"), presentableName = CodeInsightBundle.message("command.completion.view.structure.text"), priority = -150, previewText = ActionsBundle.message("action.FileStructurePopup.description")) { + + override fun createCommand(context: CommandCompletionProviderContext): ActionCompletionCommand { + return object : ActionCompletionCommand(actionId = super.actionId, presentableActionName = super.presentableName, icon = super.icon, priority = super.priority, previewText = super.previewText, synonyms = super.synonyms) { + override val action: AnAction + get() = ViewStructureAction { node: AbstractTreeNode<*> -> + val psiTreeElementBase = node.getValue() as? PsiTreeElementBase<*> ?: return@ViewStructureAction + val psiElement = psiTreeElementBase.element ?: return@ViewStructureAction + if (!psiElement.isPhysical) return@ViewStructureAction + if (!psiElement.isValid) return@ViewStructureAction + if (psiElement !is PsiNameIdentifierOwner) return@ViewStructureAction + val endOffset = psiElement.nameIdentifier?.textRange?.endOffset ?: return@ViewStructureAction + val project = context.project + val selectedTextEditor = FileEditorManager.getInstance(project).selectedTextEditor ?: return@ViewStructureAction + val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(selectedTextEditor.document) ?: return@ViewStructureAction + if (!PsiTreeUtil.isAncestor(psiFile, psiElement, false)) return@ViewStructureAction + selectedTextEditor.caretModel.moveToOffset(endOffset) + } + } + } +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/ide/actions/ViewStructureAction.java b/platform/lang-impl/src/com/intellij/ide/actions/ViewStructureAction.java index d8735e6747ba..015692f7d6df 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/ViewStructureAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/ViewStructureAction.java @@ -2,15 +2,16 @@ package com.intellij.ide.actions; -import com.intellij.ide.structureView.logical.PhysicalAndLogicalStructureViewBuilder; import com.intellij.ide.structureView.StructureView; import com.intellij.ide.structureView.StructureViewBuilder; import com.intellij.ide.structureView.StructureViewModel; import com.intellij.ide.structureView.TreeBasedStructureViewBuilder; import com.intellij.ide.structureView.impl.StructureViewComposite; +import com.intellij.ide.structureView.logical.PhysicalAndLogicalStructureViewBuilder; import com.intellij.ide.util.FileStructurePopup; import com.intellij.ide.util.FileStructurePopupListener; import com.intellij.ide.util.StructureViewCompositeModel; +import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.ide.util.treeView.smartTree.TreeStructureUtil; import com.intellij.openapi.actionSystem.ActionUpdateThread; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -29,16 +30,27 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.ui.EditorTextField; import com.intellij.ui.PlaceHolder; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.Objects; +import java.util.function.Consumer; public final class ViewStructureAction extends DumbAwareAction { + @Nullable + private final Consumer> myCallbackAfterNavigation; + public ViewStructureAction() { setEnabledInModalContext(true); + myCallbackAfterNavigation = null; + } + + @ApiStatus.Internal + public ViewStructureAction(@Nullable Consumer> callbackAfterNavigation) { + myCallbackAfterNavigation = callbackAfterNavigation; } @Override @@ -55,7 +67,7 @@ public final class ViewStructureAction extends DumbAwareAction { PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); } - FileStructurePopup popup = createPopup(project, fileEditor); + FileStructurePopup popup = createPopup(project, fileEditor, myCallbackAfterNavigation); if (popup == null) return; String title = virtualFile == null ? fileEditor.getName() : virtualFile.getName(); @@ -64,6 +76,13 @@ public final class ViewStructureAction extends DumbAwareAction { } public static @Nullable FileStructurePopup createPopup(@NotNull Project project, @NotNull FileEditor fileEditor) { + return createPopup(project, fileEditor, null); + } + + @ApiStatus.Internal + public static @Nullable FileStructurePopup createPopup(@NotNull Project project, + @NotNull FileEditor fileEditor, + @Nullable Consumer> callbackAfterNavigation) { PsiDocumentManager.getInstance(project).commitAllDocuments(); StructureViewBuilder builder = fileEditor.getStructureViewBuilder(); if (builder == null) return null; @@ -83,10 +102,9 @@ public final class ViewStructureAction extends DumbAwareAction { treeModel = createStructureViewModel(project, fileEditor, structureView); } if (treeModel instanceof PlaceHolder) { - //noinspection unchecked ((PlaceHolder)treeModel).setPlace(TreeStructureUtil.PLACE); } - FileStructurePopup popup = new FileStructurePopup(project, fileEditor, treeModel); + FileStructurePopup popup = new FileStructurePopup(project, fileEditor, treeModel, callbackAfterNavigation); if (structureView != null) Disposer.register(popup, structureView); return popup; } diff --git a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java index 75858bdd860a..72f3d871f5b8 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java +++ b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java @@ -73,10 +73,7 @@ import com.intellij.util.ui.*; import com.intellij.util.ui.tree.TreeUtil; import com.intellij.xml.util.XmlStringUtil; import kotlin.Unit; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; +import org.jetbrains.annotations.*; import org.jetbrains.concurrency.AsyncPromise; import org.jetbrains.concurrency.Promise; import org.jetbrains.concurrency.Promises; @@ -88,10 +85,11 @@ import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.event.*; -import java.util.List; import java.util.*; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.BiPredicate; +import java.util.function.Consumer; /** * @author Konstantin Bulenkov @@ -131,12 +129,24 @@ public final class FileStructurePopup implements Disposable, TreeActionsOwner { private boolean myCanClose = true; private boolean myDisposed; + @Nullable + private final Consumer> myCallbackAfterNavigation; + public FileStructurePopup(@NotNull Project project, @NotNull FileEditor fileEditor, @NotNull StructureViewModel treeModel) { + this(project, fileEditor, treeModel, null); + } + + @ApiStatus.Internal + public FileStructurePopup(@NotNull Project project, + @NotNull FileEditor fileEditor, + @NotNull StructureViewModel treeModel, + @Nullable Consumer> callbackAfterNavigation) { myProject = project; myFileEditor = fileEditor; myTreeModel = treeModel; + myCallbackAfterNavigation = callbackAfterNavigation; //Stop code analyzer to speed up the EDT DaemonCodeAnalyzer.getInstance(myProject).disableUpdateByTimer(this); @@ -697,6 +707,9 @@ public final class FileStructurePopup implements Disposable, TreeActionsOwner { if (selectedNode != null) { if (selectedNode.canNavigateToSource()) { selectedNode.navigate(true); + if (myCallbackAfterNavigation != null) { + myCallbackAfterNavigation.accept(selectedNode); + } myPopup.cancel(); succeeded.set(true); } diff --git a/plugins/kotlin/plugin/k2/resources/META-INF/command-completion-k2.xml b/plugins/kotlin/plugin/k2/resources/META-INF/command-completion-k2.xml index 695c2bf2c680..9f8aace180ac 100644 --- a/plugins/kotlin/plugin/k2/resources/META-INF/command-completion-k2.xml +++ b/plugins/kotlin/plugin/k2/resources/META-INF/command-completion-k2.xml @@ -46,6 +46,7 @@ +