From 64680b6254a0cf2a6fdeb65e24d897e8344d8123 Mon Sep 17 00:00:00 2001 From: UNV Date: Sat, 8 Nov 2025 22:43:13 +0300 Subject: [PATCH 1/2] Refactoring JavadocCompletionConfidence and JavaDocCompletionContributor. --- .../JavaDocCompletionContributor.java | 846 +++++++++--------- .../JavadocCompletionConfidence.java | 64 +- 2 files changed, 480 insertions(+), 430 deletions(-) diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaDocCompletionContributor.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaDocCompletionContributor.java index 3aa47cf82b..fc71eece36 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaDocCompletionContributor.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaDocCompletionContributor.java @@ -29,6 +29,7 @@ import com.intellij.java.language.psi.javadoc.*; import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.application.util.matcher.PrefixMatcher; import consulo.codeEditor.CaretModel; @@ -55,14 +56,12 @@ import consulo.logging.Logger; import consulo.platform.Platform; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.collection.ContainerUtil; import consulo.util.lang.CharArrayUtil; -import consulo.util.lang.Comparing; import consulo.util.lang.StringUtil; -import consulo.util.lang.SystemProperties; -import consulo.util.lang.function.Conditions; +import consulo.util.lang.function.Predicates; import jakarta.annotation.Nonnull; -import org.jetbrains.annotations.NonNls; import java.util.*; @@ -70,445 +69,494 @@ import static consulo.language.pattern.StandardPatterns.string; /** - * User: ik - * Date: 05.03.2003 - * Time: 21:40:11 + * @author ik + * @since 2003-03-05 */ @ExtensionImpl(id = "javadoc", order = "last, before javaLegacy") public class JavaDocCompletionContributor extends CompletionContributor { - private static final Logger LOG = Logger.getInstance(JavaDocCompletionContributor.class); - private static final - @NonNls - String VALUE_TAG = "value"; - private static final - @NonNls - String LINK_TAG = "link"; - private static final InsertHandler PARAM_DESCRIPTION_INSERT_HANDLER = (context, item) -> - { - if (context.getCompletionChar() != Lookup.REPLACE_SELECT_CHAR) { - return; - } + private static final Logger LOG = Logger.getInstance(JavaDocCompletionContributor.class); + private static final String VALUE_TAG = "value"; + private static final String LINK_TAG = "link"; - context.commitDocument(); - PsiDocTag docTag = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiDocTag.class, false); - if (docTag != null) { - Document document = context.getDocument(); - int tagEnd = DocTagSelectioner.getDocTagRange(docTag, document.getCharsSequence(), 0).getEndOffset(); - int tail = context.getTailOffset(); - if (tail < tagEnd) { - document.deleteString(tail, tagEnd); - } - } - }; - - public JavaDocCompletionContributor() { - extend(CompletionType.BASIC, PsiJavaPatterns.psiElement(JavaDocTokenType.DOC_TAG_NAME), new TagChooser()); - - extend(CompletionType.BASIC, PsiJavaPatterns.psiElement().inside(PsiDocComment.class), new CompletionProvider() { - @Override - public void addCompletions(@Nonnull final CompletionParameters parameters, final ProcessingContext context, @Nonnull final CompletionResultSet result) { - final PsiElement position = parameters.getPosition(); - boolean isArg = PsiJavaPatterns.psiElement().afterLeaf("(").accepts(position); - PsiDocTag tag = PsiTreeUtil.getParentOfType(position, PsiDocTag.class); - boolean onlyConstants = !isArg && tag != null && tag.getName().equals(VALUE_TAG); - - final PsiReference ref = position.getContainingFile().findReferenceAt(parameters.getOffset()); - if (ref instanceof PsiJavaReference) { - result.stopHere(); - - for (LookupElement item : completeJavadocReference(position, (PsiJavaReference) ref)) { - if (onlyConstants) { - Object o = item.getObject(); - if (!(o instanceof PsiField)) { - continue; - } - PsiField field = (PsiField) o; - if (!(field.hasModifierProperty(PsiModifier.STATIC) && field.getInitializer() != null && JavaConstantExpressionEvaluator.computeConstantExpression(field.getInitializer(), false) != null)) { - continue; - } - } + private static final InsertHandler PARAM_DESCRIPTION_INSERT_HANDLER = (context, item) -> { + if (context.getCompletionChar() != Lookup.REPLACE_SELECT_CHAR) { + return; + } - if (isArg) { - item = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(item); + context.commitDocument(); + PsiDocTag docTag = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiDocTag.class, false); + if (docTag != null) { + Document document = context.getDocument(); + int tagEnd = DocTagSelectioner.getDocTagRange(docTag, document.getCharsSequence(), 0).getEndOffset(); + int tail = context.getTailOffset(); + if (tail < tagEnd) { + document.deleteString(tail, tagEnd); } - result.addElement(item); - } - - JavaCompletionContributor.addAllClasses(parameters, result, new JavaCompletionSession(result)); } + }; - if (tag != null && "author".equals(tag.getName())) { - result.addElement(LookupElementBuilder.create(SystemProperties.getUserName())); - } - } - }); - - extend(CompletionType.SMART, psiElement().inside(psiElement(PsiDocTag.class).withName(string().oneOf(PsiKeyword.THROWS, "exception"))), new CompletionProvider() { - @Override - public void addCompletions(@Nonnull final CompletionParameters parameters, final ProcessingContext context, @Nonnull final CompletionResultSet result) { - final PsiElement element = parameters.getPosition(); - final Set throwsSet = new HashSet<>(); - final PsiMethod method = PsiTreeUtil.getContextOfType(element, PsiMethod.class, true); - if (method != null) { - for (PsiClassType ref : method.getThrowsList().getReferencedTypes()) { - final PsiClass exception = ref.resolve(); - if (exception != null && throwsSet.add(exception)) { - result.addElement(TailTypeDecorator.withTail(new JavaPsiClassReferenceElement(exception), TailType.HUMBLE_SPACE_BEFORE_WORD)); + public JavaDocCompletionContributor() { + extend(CompletionType.BASIC, PsiJavaPatterns.psiElement(JavaDocTokenType.DOC_TAG_NAME), new TagChooser()); + + extend( + CompletionType.BASIC, + PsiJavaPatterns.psiElement().inside(PsiDocComment.class), + (parameters, context, result) -> { + PsiElement position = parameters.getPosition(); + boolean isArg = PsiJavaPatterns.psiElement().afterLeaf("(").accepts(position); + PsiDocTag tag = PsiTreeUtil.getParentOfType(position, PsiDocTag.class); + boolean onlyConstants = !isArg && tag != null && tag.getName().equals(VALUE_TAG); + + PsiReference ref = position.getContainingFile().findReferenceAt(parameters.getOffset()); + if (ref instanceof PsiJavaReference javaRef) { + result.stopHere(); + + for (LookupElement item : completeJavadocReference(position, javaRef)) { + if (onlyConstants) { + Object o = item.getObject(); + if (!(o instanceof PsiField field)) { + continue; + } + if (!(field.isStatic() && field.getInitializer() != null + && JavaConstantExpressionEvaluator.computeConstantExpression(field.getInitializer(), false) != null)) { + continue; + } + } + + if (isArg) { + item = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(item); + } + result.addElement(item); + } + + JavaCompletionContributor.addAllClasses(parameters, result, new JavaCompletionSession(result)); + } + + if (tag != null && "author".equals(tag.getName())) { + result.addElement(LookupElementBuilder.create(Platform.current().user().name())); + } } - } - } - } - }); - } - - @Nonnull - private List completeJavadocReference(PsiElement position, PsiJavaReference ref) { - JavaCompletionProcessor processor = new JavaCompletionProcessor(position, TrueFilter.INSTANCE, JavaCompletionProcessor.Options.CHECK_NOTHING, Conditions.alwaysTrue()); - ref.processVariants(processor); - return ContainerUtil.map(processor.getResults(), (completionResult) -> - { - LookupElement item = createReferenceLookupItem(completionResult.getElement()); - item.putUserData(JavaCompletionUtil.FORCE_SHOW_SIGNATURE_ATTR, Boolean.TRUE); - return item; - }); - } - - private LookupElement createReferenceLookupItem(final Object element) { - if (element instanceof PsiMethod) { - return new JavaMethodCallElement((PsiMethod) element) { - @Override - public void handleInsert(InsertionContext context) { - new MethodSignatureInsertHandler().handleInsert(context, this); - } - }; + ); + + extend( + CompletionType.SMART, + psiElement().inside(psiElement(PsiDocTag.class).withName(string().oneOf(PsiKeyword.THROWS, "exception"))), + (parameters, context, result) -> { + PsiElement element = parameters.getPosition(); + Set throwsSet = new HashSet<>(); + PsiMethod method = PsiTreeUtil.getContextOfType(element, PsiMethod.class, true); + if (method != null) { + for (PsiClassType ref : method.getThrowsList().getReferencedTypes()) { + PsiClass exception = ref.resolve(); + if (exception != null && throwsSet.add(exception)) { + result.addElement(TailTypeDecorator.withTail( + new JavaPsiClassReferenceElement(exception), + TailType.HUMBLE_SPACE_BEFORE_WORD + )); + } + } + } + } + ); } - if (element instanceof PsiClass) { - JavaPsiClassReferenceElement classElement = new JavaPsiClassReferenceElement((PsiClass) element); - classElement.setInsertHandler(JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER); - return classElement; + + @Nonnull + @RequiredReadAction + private List completeJavadocReference(PsiElement position, PsiJavaReference ref) { + JavaCompletionProcessor processor = new JavaCompletionProcessor( + position, + TrueFilter.INSTANCE, + JavaCompletionProcessor.Options.CHECK_NOTHING, + Predicates.alwaysTrue() + ); + ref.processVariants(processor); + return ContainerUtil.map( + processor.getResults(), + completionResult -> { + LookupElement item = createReferenceLookupItem(completionResult.getElement()); + item.putUserData(JavaCompletionUtil.FORCE_SHOW_SIGNATURE_ATTR, Boolean.TRUE); + return item; + } + ); } - return LookupItemUtil.objectToLookupItem(element); - } - - private static PsiParameter getDocTagParam(PsiElement tag) { - if (tag instanceof PsiDocTag && "param".equals(((PsiDocTag) tag).getName())) { - PsiDocTagValue value = ((PsiDocTag) tag).getValueElement(); - if (value instanceof PsiDocParamRef) { - final PsiReference psiReference = value.getReference(); - PsiElement target = psiReference != null ? psiReference.resolve() : null; - if (target instanceof PsiParameter) { - return (PsiParameter) target; + private LookupElement createReferenceLookupItem(Object element) { + if (element instanceof PsiMethod method) { + return new JavaMethodCallElement(method) { + @Override + @RequiredUIAccess + public void handleInsert(InsertionContext context) { + new MethodSignatureInsertHandler().handleInsert(context, this); + } + }; } - } - } - return null; - } - - @Override - public void fillCompletionVariants(@Nonnull final CompletionParameters parameters, @Nonnull final CompletionResultSet result) { - PsiElement position = parameters.getPosition(); - if (PsiJavaPatterns.psiElement(JavaDocTokenType.DOC_COMMENT_DATA).accepts(position)) { - final PsiParameter param = getDocTagParam(position.getParent()); - if (param != null) { - suggestSimilarParameterDescriptions(result, position, param); - } - - suggestLinkWrappingVariants(parameters, result.withPrefixMatcher(CompletionUtilCore.findJavaIdentifierPrefix(parameters)), position); - - if (!result.getPrefixMatcher().getPrefix().isEmpty()) { - for (String keyword : Set.of("null", "true", "false")) { - String tagText = "{@code " + keyword + "}"; - result.addElement(LookupElementBuilder.create(keyword).withPresentableText(tagText).withInsertHandler((context, item) -> context.getDocument().replaceString(context - .getStartOffset(), context.getTailOffset(), tagText))); + if (element instanceof PsiClass psiClass) { + JavaPsiClassReferenceElement classElement = new JavaPsiClassReferenceElement(psiClass); + classElement.setInsertHandler(JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER); + return classElement; } - } - return; + return LookupItemUtil.objectToLookupItem(element); } - super.fillCompletionVariants(parameters, result); - } - - private void suggestLinkWrappingVariants(@Nonnull CompletionParameters parameters, @Nonnull CompletionResultSet result, PsiElement position) { - PrefixMatcher matcher = result.getPrefixMatcher(); - int prefixStart = parameters.getOffset() - matcher.getPrefix().length() - position.getTextRange().getStartOffset(); - if (prefixStart > 0 && position.getText().charAt(prefixStart - 1) == '#') { - String mockCommentPrefix = "/** {@link "; - String mockText = mockCommentPrefix + position.getText().substring(prefixStart - 1) + "}*/"; - PsiDocComment mockComment = JavaPsiFacade.getElementFactory(position.getProject()).createDocCommentFromText(mockText, position); - PsiJavaReference ref = (PsiJavaReference) mockComment.findReferenceAt(mockCommentPrefix.length() + 1); - assert ref != null : mockText; - for (LookupElement element : completeJavadocReference(ref.getElement(), ref)) { - result.addElement(LookupElementDecorator.withInsertHandler(element, wrapIntoLinkTag((context, item) -> element.handleInsert(context)))); - } - } else { - InsertHandler handler = wrapIntoLinkTag(JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER); - AllClassesGetter.processJavaClasses(parameters, matcher, parameters.getInvocationCount() == 1, psiClass -> result.addElement(AllClassesGetter.createLookupItem(psiClass, handler))); - } - } - - @Nonnull - private static InsertHandler wrapIntoLinkTag(InsertHandler delegate) { - return (context, item) -> - { - Document document = context.getDocument(); - - String link = "{@link "; - int startOffset = context.getStartOffset(); - int sharpLength = document.getCharsSequence().charAt(startOffset - 1) == '#' ? 1 : 0; - - document.insertString(startOffset - sharpLength, link); - document.insertString(context.getTailOffset(), "}"); - context.setTailOffset(context.getTailOffset() - 1); - context.getOffsetMap().addOffset(CompletionInitializationContext.START_OFFSET, startOffset + link.length()); - - context.commitDocument(); - delegate.handleInsert(context, item); - if (item.getObject() instanceof PsiField) { - context.getEditor().getCaretModel().moveToOffset(context.getTailOffset() + 1); - } - }; - } - - private static void suggestSimilarParameterDescriptions(CompletionResultSet result, PsiElement position, final PsiParameter param) { - final Set descriptions = new HashSet<>(); - position.getContainingFile().accept(new PsiRecursiveElementWalkingVisitor() { - @Override - public void visitElement(PsiElement element) { - PsiParameter param1 = getDocTagParam(element); - if (param1 != null && param1 != param && Comparing.equal(param1.getName(), param.getName()) && Comparing.equal(param1.getType(), param.getType())) { - String text = ""; - for (PsiElement psiElement : ((PsiDocTag) element).getDataElements()) { - if (psiElement != ((PsiDocTag) element).getValueElement()) { - text += psiElement.getText(); - } - } - text = text.trim(); - if (text.contains(" ")) { - descriptions.add(text); - } + @RequiredReadAction + private static PsiParameter getDocTagParam(PsiElement tag) { + if (tag instanceof PsiDocTag docTag + && "param".equals(docTag.getName()) + && docTag.getValueElement() instanceof PsiDocParamRef docParamRef + && docParamRef.getReference() instanceof PsiReference psiReference + && psiReference.resolve() instanceof PsiParameter param) { + return param; } - - super.visitElement(element); - } - }); - for (String description : descriptions) { - result.addElement(PrioritizedLookupElement.withPriority(LookupElementBuilder.create(description).withInsertHandler(PARAM_DESCRIPTION_INSERT_HANDLER), 1)); + return null; } - } - @Nonnull - @Override - public Language getLanguage() { - return JavaLanguage.INSTANCE; - } + @Override + @RequiredReadAction + public void fillCompletionVariants(@Nonnull CompletionParameters parameters, @Nonnull CompletionResultSet result) { + PsiElement position = parameters.getPosition(); + if (PsiJavaPatterns.psiElement(JavaDocTokenType.DOC_COMMENT_DATA).accepts(position)) { + PsiParameter param = getDocTagParam(position.getParent()); + if (param != null) { + suggestSimilarParameterDescriptions(result, position, param); + } - private static class TagChooser implements CompletionProvider { + suggestLinkWrappingVariants( + parameters, + result.withPrefixMatcher(CompletionUtilCore.findJavaIdentifierPrefix(parameters)), + position + ); + + if (!result.getPrefixMatcher().getPrefix().isEmpty()) { + for (String keyword : Set.of("null", "true", "false")) { + String tagText = "{@code " + keyword + "}"; + result.addElement( + LookupElementBuilder.create(keyword) + .withPresentableText(tagText) + .withInsertHandler( + (context, item) -> context.getDocument() + .replaceString(context.getStartOffset(), context.getTailOffset(), tagText) + ) + ); + } + } - @Override - public void addCompletions(@Nonnull final CompletionParameters parameters, final ProcessingContext context, @Nonnull final CompletionResultSet result) { - final List ret = new ArrayList<>(); - final PsiElement position = parameters.getPosition(); - final PsiDocComment comment = PsiTreeUtil.getParentOfType(position, PsiDocComment.class); - assert comment != null; - PsiElement parent = comment.getContext(); - if (parent instanceof PsiJavaFile) { - final PsiJavaFile file = (PsiJavaFile) parent; - if (PsiJavaPackage.PACKAGE_INFO_FILE.equals(file.getName())) { - final String packageName = file.getPackageName(); - parent = JavaPsiFacade.getInstance(position.getProject()).findPackage(packageName); + return; } - } - final boolean isInline = position.getContext() instanceof PsiInlineDocTag; + super.fillCompletionVariants(parameters, result); + } - for (JavadocTagInfo info : JavadocManager.SERVICE.getInstance(position.getProject()).getTagInfos(parent)) { - String tagName = info.getName(); - if (tagName.equals(SuppressionUtil.SUPPRESS_INSPECTIONS_TAG_NAME)) { - continue; - } - if (isInline != info.isInline()) { - continue; - } - ret.add(tagName); - addSpecialTags(ret, comment, tagName); - } - - InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(position.getProject()).getInspectionProfile(); - JavaDocLocalInspection inspection = (JavaDocLocalInspection) inspectionProfile.getUnwrappedTool(JavaDocLocalInspection.SHORT_NAME, position); - if (inspection != null) { - final StringTokenizer tokenizer = new StringTokenizer(inspection.myAdditionalJavadocTags, ", "); - while (tokenizer.hasMoreTokens()) { - ret.add(tokenizer.nextToken()); + @RequiredReadAction + private void suggestLinkWrappingVariants( + @Nonnull CompletionParameters parameters, + @Nonnull CompletionResultSet result, + PsiElement position + ) { + PrefixMatcher matcher = result.getPrefixMatcher(); + int prefixStart = parameters.getOffset() - matcher.getPrefix().length() - position.getTextRange().getStartOffset(); + if (prefixStart > 0 && position.getText().charAt(prefixStart - 1) == '#') { + String mockCommentPrefix = "/** {@link "; + String mockText = mockCommentPrefix + position.getText().substring(prefixStart - 1) + "}*/"; + PsiDocComment mockComment = JavaPsiFacade.getElementFactory(position.getProject()).createDocCommentFromText(mockText, position); + PsiJavaReference ref = (PsiJavaReference) mockComment.findReferenceAt(mockCommentPrefix.length() + 1); + assert ref != null : mockText; + for (LookupElement element : completeJavadocReference(ref.getElement(), ref)) { + result.addElement(LookupElementDecorator.withInsertHandler( + element, + wrapIntoLinkTag((context, item) -> element.handleInsert(context)) + )); + } } - } - for (final String s : ret) { - if (isInline) { - result.addElement(LookupElementDecorator.withInsertHandler(LookupElementBuilder.create(s), new InlineInsertHandler())); - } else { - result.addElement(TailTypeDecorator.withTail(LookupElementBuilder.create(s), TailType.INSERT_SPACE)); + else { + InsertHandler handler = wrapIntoLinkTag(JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER); + AllClassesGetter.processJavaClasses( + parameters, + matcher, + parameters.getInvocationCount() == 1, + psiClass -> result.addElement(AllClassesGetter.createLookupItem(psiClass, handler)) + ); } - } - result.stopHere(); // no word completions at this point } - private static void addSpecialTags(final List result, PsiDocComment comment, String tagName) { - if ("author".equals(tagName)) { - result.add(tagName + " " + Platform.current().user().name()); - return; - } - - if ("param".equals(tagName)) { - PsiMethod psiMethod = PsiTreeUtil.getParentOfType(comment, PsiMethod.class); - if (psiMethod != null) { - PsiDocTag[] tags = comment.getTags(); - for (PsiParameter param : psiMethod.getParameterList().getParameters()) { - if (!JavaDocLocalInspection.isFound(tags, param)) { - result.add(tagName + " " + param.getName()); + @Nonnull + private static InsertHandler wrapIntoLinkTag(InsertHandler delegate) { + return (context, item) -> + { + Document document = context.getDocument(); + + String link = "{@link "; + int startOffset = context.getStartOffset(); + int sharpLength = document.getCharsSequence().charAt(startOffset - 1) == '#' ? 1 : 0; + + document.insertString(startOffset - sharpLength, link); + document.insertString(context.getTailOffset(), "}"); + context.setTailOffset(context.getTailOffset() - 1); + context.getOffsetMap().addOffset(CompletionInitializationContext.START_OFFSET, startOffset + link.length()); + + context.commitDocument(); + delegate.handleInsert(context, item); + if (item.getObject() instanceof PsiField) { + context.getEditor().getCaretModel().moveToOffset(context.getTailOffset() + 1); } - } - } - return; - } - - if ("see".equals(tagName)) { - PsiMember member = PsiTreeUtil.getParentOfType(comment, PsiMember.class); - if (member instanceof PsiClass) { - InheritanceUtil.processSupers((PsiClass) member, false, psiClass -> - { - String name = psiClass.getQualifiedName(); - if (StringUtil.isNotEmpty(name) && !CommonClassNames.JAVA_LANG_OBJECT.equals(name)) { - result.add("see " + name); + }; + } + + private static void suggestSimilarParameterDescriptions(CompletionResultSet result, PsiElement position, final PsiParameter param) { + final Set descriptions = new HashSet<>(); + position.getContainingFile().accept(new PsiRecursiveElementWalkingVisitor() { + @Override + @RequiredReadAction + public void visitElement(PsiElement element) { + PsiParameter param1 = getDocTagParam(element); + if (param1 != null && param1 != param + && Objects.equals(param1.getName(), param.getName()) + && Objects.equals(param1.getType(), param.getType())) { + String text = ""; + PsiDocTag docTag = (PsiDocTag) element; + for (PsiElement psiElement : docTag.getDataElements()) { + if (psiElement != docTag.getValueElement()) { + text += psiElement.getText(); + } + } + text = text.trim(); + if (text.contains(" ")) { + descriptions.add(text); + } + } + + super.visitElement(element); } - return true; - }); + }); + for (String description : descriptions) { + result.addElement(PrioritizedLookupElement.withPriority(LookupElementBuilder.create(description) + .withInsertHandler(PARAM_DESCRIPTION_INSERT_HANDLER), 1)); } - } } - } - private static class InlineInsertHandler implements InsertHandler { + @Nonnull @Override - public void handleInsert(InsertionContext context, LookupElement item) { - if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) { - final Project project = context.getProject(); - PsiDocumentManager.getInstance(project).commitAllDocuments(); - final Editor editor = context.getEditor(); - final CaretModel caretModel = editor.getCaretModel(); - final int offset = caretModel.getOffset(); - final PsiElement element = context.getFile().findElementAt(offset - 1); - PsiDocTag tag = PsiTreeUtil.getParentOfType(element, PsiDocTag.class); - assert tag != null; - - for (PsiElement child = tag.getFirstChild(); child != null; child = child.getNextSibling()) { - if (child instanceof PsiDocToken) { - PsiDocToken token = (PsiDocToken) child; - if (token.getTokenType() == JavaDocTokenType.DOC_INLINE_TAG_END) { - return; + public Language getLanguage() { + return JavaLanguage.INSTANCE; + } + + private static class TagChooser implements CompletionProvider { + @Override + @RequiredReadAction + public void addCompletions( + @Nonnull CompletionParameters parameters, + ProcessingContext context, + @Nonnull CompletionResultSet result + ) { + List ret = new ArrayList<>(); + PsiElement position = parameters.getPosition(); + PsiDocComment comment = PsiTreeUtil.getParentOfType(position, PsiDocComment.class); + assert comment != null; + PsiElement parent = comment.getContext(); + if (parent instanceof PsiJavaFile file && PsiJavaPackage.PACKAGE_INFO_FILE.equals(file.getName())) { + String packageName = file.getPackageName(); + parent = JavaPsiFacade.getInstance(position.getProject()).findPackage(packageName); + } + + boolean isInline = position.getContext() instanceof PsiInlineDocTag; + + for (JavadocTagInfo info : JavadocManager.SERVICE.getInstance(position.getProject()).getTagInfos(parent)) { + String tagName = info.getName(); + if (tagName.equals(SuppressionUtil.SUPPRESS_INSPECTIONS_TAG_NAME)) { + continue; + } + if (isInline != info.isInline()) { + continue; + } + ret.add(tagName); + addSpecialTags(ret, comment, tagName); } - } + + InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(position.getProject()).getInspectionProfile(); + JavaDocLocalInspection inspection = + (JavaDocLocalInspection) inspectionProfile.getUnwrappedTool(JavaDocLocalInspection.SHORT_NAME, position); + if (inspection != null) { + StringTokenizer tokenizer = new StringTokenizer(inspection.myAdditionalJavadocTags, ", "); + while (tokenizer.hasMoreTokens()) { + ret.add(tokenizer.nextToken()); + } + } + for (String s : ret) { + if (isInline) { + result.addElement(LookupElementDecorator.withInsertHandler(LookupElementBuilder.create(s), new InlineInsertHandler())); + } + else { + result.addElement(TailTypeDecorator.withTail(LookupElementBuilder.create(s), TailType.INSERT_SPACE)); + } + } + result.stopHere(); // no word completions at this point } - final String name = tag.getName(); - - final CharSequence chars = editor.getDocument().getCharsSequence(); - final int currentOffset = caretModel.getOffset(); - if (chars.charAt(currentOffset) == '}') { - caretModel.moveToOffset(offset + 1); - } else if (chars.charAt(currentOffset + 1) == '}' && chars.charAt(currentOffset) == ' ') { - caretModel.moveToOffset(offset + 2); - } else if (name.equals(LINK_TAG)) { - EditorModificationUtil.insertStringAtCaret(editor, " }"); - caretModel.moveToOffset(offset + 1); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - editor.getSelectionModel().removeSelection(); - } else { - EditorModificationUtil.insertStringAtCaret(editor, "}"); - caretModel.moveToOffset(offset + 1); + @RequiredReadAction + private static void addSpecialTags(List result, PsiDocComment comment, String tagName) { + switch (tagName) { + case "author" -> result.add(tagName + " " + Platform.current().user().name()); + + case "param" -> { + PsiMethod psiMethod = PsiTreeUtil.getParentOfType(comment, PsiMethod.class); + if (psiMethod != null) { + PsiDocTag[] tags = comment.getTags(); + for (PsiParameter param : psiMethod.getParameterList().getParameters()) { + if (!JavaDocLocalInspection.isFound(tags, param)) { + result.add(tagName + " " + param.getName()); + } + } + } + } + + case "see" -> { + if (PsiTreeUtil.getParentOfType(comment, PsiMember.class) instanceof PsiClass psiClass) { + InheritanceUtil.processSupers( + psiClass, + false, + thisClass -> { + String name = thisClass.getQualifiedName(); + if (StringUtil.isNotEmpty(name) && !CommonClassNames.JAVA_LANG_OBJECT.equals(name)) { + result.add("see " + name); + } + return true; + } + ); + } + } + } } - } } - } - private static class MethodSignatureInsertHandler implements InsertHandler { - @Override - public void handleInsert(InsertionContext context, JavaMethodCallElement item) { - PsiDocumentManager.getInstance(context.getProject()).commitDocument(context.getEditor().getDocument()); - final Editor editor = context.getEditor(); - final PsiMethod method = item.getObject(); - - final PsiParameter[] parameters = method.getParameterList().getParameters(); - final StringBuilder buffer = new StringBuilder(); - - final CharSequence chars = editor.getDocument().getCharsSequence(); - int endOffset = editor.getCaretModel().getOffset(); - final Project project = context.getProject(); - int afterSharp = CharArrayUtil.shiftBackwardUntil(chars, endOffset - 1, "#") + 1; - int signatureOffset = afterSharp; - - PsiElement element = context.getFile().findElementAt(signatureOffset - 1); - final CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(context.getProject()); - PsiDocTag tag = PsiTreeUtil.getParentOfType(element, PsiDocTag.class); - if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR && tag != null) { - final PsiDocTagValue valueElement = tag.getValueElement(); - if (valueElement != null) { - endOffset = valueElement.getTextRange().getEndOffset(); - context.setTailOffset(endOffset); - } - } - editor.getDocument().deleteString(afterSharp, endOffset); - editor.getCaretModel().moveToOffset(signatureOffset); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - editor.getSelectionModel().removeSelection(); - buffer.append(method.getName()).append("("); - final int afterParenth = afterSharp + buffer.length(); - for (int i = 0; i < parameters.length; i++) { - final PsiType type = TypeConversionUtil.erasure(parameters[i].getType()); - buffer.append(type.getCanonicalText()); - - if (i < parameters.length - 1) { - buffer.append(","); - if (styleSettings.getCommonSettings(JavaLanguage.INSTANCE).SPACE_AFTER_COMMA) { - buffer.append(" "); - } - } - } - buffer.append(")"); - if (!(tag instanceof PsiInlineDocTag)) { - buffer.append(" "); - } else { - final int currentOffset = editor.getCaretModel().getOffset(); - if (chars.charAt(currentOffset) == '}') { - afterSharp++; - } else { - buffer.append("} "); + private static class InlineInsertHandler implements InsertHandler { + @Override + @RequiredUIAccess + public void handleInsert(InsertionContext context, LookupElement item) { + if (context.getCompletionChar() != Lookup.REPLACE_SELECT_CHAR) { + return; + } + + Project project = context.getProject(); + PsiDocumentManager.getInstance(project).commitAllDocuments(); + Editor editor = context.getEditor(); + CaretModel caretModel = editor.getCaretModel(); + int offset = caretModel.getOffset(); + PsiElement element = context.getFile().findElementAt(offset - 1); + PsiDocTag tag = PsiTreeUtil.getParentOfType(element, PsiDocTag.class); + assert tag != null; + + for (PsiElement child = tag.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child instanceof PsiDocToken token && token.getTokenType() == JavaDocTokenType.DOC_INLINE_TAG_END) { + return; + } + } + + String name = tag.getName(); + + CharSequence chars = editor.getDocument().getCharsSequence(); + int currentOffset = caretModel.getOffset(); + if (chars.charAt(currentOffset) == '}') { + caretModel.moveToOffset(offset + 1); + } + else if (chars.charAt(currentOffset + 1) == '}' && chars.charAt(currentOffset) == ' ') { + caretModel.moveToOffset(offset + 2); + } + else if (name.equals(LINK_TAG)) { + EditorModificationUtil.insertStringAtCaret(editor, " }"); + caretModel.moveToOffset(offset + 1); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + editor.getSelectionModel().removeSelection(); + } + else { + EditorModificationUtil.insertStringAtCaret(editor, "}"); + caretModel.moveToOffset(offset + 1); + } } - } - String insertString = buffer.toString(); - EditorModificationUtil.insertStringAtCaret(editor, insertString); - editor.getCaretModel().moveToOffset(afterSharp + buffer.length()); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); - - shortenReferences(project, editor, context, afterParenth); } - private static void shortenReferences(final Project project, final Editor editor, InsertionContext context, int offset) { - PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); - final PsiElement element = context.getFile().findElementAt(offset); - final PsiDocComment docComment = PsiTreeUtil.getParentOfType(element, PsiDocComment.class); - if (!JavaDocUtil.isInsidePackageInfo(docComment)) { - final PsiDocTagValue tagValue = PsiTreeUtil.getParentOfType(element, PsiDocTagValue.class); - if (tagValue != null) { - try { - JavaCodeStyleManager.getInstance(project).shortenClassReferences(tagValue); - } catch (IncorrectOperationException e) { - LOG.error(e); - } + private static class MethodSignatureInsertHandler implements InsertHandler { + @Override + @RequiredUIAccess + public void handleInsert(InsertionContext context, JavaMethodCallElement item) { + PsiDocumentManager.getInstance(context.getProject()).commitDocument(context.getEditor().getDocument()); + Editor editor = context.getEditor(); + PsiMethod method = item.getObject(); + + PsiParameter[] parameters = method.getParameterList().getParameters(); + StringBuilder buffer = new StringBuilder(); + + CharSequence chars = editor.getDocument().getCharsSequence(); + int endOffset = editor.getCaretModel().getOffset(); + Project project = context.getProject(); + int afterSharp = CharArrayUtil.shiftBackwardUntil(chars, endOffset - 1, "#") + 1; + int signatureOffset = afterSharp; + + PsiElement element = context.getFile().findElementAt(signatureOffset - 1); + CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(context.getProject()); + PsiDocTag tag = PsiTreeUtil.getParentOfType(element, PsiDocTag.class); + if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR && tag != null) { + PsiDocTagValue valueElement = tag.getValueElement(); + if (valueElement != null) { + endOffset = valueElement.getTextRange().getEndOffset(); + context.setTailOffset(endOffset); + } + } + editor.getDocument().deleteString(afterSharp, endOffset); + editor.getCaretModel().moveToOffset(signatureOffset); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + editor.getSelectionModel().removeSelection(); + buffer.append(method.getName()).append("("); + int afterParen = afterSharp + buffer.length(); + for (int i = 0; i < parameters.length; i++) { + PsiType type = TypeConversionUtil.erasure(parameters[i].getType()); + buffer.append(type.getCanonicalText()); + + if (i < parameters.length - 1) { + buffer.append(","); + if (styleSettings.getCommonSettings(JavaLanguage.INSTANCE).SPACE_AFTER_COMMA) { + buffer.append(" "); + } + } + } + buffer.append(")"); + if (!(tag instanceof PsiInlineDocTag)) { + buffer.append(" "); + } + else { + int currentOffset = editor.getCaretModel().getOffset(); + if (chars.charAt(currentOffset) == '}') { + afterSharp++; + } + else { + buffer.append("} "); + } + } + String insertString = buffer.toString(); + EditorModificationUtil.insertStringAtCaret(editor, insertString); + editor.getCaretModel().moveToOffset(afterSharp + buffer.length()); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + + shortenReferences(project, editor, context, afterParen); + } + + @RequiredReadAction + private static void shortenReferences(Project project, Editor editor, InsertionContext context, int offset) { + PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); + PsiElement element = context.getFile().findElementAt(offset); + PsiDocComment docComment = PsiTreeUtil.getParentOfType(element, PsiDocComment.class); + if (!JavaDocUtil.isInsidePackageInfo(docComment)) { + PsiDocTagValue tagValue = PsiTreeUtil.getParentOfType(element, PsiDocTagValue.class); + if (tagValue != null) { + try { + JavaCodeStyleManager.getInstance(project).shortenClassReferences(tagValue); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); + } } - PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments(); - } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavadocCompletionConfidence.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavadocCompletionConfidence.java index 8b89ddf640..88d10c596f 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavadocCompletionConfidence.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavadocCompletionConfidence.java @@ -16,6 +16,7 @@ package com.intellij.java.impl.codeInsight.completion; import com.intellij.java.language.JavaLanguage; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.language.Language; import consulo.language.editor.completion.CompletionConfidence; @@ -39,40 +40,41 @@ */ @ExtensionImpl(id = "javadoc", order = "before javaComments") public class JavadocCompletionConfidence extends CompletionConfidence { - - @Nonnull - @Override - public ThreeState shouldSkipAutopopup(@Nonnull PsiElement contextElement, @Nonnull PsiFile psiFile, int offset) { - if (psiElement().inside(PsiDocTag.class).accepts(contextElement)) { - if (findJavaReference(psiFile, offset - 1) != null) { - return ThreeState.NO; - } - if (PlatformPatterns.psiElement(JavaDocTokenType.DOC_TAG_NAME).accepts(contextElement)) { - return ThreeState.NO; - } - if (contextElement.textMatches("#")) { - return ThreeState.NO; - } + @Nonnull + @Override + @RequiredReadAction + public ThreeState shouldSkipAutopopup(@Nonnull PsiElement contextElement, @Nonnull PsiFile psiFile, int offset) { + if (psiElement().inside(PsiDocTag.class).accepts(contextElement)) { + if (findJavaReference(psiFile, offset - 1) != null) { + return ThreeState.NO; + } + if (PlatformPatterns.psiElement(JavaDocTokenType.DOC_TAG_NAME).accepts(contextElement)) { + return ThreeState.NO; + } + if (contextElement.textMatches("#")) { + return ThreeState.NO; + } + } + return super.shouldSkipAutopopup(contextElement, psiFile, offset); } - return super.shouldSkipAutopopup(contextElement, psiFile, offset); - } - @Nullable - private static PsiJavaReference findJavaReference(final PsiFile file, final int offset) { - PsiReference reference = file.findReferenceAt(offset); - if (reference instanceof PsiMultiReference) { - for (final PsiReference psiReference : ((PsiMultiReference) reference).getReferences()) { - if (psiReference instanceof PsiJavaReference) { - return (PsiJavaReference) psiReference; + @Nullable + @RequiredReadAction + private static PsiJavaReference findJavaReference(PsiFile file, int offset) { + PsiReference reference = file.findReferenceAt(offset); + if (reference instanceof PsiMultiReference multiRef) { + for (PsiReference psiReference : multiRef.getReferences()) { + if (psiReference instanceof PsiJavaReference javaRef) { + return javaRef; + } + } } - } + return reference instanceof PsiJavaReference javaRef ? javaRef : null; } - return reference instanceof PsiJavaReference ? (PsiJavaReference) reference : null; - } - @Nonnull - @Override - public Language getLanguage() { - return JavaLanguage.INSTANCE; - } + @Nonnull + @Override + public Language getLanguage() { + return JavaLanguage.INSTANCE; + } } From 8d7561fd94c202315fd4aa6861aa6f8782acd709 Mon Sep 17 00:00:00 2001 From: UNV Date: Sat, 8 Nov 2025 22:44:55 +0300 Subject: [PATCH 2/2] Inserting humble space after JavaDoc tag autocompletion (so "@auth| Foobar" is transformed into "@author| Foobar" without two spaces between tag and author name). --- .../codeInsight/completion/JavaDocCompletionContributor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaDocCompletionContributor.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaDocCompletionContributor.java index fc71eece36..2bbb73570a 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaDocCompletionContributor.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaDocCompletionContributor.java @@ -386,7 +386,7 @@ public void addCompletions( result.addElement(LookupElementDecorator.withInsertHandler(LookupElementBuilder.create(s), new InlineInsertHandler())); } else { - result.addElement(TailTypeDecorator.withTail(LookupElementBuilder.create(s), TailType.INSERT_SPACE)); + result.addElement(TailTypeDecorator.withTail(LookupElementBuilder.create(s), TailType.HUMBLE_SPACE_BEFORE_WORD)); } } result.stopHere(); // no word completions at this point