Skip to content

Commit 2fe7a4d

Browse files
Mikhail Pyltsinintellij-monorepo-bot
authored andcommitted
[command-completion] IDEA-376844 Command completion. More previews
GitOrigin-RevId: a35f3298e148551ed5f5f400969007bd3035f2fc
1 parent 8ff54ab commit 2fe7a4d

File tree

9 files changed

+190
-17
lines changed

9 files changed

+190
-17
lines changed

java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaFormatCodeCompletionCommand.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal class JavaFormatCodeCompletionCommandProvider : AbstractFormatCodeCompl
1616
val element = getCommandContext(context.offset, context.psiFile) ?: return null
1717
val targetElement = findTargetToRefactorInner(element)
1818
val highlightInfoLookup = HighlightInfoLookup(targetElement.textRange, EditorColors.SEARCH_RESULT_ATTRIBUTES, 0)
19-
val command = object : JavaFormatCodeCompletionCommand(){
19+
val command = object : JavaFormatCodeCompletionCommand(context){
2020
override val highlightInfo: HighlightInfoLookup
2121
get() {
2222
return highlightInfoLookup
@@ -34,7 +34,7 @@ private fun findTargetToRefactorInner(element: PsiElement): PsiElement {
3434
return parentElement
3535
}
3636

37-
internal abstract class JavaFormatCodeCompletionCommand : AbstractFormatCodeCompletionCommand() {
37+
internal abstract class JavaFormatCodeCompletionCommand(context: CommandCompletionProviderContext) : AbstractFormatCodeCompletionCommand(context) {
3838
override fun findTargetToRefactor(element: PsiElement): PsiElement {
3939
return findTargetToRefactorInner(element)
4040
}

java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.completion.command.CommandCompletionDocumentatio
66
import com.intellij.codeInsight.completion.command.CommandCompletionLookupElement
77
import com.intellij.codeInsight.hint.HintManager
88
import com.intellij.codeInsight.hint.HintManagerImpl
9+
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
910
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
1011
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection
1112
import com.intellij.ide.highlighter.JavaFileType
@@ -72,6 +73,39 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
7273
""".trimIndent())
7374
}
7475

76+
fun testFormatPreview() {
77+
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
78+
myFixture.configureByText(JavaFileType.INSTANCE, """
79+
class A {
80+
void foo() {
81+
int y = 10;
82+
int x = y.<caret>;
83+
}
84+
}
85+
""".trimIndent())
86+
val elements = myFixture.completeBasic()
87+
val item = elements.first { element -> element.lookupString.contains("format", ignoreCase = true) }
88+
.`as`(CommandCompletionLookupElement::class.java)
89+
if (item == null) {
90+
fail()
91+
return
92+
}
93+
val preview = item.command.getPreview()
94+
if (preview !is IntentionPreviewInfo.CustomDiff) {
95+
fail()
96+
return
97+
}
98+
val expected = """
99+
class A {
100+
void foo() {
101+
int y = 10;
102+
int x = y;
103+
}
104+
}
105+
""".trimIndent()
106+
assertEquals(preview.modifiedText(), expected)
107+
}
108+
75109
fun testFormatWholeMethod() {
76110
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
77111
myFixture.configureByText(JavaFileType.INSTANCE, """
@@ -407,13 +441,26 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
407441
}
408442
}.<caret>""".trimIndent())
409443
val elements = myFixture.completeBasic()
410-
selectItem(elements.first { element -> element.lookupString.contains("Comment with line comment", ignoreCase = true) })
411-
myFixture.checkResult("""
444+
val item = elements.first { element -> element.lookupString.contains("Comment with line comment", ignoreCase = true) }
445+
selectItem(item)
446+
val expectedText = """
412447
//class A {
413448
// public String getY() {
414449
// return "y";
415450
// }
416-
//}""".trimIndent())
451+
//}""".trimIndent()
452+
myFixture.checkResult(expectedText)
453+
val lookupElement = item.`as`(CommandCompletionLookupElement::class.java)
454+
if (lookupElement == null) {
455+
fail()
456+
return
457+
}
458+
val preview = lookupElement.command.getPreview()
459+
if (preview !is IntentionPreviewInfo.CustomDiff) {
460+
fail()
461+
return
462+
}
463+
assertEquals(preview.modifiedText(), expectedText)
417464
}
418465

419466
fun testCommentElementByBlock() {

platform/lang-api/resources/messages/CodeInsightBundle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ command.completion.filter.hint=press ''{0}'' to show commands
576576
command.completion.project.tool.text=Project tool
577577
command.completion.recent.files.text=Recent files
578578
command.completion.generate.text=Generate ''{0}''
579-
command.completion.psi.element.comment.line.text=Comment/uncomment with line comment
579+
command.completion.psi.element.comment.line.text=Comment with line comment
580580
command.completion.psi.element.comment.block.text=Comment with block comment
581581
command.completion.psi.element.uncomment.block.text=Uncomment
582582
command.completion.copy.reference.description=Copy reference for ''{0}''

platform/lang-impl/api-dump-experimental.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ c:com.intellij.codeInsight.actions.VcsFacade
184184
- p:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
185185
*a:com.intellij.codeInsight.completion.command.commands.AbstractFormatCodeCompletionCommand
186186
- com.intellij.codeInsight.completion.command.CompletionCommand
187-
- <init>():V
187+
- <init>(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext):V
188188
- f:execute(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):V
189189
- a:findTargetToRefactor(com.intellij.psi.PsiElement):com.intellij.psi.PsiElement
190190
- getAdditionalInfo():java.lang.String

platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandCompletionProvider.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,19 @@ internal class CommandCompletionUnsupportedOperationException
341341

342342
internal class MyEditor(psiFileCopy: PsiFile, private val settings: EditorSettings) : ImaginaryEditor(psiFileCopy.project,
343343
psiFileCopy.viewProvider.document!!) {
344+
345+
override fun getFoldingModel(): FoldingModel {
346+
return object : FoldingModel{
347+
override fun addFoldRegion(startOffset: Int, endOffset: Int, placeholderText: String): FoldRegion? = null
348+
override fun removeFoldRegion(region: FoldRegion) = Unit
349+
override fun getAllFoldRegions(): Array<out FoldRegion?> = emptyArray()
350+
override fun isOffsetCollapsed(offset: Int): Boolean = false
351+
override fun getCollapsedRegionAtOffset(offset: Int): FoldRegion? = null
352+
override fun getFoldRegion(startOffset: Int, endOffset: Int): FoldRegion? = null
353+
override fun runBatchFoldingOperation(operation: Runnable, allowMovingCaret: Boolean, keepRelativeCaretPosition: Boolean) {}
354+
}
355+
}
356+
344357
override fun notImplemented(): RuntimeException = throw CommandCompletionUnsupportedOperationException()
345358

346359
override fun isViewer(): Boolean = false

platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractFormatCodeCompletionCommand.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.intellij.openapi.editor.Editor
1313
import com.intellij.openapi.keymap.KeymapUtil
1414
import com.intellij.psi.PsiElement
1515
import com.intellij.psi.PsiFile
16+
import com.intellij.psi.codeStyle.CodeStyleManager
1617
import org.jetbrains.annotations.Nls
1718
import java.util.Locale.getDefault
1819

@@ -33,7 +34,7 @@ abstract class AbstractFormatCodeCompletionCommandProvider :
3334
abstract fun createCommand(context: CommandCompletionProviderContext): CompletionCommand?
3435
}
3536

36-
abstract class AbstractFormatCodeCompletionCommand : CompletionCommand() {
37+
abstract class AbstractFormatCodeCompletionCommand(private val context: CommandCompletionProviderContext) : CompletionCommand() {
3738
final override val synonyms: List<String>
3839
get() = listOf("Format")
3940

@@ -54,7 +55,16 @@ abstract class AbstractFormatCodeCompletionCommand : CompletionCommand() {
5455
.replaceFirstChar { if (it.isLowerCase()) it.titlecase(getDefault()) else it.toString() }
5556

5657
override fun getPreview(): IntentionPreviewInfo {
57-
return IntentionPreviewInfo.Html(ActionsBundle.message("action.ReformatCode.description"))
58+
return tryToCalculateCommandCompletionPreview(
59+
previewGenerator = { _, psiFile, offset ->
60+
val element = getCommandContext(offset, psiFile) ?: return@tryToCalculateCommandCompletionPreview null
61+
val target = findTargetToRefactor(element)
62+
CodeStyleManager.getInstance(psiFile.getProject()).reformat(target)
63+
IntentionPreviewInfo.CustomDiff(context.psiFile.fileType, null, context.psiFile.text, psiFile.text, true)
64+
},
65+
context = context,
66+
highlight = { _, _, _ -> true },
67+
fallback = { IntentionPreviewInfo.Html(ActionsBundle.message("action.ReformatCode.description")) })
5868
}
5969

6070
final override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
package com.intellij.codeInsight.completion.command.commands
3+
4+
import com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler
5+
import com.intellij.codeInsight.completion.command.CommandCompletionProviderContext
6+
import com.intellij.codeInsight.completion.command.MyEditor
7+
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
8+
import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils
9+
import com.intellij.model.SideEffectGuard
10+
import com.intellij.openapi.editor.Editor
11+
import com.intellij.openapi.editor.SelectionModel
12+
import com.intellij.psi.PsiDocumentManager
13+
import com.intellij.psi.PsiFile
14+
import com.intellij.psi.impl.source.PostprocessReformattingAspect
15+
import org.jetbrains.annotations.ApiStatus
16+
17+
18+
@ApiStatus.Internal
19+
fun tryToCalculateCommandCompletionPreview(
20+
previewGenerator: (Editor, PsiFile, Int) -> IntentionPreviewInfo?,
21+
context: CommandCompletionProviderContext,
22+
highlight: (Int, PsiFile, SelectionModel) -> Boolean,
23+
fallback: () -> IntentionPreviewInfo,
24+
): IntentionPreviewInfo {
25+
try {
26+
val copiedFile = (context.psiFile.copy() as? PsiFile) ?: return fallback()
27+
val customEditor = createCustomEditor(copiedFile, context.editor, context.offset)
28+
29+
var preview: IntentionPreviewInfo? = null
30+
IntentionPreviewUtils.previewSession(customEditor) {
31+
PostprocessReformattingAspect.getInstance(context.project).postponeFormattingInside {
32+
preview = SideEffectGuard.computeWithoutSideEffects {
33+
if (!highlight(context.offset, copiedFile, customEditor.selectionModel)) return@computeWithoutSideEffects null
34+
previewGenerator(customEditor, copiedFile, context.offset) ?: fallback()
35+
}
36+
}
37+
}
38+
return preview ?: fallback()
39+
}
40+
catch (e: Exception) {
41+
return fallback()
42+
}
43+
}
44+
45+
internal fun tryToCalculateCommandCompletionPreview(
46+
commentHandler: MultiCaretCodeInsightActionHandler,
47+
context: CommandCompletionProviderContext,
48+
highlight: (Int, PsiFile, SelectionModel) -> Boolean,
49+
fallback: () -> IntentionPreviewInfo,
50+
): IntentionPreviewInfo {
51+
return tryToCalculateCommandCompletionPreview({ editor, psiFile, offset ->
52+
commentHandler.invoke(context.project, editor, editor.caretModel.currentCaret, psiFile)
53+
commentHandler.postInvoke()
54+
PsiDocumentManager.getInstance(context.project).commitDocument(psiFile.fileDocument)
55+
IntentionPreviewInfo.CustomDiff(context.psiFile.fileType, null, context.psiFile.text, psiFile.text, true)
56+
}, context, highlight, fallback)
57+
}
58+
59+
internal fun createCustomEditor(
60+
psiFile: PsiFile,
61+
editor: Editor,
62+
offset: Int,
63+
): MyEditor {
64+
val intentionEditor = MyEditor(psiFile, editor.settings)
65+
intentionEditor.caretModel.moveToOffset(offset)
66+
return intentionEditor
67+
}

platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/PsiElementCommentCompletionCommand.kt

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import com.intellij.codeInsight.CodeInsightBundle
55
import com.intellij.codeInsight.completion.command.CommandCompletionProviderContext
66
import com.intellij.codeInsight.completion.command.HighlightInfoLookup
77
import com.intellij.codeInsight.completion.command.getCommandContext
8+
import com.intellij.codeInsight.generation.CommentByBlockCommentHandler
9+
import com.intellij.codeInsight.generation.CommentByLineCommentHandler
10+
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
811
import com.intellij.idea.ActionsBundle
912
import com.intellij.openapi.editor.Editor
13+
import com.intellij.openapi.editor.SelectionModel
1014
import com.intellij.openapi.editor.colors.EditorColors
1115
import com.intellij.psi.PsiComment
1216
import com.intellij.psi.PsiElement
@@ -41,13 +45,29 @@ internal class PsiElementCommentByBlockCompletionCommandProvider : ActionCommand
4145
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
4246
if (editor == null) return
4347
val selectionModel = editor.selectionModel
44-
val highLevelContext = getHighLevelContext(offset, psiFile) ?: return
48+
if (!highlight(offset, psiFile, selectionModel)) return
49+
super.execute(offset, psiFile, editor)
50+
selectionModel.removeSelection()
51+
}
52+
53+
private fun highlight(
54+
offset: Int,
55+
psiFile: PsiFile,
56+
selectionModel: SelectionModel,
57+
): Boolean {
58+
val highLevelContext = getHighLevelContext(offset, psiFile) ?: return false
4559
val textRange = highLevelContext.textRange
4660
val startOffset = textRange.startOffset
4761
val endOffset = textRange.endOffset
4862
selectionModel.setSelection(startOffset, endOffset)
49-
super.execute(offset, psiFile, editor)
50-
selectionModel.removeSelection()
63+
return true
64+
}
65+
66+
override fun getPreview(): IntentionPreviewInfo {
67+
val commentByBlockCommentHandler = CommentByBlockCommentHandler()
68+
return tryToCalculateCommandCompletionPreview(commentByBlockCommentHandler, context,
69+
highlight = ({ offset, psiFile, selectionModel -> highlight(offset, psiFile, selectionModel) }),
70+
fallback = { super.getPreview() })
5171
}
5272
}
5373
}
@@ -88,7 +108,17 @@ internal class PsiElementCommentByLineCompletionCommandProvider : ActionCommandP
88108
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
89109
if (editor == null) return
90110
val selectionModel = editor.selectionModel
91-
val highLevelContext = getHighLevelContext(offset, psiFile) ?: return
111+
if (!highlight(offset, psiFile, selectionModel)) return
112+
super.execute(offset, psiFile, editor)
113+
selectionModel.removeSelection()
114+
}
115+
116+
private fun highlight(
117+
offset: Int,
118+
psiFile: PsiFile,
119+
selectionModel: SelectionModel,
120+
): Boolean {
121+
val highLevelContext = getHighLevelContext(offset, psiFile) ?: return false
92122
val textRange = highLevelContext.textRange
93123
var startOffset = textRange.startOffset
94124
val endOffset = textRange.endOffset
@@ -98,8 +128,14 @@ internal class PsiElementCommentByLineCompletionCommandProvider : ActionCommandP
98128
current = PsiTreeUtil.skipWhitespacesBackward(current)
99129
}
100130
selectionModel.setSelection(startOffset, endOffset)
101-
super.execute(offset, psiFile, editor)
102-
selectionModel.removeSelection()
131+
return true
132+
}
133+
134+
override fun getPreview(): IntentionPreviewInfo {
135+
val handler = CommentByLineCommentHandler()
136+
return tryToCalculateCommandCompletionPreview(handler, context,
137+
highlight = ({ offset, psiFile, selectionModel -> highlight(offset, psiFile, selectionModel) }),
138+
fallback = { super.getPreview() })
103139
}
104140
}
105141
}

plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinFormatCodeCompletionCommand.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ internal class KotlinFormatCodeCompletionCommandProvider : AbstractFormatCodeCom
1919
val element = getCommandContext(context.offset, context.psiFile) ?: return null
2020
val targetElement = findTargetToRefactorInner(element)
2121
val highlightInfoLookup = HighlightInfoLookup(targetElement.textRange, EditorColors.SEARCH_RESULT_ATTRIBUTES, 0)
22-
val command = object : KotlinFormatCodeCompletionCommand() {
22+
val command = object : KotlinFormatCodeCompletionCommand(context) {
2323
override val highlightInfo: HighlightInfoLookup
2424
get() {
2525
return highlightInfoLookup
@@ -38,7 +38,7 @@ private fun findTargetToRefactorInner(element: PsiElement): PsiElement {
3838
}
3939

4040

41-
internal abstract class KotlinFormatCodeCompletionCommand : AbstractFormatCodeCompletionCommand() {
41+
internal abstract class KotlinFormatCodeCompletionCommand(context: CommandCompletionProviderContext) : AbstractFormatCodeCompletionCommand(context) {
4242
override fun findTargetToRefactor(element: PsiElement): PsiElement {
4343
return findTargetToRefactorInner(element)
4444
}

0 commit comments

Comments
 (0)