From 95e53ac83173b50508e5b822e472b6e9efd9c545 Mon Sep 17 00:00:00 2001 From: Nikita Eshkeev Date: Thu, 30 Dec 2021 23:25:23 +0300 Subject: [PATCH] [javadoc] IDEA-285556 Support language injection into snippet Add a enter handler for snippets to insert leading asterisks if needed. Signed-off-by: Nikita Eshkeev GitOrigin-RevId: 365eaf0c873f11673dd03cbbb7b4395410e0e110 --- java/java-impl/src/META-INF/JavaPlugin.xml | 1 + .../javadoc/JavadocSnippetEnterHandler.java | 69 +++++++++++++++++ .../afterEnterAfterClosingBracket.java | 16 ++++ .../enterhandler/afterEnterInMethodBody.java | 16 ++++ .../afterEnterInMethodJavadoc.java | 13 ++++ .../afterEnterInTopLevelClassJavadoc.java | 14 ++++ .../beforeEnterAfterClosingBracket.java | 15 ++++ .../enterhandler/beforeEnterInMethodBody.java | 15 ++++ .../beforeEnterInMethodJavadoc.java | 11 +++ .../beforeEnterInTopLevelClassJavadoc.java | 12 +++ .../JavadocSnippetEnterHandlerTest.java | 25 ++++++ .../enter/EnterBetweenBracesFinalHandler.java | 77 +++++++++++++++---- 12 files changed, 270 insertions(+), 14 deletions(-) create mode 100644 java/java-impl/src/com/intellij/javadoc/JavadocSnippetEnterHandler.java create mode 100644 java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterAfterClosingBracket.java create mode 100644 java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInMethodBody.java create mode 100644 java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInMethodJavadoc.java create mode 100644 java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInTopLevelClassJavadoc.java create mode 100644 java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterAfterClosingBracket.java create mode 100644 java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInMethodBody.java create mode 100644 java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInMethodJavadoc.java create mode 100644 java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInTopLevelClassJavadoc.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavadocSnippetEnterHandlerTest.java diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 5ebc70d3ef28..9ac57726ed93 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -977,6 +977,7 @@ + diff --git a/java/java-impl/src/com/intellij/javadoc/JavadocSnippetEnterHandler.java b/java/java-impl/src/com/intellij/javadoc/JavadocSnippetEnterHandler.java new file mode 100644 index 000000000000..d0f905b13c0e --- /dev/null +++ b/java/java-impl/src/com/intellij/javadoc/JavadocSnippetEnterHandler.java @@ -0,0 +1,69 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.javadoc; + +import com.intellij.application.options.CodeStyle; +import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter; +import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.CaretModel; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorModificationUtilEx; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaFile; +import com.intellij.psi.codeStyle.JavaCodeStyleSettings; +import com.intellij.psi.javadoc.PsiSnippetDocTag; +import com.intellij.util.DocumentUtil; +import com.intellij.util.ObjectUtils; +import org.jetbrains.annotations.NotNull; + +public class JavadocSnippetEnterHandler extends EnterHandlerDelegateAdapter { + + @Override + public Result postProcessEnter(@NotNull PsiFile file, + @NotNull Editor editor, + @NotNull DataContext dataContext) { + if (!(file instanceof PsiJavaFile) || !file.isValid()) return Result.Continue; + + final CaretModel caretModel = editor.getCaretModel(); + int caretOffset = caretModel.getOffset(); + + final PsiElement current = file.findElementAt(caretOffset); + if (current == null) return Result.Continue; + + final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(file.getProject()); + final PsiSnippetDocTag host = ObjectUtils.tryCast(injectedLanguageManager.getInjectionHost(current), PsiSnippetDocTag.class); + if (host == null) return Result.Continue; + + final Document document = editor.getDocument(); + final String prefix = calcPrefix(host); + + final int lineStartOffset = DocumentUtil.getLineStartOffset(caretOffset, document); + document.insertString(lineStartOffset, prefix); + + caretModel.moveToOffset(caretOffset + prefix.length()); + EditorModificationUtilEx.scrollToCaret(editor); + + return Result.Continue; + } + + private static String calcPrefix(PsiSnippetDocTag host) { + final PsiFile file = host.getContainingFile(); + final String text = file.getText(); + + int offset = host.getTextOffset(); + int asteriskOffset = offset; + while (text.charAt(offset) != '\n' && offset > 0) { + if (text.charAt(offset) == '*') asteriskOffset = offset; + offset --; + } + + final String prefix = text.substring(offset + 1, asteriskOffset); + + final JavaCodeStyleSettings settings = CodeStyle.getCustomSettings(file, JavaCodeStyleSettings.class); + + return settings.JD_LEADING_ASTERISKS_ARE_ENABLED ? prefix + "*" : prefix; + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterAfterClosingBracket.java b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterAfterClosingBracket.java new file mode 100644 index 000000000000..36e8d8d0042c --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterAfterClosingBracket.java @@ -0,0 +1,16 @@ +// "_ignore" "true" + +class Main { + /** + * A simple program. + * {@snippet : + * class HelloWorld { + * void f() { + * System.out.println(); + * } + * void g() {} + * } + * } + */ + void f() {} +} diff --git a/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInMethodBody.java b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInMethodBody.java new file mode 100644 index 000000000000..729040f1384c --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInMethodBody.java @@ -0,0 +1,16 @@ +// "_ignore" "true" + +class Main { + /** + * A simple program. + * {@snippet : + * class HelloWorld { + * void f() { + * System.out.println(); + * + * } + * } + * } + */ + void f() {} +} diff --git a/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInMethodJavadoc.java b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInMethodJavadoc.java new file mode 100644 index 000000000000..cbe458e5d2ff --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInMethodJavadoc.java @@ -0,0 +1,13 @@ +// "_ignore" "true" + +class Main { + /** + * A simple program. + * {@snippet : + * class HelloWorld { + * + * } + * } + */ + void f() {} +} diff --git a/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInTopLevelClassJavadoc.java b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInTopLevelClassJavadoc.java new file mode 100644 index 000000000000..250782c7c842 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/afterEnterInTopLevelClassJavadoc.java @@ -0,0 +1,14 @@ +// "_ignore" "true" + +/** + * A simple program. + * {@snippet : + * class HelloWorld { + * public void f() { + * + * } + * } + * } + */ +class Main { +} diff --git a/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterAfterClosingBracket.java b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterAfterClosingBracket.java new file mode 100644 index 000000000000..ae29608135b1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterAfterClosingBracket.java @@ -0,0 +1,15 @@ +// "_ignore" "true" + +class Main { + /** + * A simple program. + * {@snippet : + * class HelloWorld { + * void f() { + * System.out.println(); + * }void g() {} + * } + * } + */ + void f() {} +} diff --git a/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInMethodBody.java b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInMethodBody.java new file mode 100644 index 000000000000..42f945aed19e --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInMethodBody.java @@ -0,0 +1,15 @@ +// "_ignore" "true" + +class Main { + /** + * A simple program. + * {@snippet : + * class HelloWorld { + * void f() { + * System.out.println(); + * } + * } + * } + */ + void f() {} +} diff --git a/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInMethodJavadoc.java b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInMethodJavadoc.java new file mode 100644 index 000000000000..d0ea7fc61439 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInMethodJavadoc.java @@ -0,0 +1,11 @@ +// "_ignore" "true" + +class Main { + /** + * A simple program. + * {@snippet : + * class HelloWorld {} + * } + */ + void f() {} +} diff --git a/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInTopLevelClassJavadoc.java b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInTopLevelClassJavadoc.java new file mode 100644 index 000000000000..8164ce454dda --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadoc/snippet/enterhandler/beforeEnterInTopLevelClassJavadoc.java @@ -0,0 +1,12 @@ +// "_ignore" "true" + +/** + * A simple program. + * {@snippet : + * class HelloWorld { + * public void f() {} + * } + * } + */ +class Main { +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavadocSnippetEnterHandlerTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavadocSnippetEnterHandlerTest.java new file mode 100644 index 000000000000..41b37057607f --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavadocSnippetEnterHandlerTest.java @@ -0,0 +1,25 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.codeInsight.javadoc; + +import com.intellij.codeInsight.daemon.quickFix.ActionHint; +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import org.jetbrains.annotations.NotNull; + +public class JavadocSnippetEnterHandlerTest extends LightQuickFixParameterizedTestCase { + @Override + protected String getBasePath() { + return "/codeInsight/javadoc/snippet/enterhandler"; + } + + @Override + protected void doAction(@NotNull ActionHint actionHint, @NotNull String testFullPath, @NotNull String testName) { + executeAction("EditorEnter"); + + checkResult(testName); + } + + private void checkResult(@NotNull final String testName) { + final String expectedFilePath = getBasePath() + "/after" + testName; + checkResultByFile("In file: " + expectedFilePath, expectedFilePath, false); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/EnterBetweenBracesFinalHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/EnterBetweenBracesFinalHandler.java index d2c55a36ca03..1fe10872f80d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/EnterBetweenBracesFinalHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/EnterBetweenBracesFinalHandler.java @@ -6,13 +6,17 @@ import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.editorActions.CodeDocumentationUtil; import com.intellij.codeInsight.editorActions.EnterHandler; import com.intellij.lang.Language; +import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiLanguageInjectionHost; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; @@ -33,7 +37,6 @@ public class EnterBetweenBracesFinalHandler extends EnterHandlerDelegateAdapter if (!CodeInsightSettings.getInstance().SMART_INDENT_ON_ENTER) { return Result.Continue; } - Document document = editor.getDocument(); CharSequence text = document.getCharsSequence(); int caretOffset = caretOffsetRef.get().intValue(); @@ -43,29 +46,76 @@ public class EnterBetweenBracesFinalHandler extends EnterHandlerDelegateAdapter return Result.Continue; } - final int line = document.getLineNumber(caretOffset); - final int start = document.getLineStartOffset(line); - final CodeDocumentationUtil.CommentContext commentContext = - CodeDocumentationUtil.tryParseCommentContext(file, text, caretOffset, start); - - // special case: enter inside "()" or "{}" - String indentInsideJavadoc = helper.isInComment(file, editor, caretOffset) && commentContext.docAsterisk - ? CodeDocumentationUtil.getIndentInsideJavadoc(document, caretOffset) - : null; + final Data data = new Data(file, document, caretOffset); + final String indentInsideJavadoc = data.getIndentInsideJavadoc(helper, editor); originalHandler.execute(editor, editor.getCaretModel().getCurrentCaret(), dataContext); Project project = editor.getProject(); if (indentInsideJavadoc != null && project != null && - CodeStyleManager.getInstance(project).getDocCommentSettings(file).isLeadingAsteriskEnabled()) { - document.insertString(editor.getCaretModel().getOffset(), "*" + indentInsideJavadoc); + data.isLeadingAsteriskEnabled()) { + editor.getDocument().insertString(editor.getCaretModel().getOffset(), "*" + indentInsideJavadoc); } helper.formatAtOffset(file, editor, editor.getCaretModel().getOffset(), EnterHandler.getLanguage(dataContext)); return indentInsideJavadoc == null ? Result.Continue : Result.DefaultForceIndent; } + private static final class Data { + private final @NotNull PsiFile myFile; + private final @NotNull Document myDocument; + private final @NotNull CharSequence myText; + private final int myOffset; + + private Data(@NotNull PsiFile file, + @NotNull Document document, + int offset) { + final PsiElement element = file.findElementAt(offset); + + if (element != null) { + final PsiLanguageInjectionHost injectionHost = InjectedLanguageManager.getInstance(file.getProject()).getInjectionHost(element); + if (injectionHost != null) { + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(element.getProject()); + final Document hostDocument = documentManager.getDocument(injectionHost.getContainingFile()); + if (hostDocument != null) { + myDocument = hostDocument; + myText = hostDocument.getCharsSequence(); + myFile = injectionHost.getContainingFile(); + myOffset = injectionHost.getTextOffset(); + return; + } + } + } + + myFile = file; + myDocument = document; + myText = document.getText(); + myOffset = offset; + } + + public @Nullable String getIndentInsideJavadoc(EnterBetweenBracesDelegate helper, Editor editor) { + final CodeDocumentationUtil.CommentContext commentContext = getCommentContext(); + return isInComment(helper, editor) && commentContext.docAsterisk + ? CodeDocumentationUtil.getIndentInsideJavadoc(myDocument, myOffset) + : null; + } + + private @NotNull CodeDocumentationUtil.CommentContext getCommentContext() { + final int line = myDocument.getLineNumber(myOffset); + final int start = myDocument.getLineStartOffset(line); + return CodeDocumentationUtil.tryParseCommentContext(myFile, myText, myOffset, start); + } + + private boolean isInComment(final EnterBetweenBracesDelegate helper, Editor editor) { + return helper.isInComment(myFile, editor, myOffset); + } + + private boolean isLeadingAsteriskEnabled() { + return CodeStyleManager.getInstance(myFile.getProject()).getDocCommentSettings(myFile).isLeadingAsteriskEnabled(); + } + } + protected boolean isApplicable(@NotNull PsiFile file, @NotNull Editor editor, CharSequence documentText, @@ -79,8 +129,7 @@ public class EnterBetweenBracesFinalHandler extends EnterHandlerDelegateAdapter !helper.bracesAreInTheSameElement(file, editor, prevCharOffset, nextCharOffset); } - @NotNull - protected EnterBetweenBracesDelegate getLanguageImplementation(@Nullable Language language) { + protected static @NotNull EnterBetweenBracesDelegate getLanguageImplementation(@Nullable Language language) { if (language != null) { final EnterBetweenBracesDelegate helper = EnterBetweenBracesDelegate.EP_NAME.forLanguage(language); if (helper != null) {