Java tutorial
/* * Copyright 2010-2015 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 org.jetbrains.kotlin.idea.parameterInfo; import com.google.common.collect.Iterables; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.lang.ASTNode; import com.intellij.lang.parameterInfo.*; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.ui.Gray; import com.intellij.ui.JBColor; import com.intellij.util.ArrayUtil; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade; import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper; import org.jetbrains.kotlin.idea.core.CorePackage; import org.jetbrains.kotlin.lexer.JetTokens; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilPackage; import org.jetbrains.kotlin.renderer.DescriptorRenderer; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode; import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude; import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter; import org.jetbrains.kotlin.resolve.scopes.JetScope; import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.checker.JetTypeChecker; import java.awt.*; import java.util.*; import java.util.List; public class JetFunctionParameterInfoHandler implements ParameterInfoHandlerWithTabActionSupport<JetValueArgumentList, Pair<? extends FunctionDescriptor, ResolutionFacade>, JetValueArgument> { public final static Color GREEN_BACKGROUND = new JBColor(new Color(231, 254, 234), Gray._100); @NotNull @Override public JetValueArgument[] getActualParameters(@NotNull JetValueArgumentList arguments) { List<JetValueArgument> argumentList = arguments.getArguments(); return argumentList.toArray(new JetValueArgument[argumentList.size()]); } @NotNull @Override public IElementType getActualParameterDelimiterType() { return JetTokens.COMMA; } @NotNull @Override public IElementType getActualParametersRBraceType() { return JetTokens.RBRACE; } @NotNull @Override public Set<Class> getArgumentListAllowedParentClasses() { return Collections.singleton((Class) JetCallElement.class); } @NotNull @Override public Set<? extends Class> getArgListStopSearchClasses() { return Collections.singleton(JetFunction.class); } @NotNull @Override public Class<JetValueArgumentList> getArgumentListClass() { return JetValueArgumentList.class; } @Override public boolean couldShowInLookup() { return true; } @Override public Object[] getParametersForLookup(LookupElement item, ParameterInfoContext context) { return ArrayUtil.EMPTY_OBJECT_ARRAY; //todo: ? } @Nullable @Override public Object[] getParametersForDocumentation(Pair<? extends FunctionDescriptor, ResolutionFacade> p, ParameterInfoContext context) { return ArrayUtil.EMPTY_OBJECT_ARRAY; //todo: ? } @Override public JetValueArgumentList findElementForParameterInfo(@NotNull CreateParameterInfoContext context) { return findCall(context); } @Override public void showParameterInfo(@NotNull JetValueArgumentList element, @NotNull CreateParameterInfoContext context) { context.showHint(element, element.getTextRange().getStartOffset(), this); } @Override public JetValueArgumentList findElementForUpdatingParameterInfo(@NotNull UpdateParameterInfoContext context) { return findCallAndUpdateContext(context); } @Override public void updateParameterInfo(@NotNull JetValueArgumentList argumentList, @NotNull UpdateParameterInfoContext context) { if (context.getParameterOwner() != argumentList) context.removeHint(); int offset = context.getOffset(); ASTNode child = argumentList.getNode().getFirstChildNode(); int i = 0; while (child != null && child.getStartOffset() < offset) { if (child.getElementType() == JetTokens.COMMA) ++i; child = child.getTreeNext(); } context.setCurrentParameter(i); } @Override public String getParameterCloseChars() { return ParameterInfoUtils.DEFAULT_PARAMETER_CLOSE_CHARS; } @Override public boolean tracksParameterIndex() { return true; } private static String renderParameter(ValueParameterDescriptor parameter, boolean named) { StringBuilder builder = new StringBuilder(); if (named) builder.append("["); if (parameter.getVarargElementType() != null) { builder.append("vararg "); } builder.append(parameter.getName()).append(": ") .append(DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(getActualParameterType(parameter))); if (parameter.hasDefaultValue()) { PsiElement parameterDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(parameter); builder.append(" = ").append(getDefaultExpressionString(parameterDeclaration)); } if (named) builder.append("]"); return builder.toString(); } private static String getDefaultExpressionString(@Nullable PsiElement parameterDeclaration) { if (parameterDeclaration instanceof JetParameter) { JetExpression defaultValue = ((JetParameter) parameterDeclaration).getDefaultValue(); if (defaultValue != null) { String defaultExpression = defaultValue.getText(); if (defaultExpression.length() <= 32) { return defaultExpression; } if (defaultValue instanceof JetConstantExpression || defaultValue instanceof JetStringTemplateExpression) { if (defaultExpression.startsWith("\"")) { return "\"...\""; } else if (defaultExpression.startsWith("\'")) { return "\'...\'"; } } } } return "..."; } private static JetType getActualParameterType(ValueParameterDescriptor descriptor) { JetType paramType = descriptor.getType(); if (descriptor.getVarargElementType() != null) paramType = descriptor.getVarargElementType(); return paramType; } @Override public void updateUI(Pair<? extends FunctionDescriptor, ResolutionFacade> itemToShow, @NotNull ParameterInfoUIContext context) { //todo: when we will have ability to pass Array as vararg, implement such feature here too? if (context == null || context.getParameterOwner() == null || !context.getParameterOwner().isValid()) { context.setUIComponentEnabled(false); return; } PsiElement parameterOwner = context.getParameterOwner(); if (!(parameterOwner instanceof JetValueArgumentList)) { context.setUIComponentEnabled(false); return; } JetValueArgumentList argumentList = (JetValueArgumentList) parameterOwner; FunctionDescriptor functionDescriptor = itemToShow.first; ResolutionFacade resolutionFacade = itemToShow.second; List<ValueParameterDescriptor> valueParameters = functionDescriptor.getValueParameters(); List<JetValueArgument> valueArguments = argumentList.getArguments(); int currentParameterIndex = context.getCurrentParameterIndex(); int boldStartOffset = -1; int boldEndOffset = -1; boolean isGrey = false; boolean isDeprecated = KotlinBuiltIns.isDeprecated(functionDescriptor); boolean[] usedIndexes = new boolean[valueParameters.size()]; Arrays.fill(usedIndexes, false); boolean namedMode = false; if (!isIndexValid(valueParameters, currentParameterIndex)) { isGrey = true; } StringBuilder builder = new StringBuilder(); PsiElement owner = context.getParameterOwner(); BindingContext bindingContext = resolutionFacade.analyze((JetElement) owner, BodyResolveMode.FULL); for (int i = 0; i < valueParameters.size(); ++i) { if (i != 0) { builder.append(", "); } boolean highlightParameter = i == currentParameterIndex || (!namedMode && i < currentParameterIndex && Iterables.getLast(valueParameters).getVarargElementType() != null); if (highlightParameter) { boldStartOffset = builder.length(); } if (!namedMode) { if (valueArguments.size() > i) { JetValueArgument argument = valueArguments.get(i); if (argument.isNamed()) { namedMode = true; } else { ValueParameterDescriptor param = valueParameters.get(i); builder.append(renderParameter(param, false)); if (i <= currentParameterIndex && !isArgumentTypeValid(bindingContext, argument, param)) { isGrey = true; } usedIndexes[i] = true; } } else { ValueParameterDescriptor param = valueParameters.get(i); builder.append(renderParameter(param, false)); } } if (namedMode) { boolean takeAnyArgument = true; if (valueArguments.size() > i) { JetValueArgument argument = valueArguments.get(i); if (argument.isNamed()) { for (int j = 0; j < valueParameters.size(); ++j) { JetSimpleNameExpression referenceExpression = argument.getArgumentName() .getReferenceExpression(); ValueParameterDescriptor param = valueParameters.get(j); if (referenceExpression != null && !usedIndexes[j] && param.getName().equals(referenceExpression.getReferencedNameAsName())) { takeAnyArgument = false; usedIndexes[j] = true; builder.append(renderParameter(param, true)); if (i < currentParameterIndex && !isArgumentTypeValid(bindingContext, argument, param)) { isGrey = true; } break; } } } } if (takeAnyArgument) { if (i < currentParameterIndex) { isGrey = true; } for (int j = 0; j < valueParameters.size(); ++j) { ValueParameterDescriptor param = valueParameters.get(j); if (!usedIndexes[j]) { usedIndexes[j] = true; builder.append(renderParameter(param, true)); break; } } } } if (highlightParameter) { boldEndOffset = builder.length(); } } if (valueParameters.size() == 0) { builder.append(CodeInsightBundle.message("parameter.info.no.parameters")); } assert !builder.toString() .isEmpty() : "A message about 'no parameters' or some parameters should be present: " + functionDescriptor; Color color = isResolvedToDescriptor(argumentList, functionDescriptor, bindingContext) ? GREEN_BACKGROUND : context.getDefaultParameterColor(); context.setupUIComponentPresentation(builder.toString(), boldStartOffset, boldEndOffset, isGrey, isDeprecated, false, color); } private static boolean isArgumentTypeValid(BindingContext bindingContext, JetValueArgument argument, ValueParameterDescriptor param) { if (argument.getArgumentExpression() != null) { JetType paramType = getActualParameterType(param); JetType exprType = bindingContext.getType(argument.getArgumentExpression()); return exprType == null || JetTypeChecker.DEFAULT.isSubtypeOf(exprType, paramType); } return false; } private static boolean isIndexValid(List<ValueParameterDescriptor> valueParameters, int index) { // Index is within range of parameters or last parameter is vararg return index < valueParameters.size() || (valueParameters.size() > 0 && Iterables.getLast(valueParameters).getVarargElementType() != null); } private static Boolean isResolvedToDescriptor(JetValueArgumentList argumentList, FunctionDescriptor functionDescriptor, BindingContext bindingContext) { JetSimpleNameExpression callNameExpression = getCallSimpleNameExpression(argumentList); if (callNameExpression != null) { DeclarationDescriptor declarationDescriptor = bindingContext.get(BindingContext.REFERENCE_TARGET, callNameExpression); if (declarationDescriptor != null) { if (declarationDescriptor == functionDescriptor) { return true; } } } return false; } private static JetValueArgumentList findCall(CreateParameterInfoContext context) { //todo: calls to this constructors, when we will have auxiliary constructors PsiFile file = context.getFile(); if (!(file instanceof JetFile)) { return null; } JetValueArgumentList argumentList = PsiTreeUtil.getParentOfType(file.findElementAt(context.getOffset()), JetValueArgumentList.class); if (argumentList == null) { return null; } final JetSimpleNameExpression callNameExpression = getCallSimpleNameExpression(argumentList); if (callNameExpression == null) { return null; } PsiReference[] references = callNameExpression.getReferences(); if (references.length == 0) { return null; } ResolutionFacade resolutionFacade = ResolvePackage .getResolutionFacade(callNameExpression.getContainingJetFile()); final BindingContext bindingContext = resolutionFacade.analyze(callNameExpression, BodyResolveMode.FULL); ModuleDescriptor moduleDescriptor = resolutionFacade.findModuleDescriptor(callNameExpression); JetScope scope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, callNameExpression); final DeclarationDescriptor placeDescriptor; if (scope != null) { placeDescriptor = scope.getContainingDeclaration(); } else { placeDescriptor = null; } Function1<DeclarationDescriptor, Boolean> visibilityFilter = new Function1<DeclarationDescriptor, Boolean>() { @Override public Boolean invoke(DeclarationDescriptor descriptor) { if (placeDescriptor == null) return true; if (!(descriptor instanceof DeclarationDescriptorWithVisibility)) return true; return CorePackage.isVisible((DeclarationDescriptorWithVisibility) descriptor, placeDescriptor, bindingContext, callNameExpression); } }; final Name refName = callNameExpression.getReferencedNameAsName(); Function1<Name, Boolean> nameFilter = new Function1<Name, Boolean>() { @Override public Boolean invoke(Name name) { return name.equals(refName); } }; Collection<DeclarationDescriptor> variants = new ReferenceVariantsHelper(bindingContext, moduleDescriptor, file.getProject(), visibilityFilter).getReferenceVariants( callNameExpression, new DescriptorKindFilter( DescriptorKindFilter.FUNCTIONS_MASK | DescriptorKindFilter.CLASSIFIERS_MASK, Collections.<DescriptorKindExclude>emptyList()), nameFilter, false, false); Collection<Pair<? extends DeclarationDescriptor, ResolutionFacade>> itemsToShow = new ArrayList<Pair<? extends DeclarationDescriptor, ResolutionFacade>>(); for (DeclarationDescriptor variant : variants) { if (variant instanceof FunctionDescriptor) { //todo: renamed functions? itemsToShow.add(Pair.create((FunctionDescriptor) variant, resolutionFacade)); } else if (variant instanceof ClassDescriptor) { //todo: renamed classes? for (ConstructorDescriptor constructorDescriptor : ((ClassDescriptor) variant).getConstructors()) { itemsToShow.add(Pair.create(constructorDescriptor, resolutionFacade)); } } } context.setItemsToShow(ArrayUtil.toObjectArray(itemsToShow)); return argumentList; } @Nullable private static JetSimpleNameExpression getCallSimpleNameExpression(JetValueArgumentList argumentList) { PsiElement argumentListParent = argumentList.getParent(); return (argumentListParent instanceof JetCallElement) ? PsiUtilPackage.getCallNameExpression((JetCallElement) argumentListParent) : null; } private static JetValueArgumentList findCallAndUpdateContext(UpdateParameterInfoContext context) { PsiFile file = context.getFile(); PsiElement element = file.findElementAt(context.getOffset()); if (element == null) return null; PsiElement parent = element.getParent(); while (parent != null && !(parent instanceof JetValueArgumentList)) { element = element.getParent(); parent = parent.getParent(); } if (parent == null) return null; JetValueArgumentList argumentList = (JetValueArgumentList) parent; if (element instanceof JetValueArgument) { JetValueArgument arg = (JetValueArgument) element; int i = argumentList.getArguments().indexOf(arg); context.setCurrentParameter(i); context.setHighlightedParameter(arg); } return argumentList; } }