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 @@
+