From 8f9d4cb5b0b5ddf713ca5d58a1d89b5b04940939 Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Sun, 12 Oct 2025 15:17:19 +0200 Subject: [PATCH] [java, completion] IJPL-207762 ParenthInsertHandler is frontend compatible GitOrigin-RevId: 4533706e69b021e76114bb5790d9ba314552e0b8 --- .../resource/intellij.java.frontback.impl.xml | 1 + .../completion/JavaFrontendCompletionUtil.kt | 182 ++++++++++++++++++ ...rontendFriendlyParenthesesInsertHandler.kt | 32 +++ .../codeInsight/ExpectedTypesProvider.java | 6 +- .../completion/ConstructorInsertHandler.java | 2 +- .../completion/JavaCompletionContributor.java | 2 +- .../completion/JavaCompletionUtil.java | 137 +------------ .../completion/JavaMethodCallInsertHandler.kt | 25 ++- .../intellij.platform.completion.common.xml | 5 + .../src/AutoPopupControllerHelperImpl.kt | 36 ++++ .../common/src/AutoPopupControllerRpc.kt | 28 +++ .../common/src/LookupElementWithRpcId.kt | 16 ++ .../completion/common/src/package-info.java | 5 + .../codeInsight/AutoPopupControllerHelper.kt | 27 +++ 14 files changed, 363 insertions(+), 141 deletions(-) create mode 100644 java/java-frontback-impl/src/com/intellij/codeInsight/completion/JavaFrontendCompletionUtil.kt create mode 100644 java/java-frontback-impl/src/com/intellij/codeInsight/completion/method/FrontendFriendlyParenthesesInsertHandler.kt create mode 100644 platform/completion/common/src/AutoPopupControllerHelperImpl.kt create mode 100644 platform/completion/common/src/AutoPopupControllerRpc.kt create mode 100644 platform/completion/common/src/LookupElementWithRpcId.kt create mode 100644 platform/completion/common/src/package-info.java create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/AutoPopupControllerHelper.kt diff --git a/java/java-frontback-impl/resource/intellij.java.frontback.impl.xml b/java/java-frontback-impl/resource/intellij.java.frontback.impl.xml index b5a581e1a928..3fa16e8cd284 100644 --- a/java/java-frontback-impl/resource/intellij.java.frontback.impl.xml +++ b/java/java-frontback-impl/resource/intellij.java.frontback.impl.xml @@ -81,6 +81,7 @@ + diff --git a/java/java-frontback-impl/src/com/intellij/codeInsight/completion/JavaFrontendCompletionUtil.kt b/java/java-frontback-impl/src/com/intellij/codeInsight/completion/JavaFrontendCompletionUtil.kt new file mode 100644 index 000000000000..4f21265440b5 --- /dev/null +++ b/java/java-frontback-impl/src/com/intellij/codeInsight/completion/JavaFrontendCompletionUtil.kt @@ -0,0 +1,182 @@ +// 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 + +import com.intellij.codeInsight.AutoPopupController +import com.intellij.codeInsight.AutoPopupControllerHelper +import com.intellij.codeInsight.CodeInsightSettings +import com.intellij.codeInsight.TailType +import com.intellij.codeInsight.TailTypes +import com.intellij.codeInsight.completion.util.CompletionStyleUtil +import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler +import com.intellij.codeInsight.editorActions.TabOutScopesTracker +import com.intellij.codeInsight.lookup.EqTailType +import com.intellij.codeInsight.lookup.Lookup +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupItem +import com.intellij.openapi.editor.ex.EditorSettingsExternalizable +import com.intellij.patterns.PlatformPatterns +import com.intellij.psi.* +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.PsiUtilCore +import com.intellij.util.PlatformUtils +import com.intellij.util.ThreeState + +object JavaFrontendCompletionUtil { + /** + * If used in a [FrontendFriendlyInsertHandler], make sure that the original backend item does not have [LookupItem.getTailType]. + * This tail type is going to be lost if used in the Remote Development scenario. + */ + @JvmStatic + fun insertParentheses( + context: InsertionContext, + item: LookupElement, + overloadsMatter: Boolean, + hasParams: ThreeState, // UNSURE if providing no arguments is a valid situation + forceClosingParenthesis: Boolean, + ) { + var hasParams = hasParams + val editor = context.editor + val completionChar = context.completionChar + val file = context.file + + val tailType = when (completionChar) { + '(' -> TailTypes.noneType() + ':' -> TailTypes.conditionalExpressionColonType() + else -> LookupItem.handleCompletionChar(context.editor, item, completionChar) + } + + val hasTail = tailType !== TailTypes.noneType() && tailType !== TailTypes.unknownType() + val smart = completionChar == Lookup.COMPLETE_STATEMENT_SELECT_CHAR + + if (completionChar == '(' || completionChar == '.' || completionChar == ',' || completionChar == ';' || completionChar == ':' || completionChar == ' ') { + context.setAddCompletionChar(false) + } + + if (hasTail) { + hasParams = ThreeState.NO + } + + val needRightParenth = forceClosingParenthesis || + !smart && (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET || hasParams == ThreeState.NO && completionChar != '(') + + context.commitDocument() + + val styleSettings = CompletionStyleUtil.getCodeStyleSettings(context) + val elementAt = file.findElementAt(context.startOffset) + if (elementAt == null || elementAt.parent !is PsiMethodReferenceExpression) { + val hasParameters = hasParams + val spaceBetweenParentheses = hasParams == ThreeState.YES && styleSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES || + hasParams == ThreeState.UNSURE && styleSettings.SPACE_WITHIN_EMPTY_METHOD_CALL_PARENTHESES + + object : ParenthesesInsertHandler( + /* spaceBeforeParentheses = */ styleSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES, + /* spaceBetweenParentheses = */ spaceBetweenParentheses, + /* mayInsertRightParenthesis = */ needRightParenth, + /* allowParametersOnNextLine = */ styleSettings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE + ) { + override fun placeCaretInsideParentheses(context: InsertionContext?, item: LookupElement): Boolean { + return hasParameters != ThreeState.NO + } + + override fun findExistingLeftParenthesis(context: InsertionContext): PsiElement? { + val token = super.findExistingLeftParenthesis(context) + return if (isPartOfLambda(token)) null else token + } + + fun isPartOfLambda(token: PsiElement?): Boolean { + return token != null && + token.parent is PsiExpressionList && + PsiUtilCore.getElementType(PsiTreeUtil.nextVisibleLeaf(token.parent)) === JavaTokenType.ARROW + } + }.handleInsert(context, item) + } + + if (hasParams != ThreeState.NO) { + // Invoke parameters popup + AutoPopupControllerHelper.getInstance(file.project).autoPopupParameterInfoAfterCompletion( + editor = editor, + selectedItem = if (overloadsMatter) null else item + ) + } + + if (smart || !needRightParenth || !EditorSettingsExternalizable.getInstance().isInsertParenthesesAutomatically) { + return + } + + if (!insertTail(context, item, tailType, hasTail)) { + return + } + + when (completionChar) { + '.' -> { + AutoPopupController.getInstance(file.project).scheduleAutoPopup(context.editor) + } + ',' -> { + AutoPopupController.getInstance(file.project).autoPopupParameterInfo(context.editor, null) + } + } + } + + private fun insertTail( + context: InsertionContext, + item: LookupElement, + tailType: TailType, + hasTail: Boolean, + ): Boolean { + var toInsert = tailType + if (toInsert === EqTailType.INSTANCE) { + toInsert = TailTypes.unknownType() + } + + val lookupItem = item.`as`(LookupItem.CLASS_CONDITION_KEY) + if (lookupItem == null || lookupItem.getAttribute(LookupItem.TAIL_TYPE_ATTR) !== TailTypes.unknownType()) { + if (!hasTail && item.getObject() is PsiMethod && PsiTypes.voidType() == (item.getObject() as PsiMethod).returnType) { + PsiDocumentManager.getInstance(context.project).commitAllDocuments() + if (PlatformPatterns.psiElement().beforeLeaf(PlatformPatterns.psiElement().withText(".")).accepts(context.file.findElementAt(context.tailOffset - 1))) { + return false + } + + var insertAdditionalSemicolon = true + val leaf = context.file.findElementAt(context.startOffset) + val composite = leaf?.parent + if (composite is PsiReferenceExpression) { + var parent = composite.parent + if (parent is PsiMethodCallExpression) { + parent = parent.parent + } + + if (parent is PsiLambdaExpression && !insertSemicolonAfter(parent)) { + insertAdditionalSemicolon = false + } + if (parent is PsiExpressionStatement && (parent.parent as? PsiForStatement)?.update === parent) { + insertAdditionalSemicolon = false + } + } + if (insertAdditionalSemicolon) { + toInsert = TailTypes.semicolonType() + } + } + } + val editor = context.editor + val tailOffset = context.tailOffset + val afterTailOffset = toInsert.processTail(editor, tailOffset) + val caretOffset = editor.caretModel.offset + + // todo IJPL-207762 what's this??? shall it run on frontend or backend??? + if (tailOffset in (caretOffset + 1).. 0) { + TabOutScopesTracker.getInstance().registerEmptyScope(editor, caretOffset, afterTailOffset) + } + + return true + } + + @JvmStatic + fun insertSemicolonAfter(lambdaExpression: PsiLambdaExpression): Boolean { + return lambdaExpression.body is PsiCodeBlock || insertSemicolon(lambdaExpression.parent) + } + + @JvmStatic + fun insertSemicolon(parent: PsiElement?): Boolean { + return parent !is PsiExpressionList && parent !is PsiExpression + } +} \ No newline at end of file diff --git a/java/java-frontback-impl/src/com/intellij/codeInsight/completion/method/FrontendFriendlyParenthesesInsertHandler.kt b/java/java-frontback-impl/src/com/intellij/codeInsight/completion/method/FrontendFriendlyParenthesesInsertHandler.kt new file mode 100644 index 000000000000..4661823c2671 --- /dev/null +++ b/java/java-frontback-impl/src/com/intellij/codeInsight/completion/method/FrontendFriendlyParenthesesInsertHandler.kt @@ -0,0 +1,32 @@ +// 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.method + +import com.intellij.codeInsight.completion.FrontendFriendlyInsertHandler +import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.completion.JavaFrontendCompletionUtil +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.util.ThreeState +import kotlinx.serialization.Serializable + +@Suppress("NonDefaultConstructor") +@Serializable +class FrontendFriendlyParenthesesInsertHandler( + private val hasParameters: Boolean +) : FrontendFriendlyInsertHandler { + override fun handleInsert(context: InsertionContext, item: LookupElement) { + // todo FrontendFriendlyParenthInsertHandler differs from ParenthInsertHandler + // in that it does not check lookup elements for overloads with `MethodParenthesesHandler.overloadsHaveParameters` + // not sure if this can be reliably implemented on frontend + insertParenthesesForJavaMethod(item, context, ThreeState.fromBoolean(hasParameters)) + } + + companion object { + fun insertParenthesesForJavaMethod( + item: LookupElement, + context: InsertionContext, + hasParams: ThreeState, + ) { + JavaFrontendCompletionUtil.insertParentheses(context, item, false, hasParams, false) + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java index dbb511db00f4..f3b7e37ae652 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java @@ -423,8 +423,8 @@ public final class ExpectedTypesProvider { final PsiType functionalInterfaceType = lambdaExpression.getFunctionalInterfaceType(); final PsiMethod scopeMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); if (scopeMethod != null) { - visitMethodReturnType(scopeMethod, LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType), - JavaCompletionUtil.insertSemicolonAfter(lambdaExpression)); + visitMethodReturnType(scopeMethod, LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType), + JavaFrontendCompletionUtil.insertSemicolonAfter(lambdaExpression)); } } @@ -438,7 +438,7 @@ public final class ExpectedTypesProvider { final PsiType functionalInterfaceType = ((PsiLambdaExpression)psiElement).getFunctionalInterfaceType(); method = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); type = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType); - tailTypeSemicolon = JavaCompletionUtil.insertSemicolonAfter((PsiLambdaExpression)psiElement); + tailTypeSemicolon = JavaFrontendCompletionUtil.insertSemicolonAfter((PsiLambdaExpression)psiElement); } else if (psiElement instanceof PsiMethod) { method = (PsiMethod)psiElement; diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java b/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java index dd6e78117cab..fb3f68cb8b0d 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java @@ -244,7 +244,7 @@ public final class ConstructorInsertHandler implements InsertHandler processLabelReference(PsiLabelReference reference) { diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java index 76006ddc3e66..8412a18d29c5 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java @@ -1,12 +1,14 @@ // 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; -import com.intellij.codeInsight.*; +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.CodeInsightUtilCore; +import com.intellij.codeInsight.ExpectedTypeInfo; +import com.intellij.codeInsight.JavaProjectCodeInsightSettings; import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.codeInsight.completion.scope.CompletionElement; import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor; import com.intellij.codeInsight.completion.util.CompletionStyleUtil; -import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler; import com.intellij.codeInsight.editorActions.TabOutScopesTracker; import com.intellij.codeInsight.guess.GuessManager; import com.intellij.codeInsight.lookup.*; @@ -15,9 +17,7 @@ import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.module.JdkApiCompatibilityService; import com.intellij.openapi.project.DumbService; @@ -65,7 +65,6 @@ import java.util.function.Consumer; import java.util.function.Function; import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch; -import static com.intellij.patterns.PlatformPatterns.psiElement; import static com.intellij.psi.util.proximity.ReferenceListWeigher.ReferenceListApplicability.inapplicable; public final class JavaCompletionUtil { @@ -466,14 +465,6 @@ public final class JavaCompletionUtil { return type instanceof PsiClassType ? ((PsiClassType)type).rawType() : type; } - public static boolean insertSemicolonAfter(@NotNull PsiLambdaExpression lambdaExpression) { - return lambdaExpression.getBody() instanceof PsiCodeBlock || insertSemicolon(lambdaExpression.getParent()); - } - - static boolean insertSemicolon(PsiElement parent) { - return !(parent instanceof PsiExpressionList) && !(parent instanceof PsiExpression); - } - static class JavaLookupElementHighlighter { private final @NotNull PsiElement myPlace; private final @Nullable VirtualFile myOriginalFile; @@ -794,125 +785,7 @@ public final class JavaCompletionUtil { @NotNull LookupElement item, boolean overloadsMatter, boolean hasParams) { - insertParentheses(context, item, overloadsMatter, ThreeState.fromBoolean(hasParams), false); - } - - static void insertParentheses(@NotNull InsertionContext context, - @NotNull LookupElement item, - boolean overloadsMatter, - @NotNull ThreeState hasParams, // UNSURE if providing no arguments is a valid situation - boolean forceClosingParenthesis) { - Editor editor = context.getEditor(); - char completionChar = context.getCompletionChar(); - PsiFile file = context.getFile(); - - TailType tailType = completionChar == '(' ? TailTypes.noneType() : - completionChar == ':' ? TailTypes.conditionalExpressionColonType() : - LookupItem.handleCompletionChar(context.getEditor(), item, completionChar); - boolean hasTail = tailType != TailTypes.noneType() && tailType != TailTypes.unknownType(); - boolean smart = completionChar == Lookup.COMPLETE_STATEMENT_SELECT_CHAR; - - if (completionChar == '(' || completionChar == '.' || completionChar == ',' || completionChar == ';' || completionChar == ':' || completionChar == ' ') { - context.setAddCompletionChar(false); - } - - if (hasTail) { - hasParams = ThreeState.NO; - } - boolean needRightParenth = forceClosingParenthesis || - !smart && (CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET || - hasParams == ThreeState.NO && completionChar != '('); - - context.commitDocument(); - - CommonCodeStyleSettings styleSettings = CompletionStyleUtil.getCodeStyleSettings(context); - PsiElement elementAt = file.findElementAt(context.getStartOffset()); - if (elementAt == null || !(elementAt.getParent() instanceof PsiMethodReferenceExpression)) { - ThreeState hasParameters = hasParams; - boolean spaceBetweenParentheses = hasParams == ThreeState.YES && styleSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES || - hasParams == ThreeState.UNSURE && styleSettings.SPACE_WITHIN_EMPTY_METHOD_CALL_PARENTHESES; - new ParenthesesInsertHandler<>(styleSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES, spaceBetweenParentheses, - needRightParenth, styleSettings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE) { - @Override - protected boolean placeCaretInsideParentheses(InsertionContext context1, LookupElement item1) { - return hasParameters != ThreeState.NO; - } - - @Override - protected PsiElement findExistingLeftParenthesis(@NotNull InsertionContext context) { - PsiElement token = super.findExistingLeftParenthesis(context); - return isPartOfLambda(token) ? null : token; - } - - private static boolean isPartOfLambda(PsiElement token) { - return token != null && token.getParent() instanceof PsiExpressionList && - PsiUtilCore.getElementType(PsiTreeUtil.nextVisibleLeaf(token.getParent())) == JavaTokenType.ARROW; - } - }.handleInsert(context, item); - } - - if (hasParams != ThreeState.NO) { - // Invoke parameters popup - AutoPopupController.getInstance(file.getProject()).autoPopupParameterInfo(editor, overloadsMatter ? null : (PsiElement)item.getObject()); - } - - if (smart || !needRightParenth || !EditorSettingsExternalizable.getInstance().isInsertParenthesesAutomatically() || - !insertTail(context, item, tailType, hasTail)) { - return; - } - - if (completionChar == '.') { - AutoPopupController.getInstance(file.getProject()).scheduleAutoPopup(context.getEditor()); - } else if (completionChar == ',') { - AutoPopupController.getInstance(file.getProject()).autoPopupParameterInfo(context.getEditor(), null); - } - } - - private static boolean insertTail(InsertionContext context, LookupElement item, TailType tailType, boolean hasTail) { - TailType toInsert = tailType; - LookupItem lookupItem = item.as(LookupItem.CLASS_CONDITION_KEY); - if (toInsert == EqTailType.INSTANCE) { - toInsert = TailTypes.unknownType(); - } - if (lookupItem == null || lookupItem.getAttribute(LookupItem.TAIL_TYPE_ATTR) != TailTypes.unknownType()) { - if (!hasTail && item.getObject() instanceof PsiMethod && PsiTypes.voidType().equals(((PsiMethod)item.getObject()).getReturnType())) { - PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); - if (psiElement().beforeLeaf(psiElement().withText(".")).accepts(context.getFile().findElementAt(context.getTailOffset() - 1))) { - return false; - } - - boolean insertAdditionalSemicolon = true; - PsiElement leaf = context.getFile().findElementAt(context.getStartOffset()); - PsiElement composite = leaf == null ? null : leaf.getParent(); - if (composite instanceof PsiReferenceExpression) { - PsiElement parent = composite.getParent(); - if (parent instanceof PsiMethodCallExpression) { - parent = parent.getParent(); - } - if (parent instanceof PsiLambdaExpression lambda && !insertSemicolonAfter(lambda)) { - insertAdditionalSemicolon = false; - } - if (parent instanceof PsiExpressionStatement && parent.getParent() instanceof PsiForStatement forStatement && - forStatement.getUpdate() == parent) { - insertAdditionalSemicolon = false; - } - } - if (insertAdditionalSemicolon) { - toInsert = TailTypes.semicolonType(); - } - - } - } - Editor editor = context.getEditor(); - int tailOffset = context.getTailOffset(); - int afterTailOffset = toInsert.processTail(editor, tailOffset); - int caretOffset = editor.getCaretModel().getOffset(); - if (afterTailOffset > tailOffset && - tailOffset > caretOffset && - TabOutScopesTracker.getInstance().removeScopeEndingAt(editor, caretOffset) > 0) { - TabOutScopesTracker.getInstance().registerEmptyScope(editor, caretOffset, afterTailOffset); - } - return true; + JavaFrontendCompletionUtil.insertParentheses(context, item, overloadsMatter, ThreeState.fromBoolean(hasParams), false); } //need to shorten references in type argument list diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallInsertHandler.kt b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallInsertHandler.kt index d377dc698152..7f2c2070f730 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallInsertHandler.kt +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallInsertHandler.kt @@ -2,6 +2,7 @@ package com.intellij.codeInsight.completion import com.intellij.codeInsight.CodeInsightSettings +import com.intellij.codeInsight.TailTypes import com.intellij.codeInsight.completion.JavaMethodCallElement.areParameterTemplatesEnabledOnCompletion import com.intellij.codeInsight.completion.JavaMethodCallInsertHandler.Companion.needParameterHints import com.intellij.codeInsight.completion.JavaMethodCallInsertHandler.Companion.showParameterHints @@ -70,7 +71,7 @@ public class JavaMethodCallInsertHandler( private val myHandlers: List> = listOfNotNull( RefStartInsertHandler(), createDiamondInsertHandler(item), - ParenthInsertHandler(), + ParenthInsertHandler.create(item), beforeHandler, ImportQualifyAndInsertTypeParametersHandler.create(needImportOrQualify, needExplicitTypeParameters, item), MethodCallInstallerHandler(), @@ -192,12 +193,28 @@ private fun createDiamondInsertHandler(item: JavaMethodCallElement): InsertHandl return DiamondInsertHandler() } -private class ParenthInsertHandler : InsertHandler { +private class ParenthInsertHandler private constructor( + private val hasParameters: Boolean, + private val hasTailType: Boolean, +) : InsertHandler, FrontendConvertibleInsertHandler { override fun handleInsert(context: InsertionContext, item: JavaMethodCallElement) { val method = item.getObject() val allItems = context.elements - val hasParams = if (method.parameterList.isEmpty) ThreeState.NO else MethodParenthesesHandler.overloadsHaveParameters(allItems, method) - JavaCompletionUtil.insertParentheses(context, item, false, hasParams, false) + val hasParams = if (hasParameters) MethodParenthesesHandler.overloadsHaveParameters(allItems, method) else ThreeState.NO + FrontendFriendlyParenthesesInsertHandler.insertParenthesesForJavaMethod(item, context, hasParams) + } + + override fun asFrontendFriendly(): FrontendFriendlyInsertHandler? { + if (hasTailType) return null + return FrontendFriendlyParenthesesInsertHandler(hasParameters) + } + + companion object { + fun create(item: JavaMethodCallElement): InsertHandler { + val method = item.getObject() + val hasTailType = item.tailType != TailTypes.unknownType() + return ParenthInsertHandler(!method.parameterList.isEmpty, hasTailType) + } } } diff --git a/platform/completion/common/resources/intellij.platform.completion.common.xml b/platform/completion/common/resources/intellij.platform.completion.common.xml index aa52f3dbdcd0..80cc23c2cfad 100644 --- a/platform/completion/common/resources/intellij.platform.completion.common.xml +++ b/platform/completion/common/resources/intellij.platform.completion.common.xml @@ -1,2 +1,7 @@ + + + + diff --git a/platform/completion/common/src/AutoPopupControllerHelperImpl.kt b/platform/completion/common/src/AutoPopupControllerHelperImpl.kt new file mode 100644 index 000000000000..88a4f15ff513 --- /dev/null +++ b/platform/completion/common/src/AutoPopupControllerHelperImpl.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.platform.completion.common + +import com.intellij.codeInsight.AutoPopupController +import com.intellij.codeInsight.AutoPopupControllerHelper +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.impl.editorId +import com.intellij.openapi.project.Project +import com.intellij.platform.project.projectId +import com.intellij.psi.PsiElement +import com.intellij.util.PlatformUtils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +internal class AutoPopupControllerHelperImpl( + private val project: Project, + private val scope: CoroutineScope, +) : AutoPopupControllerHelper { + override fun autoPopupParameterInfoAfterCompletion(editor: Editor, selectedItem: LookupElement?) { + if (PlatformUtils.isJetBrainsClient()) { + triggerAutopopupParameterInfoOnBackend(editor, selectedItem) + } + else { + val highlightedMethod = selectedItem?.`object` as? PsiElement + AutoPopupController.getInstance(project).autoPopupParameterInfo(editor, highlightedMethod) + } + } + + private fun triggerAutopopupParameterInfoOnBackend(editor: Editor, selectedItem: LookupElement?) { + scope.launch { + val rpcId = (selectedItem as? LookupElementWithRpcId)?.rpcId + AutoPopupControllerRpc.getInstance().autoPopupParameterInfo(editor.editorId(), project.projectId(), rpcId) + } + } +} \ No newline at end of file diff --git a/platform/completion/common/src/AutoPopupControllerRpc.kt b/platform/completion/common/src/AutoPopupControllerRpc.kt new file mode 100644 index 000000000000..ef8569d2c357 --- /dev/null +++ b/platform/completion/common/src/AutoPopupControllerRpc.kt @@ -0,0 +1,28 @@ +// 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.platform.completion.common + +import com.intellij.openapi.editor.impl.EditorId +import com.intellij.platform.completion.common.protocol.RpcCompletionItemId +import com.intellij.platform.project.ProjectId +import com.intellij.platform.rpc.RemoteApiProviderService +import fleet.rpc.RemoteApi +import fleet.rpc.Rpc +import fleet.rpc.remoteApiDescriptor +import org.jetbrains.annotations.ApiStatus + +/** + * This is a hack for Java plugin + * Available in split mode only, do not try to use in monolith mode. + */ +@ApiStatus.Internal +@Rpc +interface AutoPopupControllerRpc : RemoteApi { + suspend fun autoPopupParameterInfo(editorId: EditorId, projectId: ProjectId, item: RpcCompletionItemId?) + + companion object { + @JvmStatic + suspend fun getInstance(): AutoPopupControllerRpc { + return RemoteApiProviderService.resolve(remoteApiDescriptor()) + } + } +} \ No newline at end of file diff --git a/platform/completion/common/src/LookupElementWithRpcId.kt b/platform/completion/common/src/LookupElementWithRpcId.kt new file mode 100644 index 000000000000..b71739ae3bde --- /dev/null +++ b/platform/completion/common/src/LookupElementWithRpcId.kt @@ -0,0 +1,16 @@ +// 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.platform.completion.common + +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.platform.completion.common.protocol.RpcCompletionItem +import com.intellij.platform.completion.common.protocol.RpcCompletionItemId + +/** + * Marker interface for [LookupElement]s that are backed by [RpcCompletionItem]s. + */ +interface LookupElementWithRpcId { + /** + * ID of the corresponding [RpcCompletionItem] or `null` if the element is not backed by an RPC completion item. + */ + val rpcId: RpcCompletionItemId? +} \ No newline at end of file diff --git a/platform/completion/common/src/package-info.java b/platform/completion/common/src/package-info.java new file mode 100644 index 000000000000..1acf0322b500 --- /dev/null +++ b/platform/completion/common/src/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@ApiStatus.Internal +package com.intellij.platform.completion.common; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/AutoPopupControllerHelper.kt b/platform/lang-impl/src/com/intellij/codeInsight/AutoPopupControllerHelper.kt new file mode 100644 index 000000000000..e9eef601b3aa --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/AutoPopupControllerHelper.kt @@ -0,0 +1,27 @@ +// 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 + +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.openapi.components.service +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import org.jetbrains.annotations.ApiStatus + +/** + * Hack for Java plugin for talking to backend in rem-dev scenarios. + * Must be removed once Java plugin is converted to v2. + */ +@ApiStatus.Internal +interface AutoPopupControllerHelper { + /** + * Invokes parameter info popup for the given method. + * In the RemDev scenario, the popup will be scheduled on the backend + */ + fun autoPopupParameterInfoAfterCompletion(editor: Editor, selectedItem: LookupElement?) + + companion object { + @JvmStatic + fun getInstance(project: Project): AutoPopupControllerHelper = project.service() + } +} \ No newline at end of file