[command-completion] IDEA-373902 new action: FileStructurePopup

GitOrigin-RevId: d33f62c80ea847f720a317d7a81d3cf0adb88947
This commit is contained in:
Mikhail Pyltsin
2025-09-03 17:59:20 +00:00
committed by intellij-monorepo-bot
parent 0029d5ce36
commit a2b6337be8
9 changed files with 86 additions and 11 deletions
@@ -1504,6 +1504,7 @@
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaGenerateConstructorCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaSurroundWithCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaSafeDeleteCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.command.commands.ViewStructureCompletionCommandProvider" order="last"/>
<weigher implementationClass="com.intellij.codeInsight.completion.LoggerWeigher" key="completion" id="logger"/>
@@ -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
@@ -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
- <init>():V
*:com.intellij.codeInsight.daemon.impl.focusMode.FocusModeProvider
- a:calcFocusZones(com.intellij.psi.PsiFile):java.util.List
*:com.intellij.codeInsight.editorActions.TypingActionsExtension
@@ -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)
@@ -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() {
@@ -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)
}
}
}
}
@@ -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<AbstractTreeNode<?>> myCallbackAfterNavigation;
public ViewStructureAction() {
setEnabledInModalContext(true);
myCallbackAfterNavigation = null;
}
@ApiStatus.Internal
public ViewStructureAction(@Nullable Consumer<AbstractTreeNode<?>> 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<AbstractTreeNode<?>> 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;
}
@@ -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<AbstractTreeNode<?>> 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<AbstractTreeNode<?>> 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);
}
@@ -46,6 +46,7 @@
<codeInsight.completion.command.provider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands.KotlinQuickDocumentationCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands.KotlinSurroundWithCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands.KotlinSafeDeleteCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="kotlin" implementationClass="com.intellij.codeInsight.completion.command.commands.ViewStructureCompletionCommandProvider" order="last"/>
</extensions>
</idea-plugin>