blob: 7e7e0955b5605f65e0c53d1af02d121f44c0b7ee [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.completion.scope.CompletionElement;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.editorActions.wordSelection.DocTagSelectioner;
import com.intellij.codeInsight.lookup.*;
import com.intellij.codeInspection.InspectionProfile;
import com.intellij.codeInspection.SuppressionUtil;
import com.intellij.codeInspection.javaDoc.JavaDocLocalInspection;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.PsiJavaPatterns;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.filters.TrueFilter;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
import com.intellij.psi.javadoc.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
/**
* Created by IntelliJ IDEA.
* User: ik
* Date: 05.03.2003
* Time: 21:40:11
* To change this template use Options | File Templates.
*/
public class JavaDocCompletionContributor extends CompletionContributor {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaDocCompletionContributor");
private static final @NonNls String VALUE_TAG = "value";
private static final @NonNls String LINK_TAG = "link";
public JavaDocCompletionContributor() {
extend(CompletionType.BASIC, PsiJavaPatterns.psiElement(JavaDocTokenType.DOC_TAG_NAME), new TagChooser());
extend(CompletionType.BASIC, PsiJavaPatterns.psiElement().inside(PsiDocComment.class), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext context, @NotNull 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();
final JavaCompletionProcessor processor = new JavaCompletionProcessor(position, TrueFilter.INSTANCE, JavaCompletionProcessor.Options.CHECK_NOTHING, Condition.TRUE);
((PsiJavaReference) ref).processVariants(processor);
for (final CompletionElement _item : processor.getResults()) {
final Object element = _item.getElement();
LookupItem item = createLookupItem(element);
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;
}
item.putUserData(LookupItem.FORCE_SHOW_SIGNATURE_ATTR, Boolean.TRUE);
if (isArg) {
item.setAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
}
result.addElement(item);
}
JavaCompletionContributor.addAllClasses(parameters, result, new InheritorsHolder(position, result));
}
}
private LookupItem createLookupItem(final Object element) {
if (element instanceof PsiMethod) {
return new JavaMethodCallElement((PsiMethod)element) {
@Override
public void handleInsert(InsertionContext context) {
new MethodSignatureInsertHandler().handleInsert(context, this);
}
};
}
if (element instanceof PsiClass) {
JavaPsiClassReferenceElement classElement = new JavaPsiClassReferenceElement((PsiClass)element);
classElement.setInsertHandler(JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER);
return classElement;
}
return (LookupItem)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;
}
}
}
return null;
}
@Override
public void fillCompletionVariants(final CompletionParameters parameters, 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);
}
return;
}
super.fillCompletionVariants(parameters, result);
}
private static void suggestSimilarParameterDescriptions(CompletionResultSet result, PsiElement position, final PsiParameter param) {
final Set<String> descriptions = ContainerUtil.newHashSet();
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);
}
}
super.visitElement(element);
}
});
for (String description : descriptions) {
result.addElement(LookupElementBuilder.create(description).withInsertHandler(new InsertHandler<LookupElement>() {
@Override
public void handleInsert(InsertionContext context, LookupElement item) {
if (context.getCompletionChar() != Lookup.REPLACE_SELECT_CHAR) return;
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);
}
}
}
}));
}
}
private static class TagChooser extends CompletionProvider<CompletionParameters> {
@Override
protected void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) {
final List<String> ret = new ArrayList<String>();
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 (PsiPackage.PACKAGE_INFO_FILE.equals(file.getName())) {
final String packageName = file.getPackageName();
parent = JavaPsiFacade.getInstance(position.getProject()).findPackage(packageName);
}
}
final 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);
final StringTokenizer tokenizer = new StringTokenizer(inspection.myAdditionalJavadocTags, ", ");
while (tokenizer.hasMoreTokens()) {
ret.add(tokenizer.nextToken());
}
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));
}
}
result.stopHere(); // no word completions at this point
}
private static void addSpecialTags(final List<String> result, PsiDocComment comment, String tagName) {
if ("author".equals(tagName)) {
result.add(tagName + " " + SystemProperties.getUserName());
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());
}
}
}
return;
}
if ("see".equals(tagName)) {
PsiMember member = PsiTreeUtil.getParentOfType(comment, PsiMember.class);
if (member instanceof PsiClass) {
InheritanceUtil.processSupers((PsiClass)member, false, new Processor<PsiClass>() {
@Override
public boolean process(PsiClass psiClass) {
String name = psiClass.getQualifiedName();
if (StringUtil.isNotEmpty(name) && !CommonClassNames.JAVA_LANG_OBJECT.equals(name)) {
result.add("see " + name);
}
return true;
}
});
}
}
}
}
private static class InlineInsertHandler implements InsertHandler<LookupElement> {
@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);
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;
}
}
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);
}
}
}
}
private static class MethodSignatureInsertHandler implements InsertHandler<LookupItem> {
@Override
public void handleInsert(InsertionContext context, LookupItem item) {
if (!(item.getObject() instanceof PsiMethod)) {
return;
}
PsiDocumentManager.getInstance(context.getProject()).commitDocument(context.getEditor().getDocument());
final Editor editor = context.getEditor();
final PsiMethod method = (PsiMethod)item.getObject();
final PsiParameter[] parameters = method.getParameterList().getParameters();
final StringBuffer buffer = new StringBuffer();
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(element.getProject());
PsiDocTag tag = PsiTreeUtil.getParentOfType(element, PsiDocTag.class);
if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) {
final PsiDocTagValue valueElement = tag.getValueElement();
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() + "(");
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.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("} ");
}
}
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 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();
}
}
}