[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 <nikita.eshkeev@jetbrains.com>

GitOrigin-RevId: 365eaf0c873f11673dd03cbbb7b4395410e0e110
This commit is contained in:
Nikita Eshkeev
2022-01-01 16:30:04 +00:00
committed by intellij-monorepo-bot
parent c9eefd16ec
commit 95e53ac831
12 changed files with 270 additions and 14 deletions
@@ -977,6 +977,7 @@
<testFinder implementation="com.intellij.testIntegration.JavaTestFinder"/>
<filePasteProvider implementation="com.intellij.ide.JavaFilePasteProvider" order="before fileList"/>
<enterHandlerDelegate implementation="com.intellij.javadoc.EnterInJavadocParamDescriptionHandler"/>
<enterHandlerDelegate implementation="com.intellij.javadoc.JavadocSnippetEnterHandler"/>
<editorNavigation implementation="com.intellij.javadoc.JavadocNavigationDelegate"/>
<methodNavigationOffsetProvider implementation="com.intellij.codeInsight.navigation.JavaMethodNavigationOffsetProvider"/>
<dom.customAnnotationChecker implementation="com.intellij.util.xml.impl.ExtendsClassChecker"/>
@@ -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;
}
}
@@ -0,0 +1,16 @@
// "_ignore" "true"
class Main {
/**
* A simple program.
* {@snippet :
* class HelloWorld {
* void f() {
* System.out.println();
* }
* void g() {}
* }
* }
*/
void f() {}
}
@@ -0,0 +1,16 @@
// "_ignore" "true"
class Main {
/**
* A simple program.
* {@snippet :
* class HelloWorld {
* void f() {
* System.out.println();
* <caret>
* }
* }
* }
*/
void f() {}
}
@@ -0,0 +1,13 @@
// "_ignore" "true"
class Main {
/**
* A simple program.
* {@snippet :
* class HelloWorld {
* <caret>
* }
* }
*/
void f() {}
}
@@ -0,0 +1,14 @@
// "_ignore" "true"
/**
* A simple program.
* {@snippet :
* class HelloWorld {
* public void f() {
* <caret>
* }
* }
* }
*/
class Main {
}
@@ -0,0 +1,15 @@
// "_ignore" "true"
class Main {
/**
* A simple program.
* {@snippet :
* class HelloWorld {
* void f() {
* System.out.println();
* }<caret>void g() {}
* }
* }
*/
void f() {}
}
@@ -0,0 +1,15 @@
// "_ignore" "true"
class Main {
/**
* A simple program.
* {@snippet :
* class HelloWorld {
* void f() {
* System.out.println();<caret>
* }
* }
* }
*/
void f() {}
}
@@ -0,0 +1,11 @@
// "_ignore" "true"
class Main {
/**
* A simple program.
* {@snippet :
* class HelloWorld {<caret>}
* }
*/
void f() {}
}
@@ -0,0 +1,12 @@
// "_ignore" "true"
/**
* A simple program.
* {@snippet :
* class HelloWorld {
* public void f() {<caret>}
* }
* }
*/
class Main {
}
@@ -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);
}
}
@@ -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) {