/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.parameterinfo; import com.google.common.collect.Lists; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.lang.parameterInfo.*; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import gw.lang.reflect.*; import gw.lang.reflect.module.IModule; import gw.plugin.ij.lang.parser.GosuBlockInvocationImpl; import gw.plugin.ij.lang.psi.IGosuPsiElement; import gw.plugin.ij.lang.psi.api.IGosuResolveResult; import gw.plugin.ij.lang.psi.impl.GosuPsiClassReferenceType; import gw.plugin.ij.lang.psi.impl.GosuPsiSubstitutor; import gw.plugin.ij.lang.psi.impl.expressions.GosuAnnotationExpressionImpl; import gw.plugin.ij.lang.psi.impl.statements.params.GosuParameterImpl; import gw.plugin.ij.util.ExceptionUtil; import gw.plugin.ij.util.GosuBundle; import gw.plugin.ij.util.GosuModuleUtil; import gw.plugin.ij.util.InjectedElementEditor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; import static com.google.common.base.Objects.firstNonNull; public class GosuParameterInfoHandler implements ParameterInfoHandler<PsiElement, IGosuResolveResult> { @Override public boolean couldShowInLookup() { return true; } @Override public Object[] getParametersForLookup(LookupElement item, ParameterInfoContext context) { return new Object[0]; } @Override public Object[] getParametersForDocumentation(IGosuResolveResult p, ParameterInfoContext context) { return new Object[0]; } @Override public PsiElement findElementForParameterInfo(@NotNull final CreateParameterInfoContext context) { final IModule module = firstNonNull(GosuModuleUtil.findModuleForPsiElement(context.getFile()), GosuModuleUtil.getGlobalModule(context.getFile().getProject())); TypeSystem.pushModule(module); try { final PsiElement owner = getPsiAtLocation(context.getOffset(), InjectedElementEditor.getOriginalFile(context.getFile())); if (isApplicable(owner)) { final ParameterInfoUtil.ParamInfoContext contextForParamInfo = ParameterInfoUtil.getContextForParamInfo(PsiTreeUtil.getParentOfType(InjectedElementEditor.getOriginalElement(owner), owner.getClass(), false)); if (contextForParamInfo != null) { final List<IGosuResolveResult> signatures = Lists.newArrayList(); contextForParamInfo.addSignatures(InjectedElementEditor.getOriginalFile(context.getFile()), signatures); context.setItemsToShow(signatures.toArray(new IGosuResolveResult[signatures.size()])); } return owner; } else { return null; } } finally { TypeSystem.popModule(module); } } private boolean isApplicable(PsiElement owner) { return owner != null && !(owner instanceof GosuAnnotationExpressionImpl) && !(owner instanceof GosuBlockInvocationImpl); } private PsiElement getPsiAtLocation(int offset, PsiFile file) { final PsiElement element = ParameterInfoUtil.findParamOwnerAtOffset(offset, file); return element != null ? element : ParameterInfoUtil.findParamOwnerAtOffset(offset - 1, file); } @Override public void showParameterInfo(@NotNull PsiElement element, @NotNull CreateParameterInfoContext context) { String error = ParameterInfoUtil.verify(context); if (error != null) { ExceptionUtil.showNonFatalError(GosuBundle.message("parameter.info.problem"), error); } else { context.showHint(element, element.getTextRange().getStartOffset(), this); } } @Override public PsiElement findElementForUpdatingParameterInfo(@NotNull UpdateParameterInfoContext context) { return getPsiAtLocation(context.getOffset(), context.getFile()); } @Override public void updateParameterInfo(@NotNull final PsiElement o, @NotNull final UpdateParameterInfoContext context) { //we have to remove this check because it removes hint in case of injection // if (context.getParameterOwner() != o) { // context.removeHint(); // return; // } IModule module = GosuModuleUtil.findModuleForPsiElement(o); TypeSystem.pushModule(module); try { ParameterInfoUtil.ParamInfoContext contextForParamInfo = ParameterInfoUtil.getContextForParamInfo(o); if (contextForParamInfo != null) { ArrayList<IGosuResolveResult> results = Lists.newArrayList(); IGosuResolveResult highlighted = contextForParamInfo.addSignatures(context.getFile(), results); if (results.size() > 1) { context.setHighlightedParameter(highlighted); } } context.setCurrentParameter(ParameterInfoUtil.getCurrentParameterIndex(o, context.getOffset())); } finally { TypeSystem.popModule(module); } } @Override public String getParameterCloseChars() { return ParameterInfoUtils.DEFAULT_PARAMETER_CLOSE_CHARS; } @Override public boolean tracksParameterIndex() { return true; } @Override public void updateUI(@NotNull IGosuResolveResult resolveResult, @NotNull ParameterInfoUIContext context) { PsiElement element = resolveResult.getElement(); if (element instanceof PsiMethod) { PsiMethod method = (PsiMethod) element; PsiSubstitutor substitutor = resolveResult.getSubstitutor(); updateMethodUI(context, method, substitutor); } else if (element != null && element.getLanguage().getID().equals("Properties")) { updatePropertyUI(context, element); } else { if (context != null) { IFeatureInfo featureInfo = resolveResult.getFeatureInfo(); if (featureInfo instanceof IHasParameterInfos) { updateUIByFeatureInfo(context, (IHasParameterInfos) featureInfo); } } } } public static void updateUIByFeatureInfo(ParameterInfoUIContext context, IHasParameterInfos infos) { ITypeLoader typeLoader = infos.getOwnersType().getTypeLoader(); IModule module = typeLoader != null ? typeLoader.getModule() : TypeSystem.getGlobalModule(); TypeSystem.pushModule( module ); try { StringBuilder info = new StringBuilder(); CodeInsightSettings settings = CodeInsightSettings.getInstance(); boolean showMethodName = settings.SHOW_FULL_SIGNATURES_IN_PARAMETER_INFO; if (showMethodName) { info.append(infos.getDisplayName()).append("("); } IParameterInfo[] params = infos.getParameters(); int highlightStartOffset = -1; int highlightEndOffset = -1; if (params.length == 0) { info.append("<no parameters>"); } else { final int currentParameter = context.getCurrentParameterIndex(); for (int i = 0; i < params.length; ++i) { int startOffset = info.length(); IParameterInfo pi = params[i]; info.append(pi.getName()); IType featureType = pi.getFeatureType(); if (featureType != null) { info.append(": ").append(getStrType(featureType)); } int endOffset = info.length(); if (i < params.length - 1) { info.append(", "); } if (context.isUIComponentEnabled() && i == currentParameter) { highlightStartOffset = startOffset; highlightEndOffset = endOffset; } } } if (showMethodName) { info.append(")"); if (infos instanceof IMethodInfo) { IType returnType = ((IMethodInfo) infos).getReturnType(); if (returnType != null) { info.append(" : ").append(getStrType(returnType)); } } } context.setupUIComponentPresentation( info.toString(), highlightStartOffset, highlightEndOffset, !context.isUIComponentEnabled(), infos.isDeprecated(), false, context.getDefaultParameterColor()); } finally { TypeSystem.popModule( module ); } } public static void updateMethodUI(@NotNull ParameterInfoUIContext context, @NotNull PsiMethod method, @Nullable PsiSubstitutor substitutor) { IModule module = GosuModuleUtil.findModuleForPsiElement(method); if (module == null) { return; } CodeInsightSettings settings = CodeInsightSettings.getInstance(); TypeSystem.pushModule(module); try { StringBuilder buffer = new StringBuilder(); if (settings.SHOW_FULL_SIGNATURES_IN_PARAMETER_INFO) { buffer.append(method.getName()); buffer.append("("); } final int currentParameter = context.getCurrentParameterIndex(); PsiParameter[] parms = method.getParameterList().getParameters(); int highlightStartOffset = -1; int highlightEndOffset = -1; if (parms != null && parms.length > 0) { for (int j = 0; j < parms.length; j++) { PsiParameter parm = parms[j]; int startOffset = buffer.length(); String name = parm.getName(); if (name != null) { buffer.append(name); buffer.append(": "); } PsiType parmType = parm.getType(); if( substitutor instanceof GosuPsiSubstitutor && parmType instanceof GosuPsiClassReferenceType && parmType.getCanonicalText().startsWith("block")) { String p = parmType.getCanonicalText(); Map<String,IType> typeVarMap = ((GosuPsiSubstitutor) substitutor).getTypeVarMap(); for(String var : typeVarMap.keySet()){ p = p.replaceAll(var, getStrType(typeVarMap.get(var))); } buffer.append(p); } else { PsiType type = substitutor != null ? substitutor.substitute(parmType) : parmType; buffer.append(type.getPresentableText()); } if (parm instanceof GosuParameterImpl) { GosuParameterImpl gosuParam = (GosuParameterImpl) parm; IGosuPsiElement defaultInitializer = gosuParam.getDefaultInitializer(); if (defaultInitializer != null) { buffer.append(" = " + defaultInitializer.getText()); } } int endOffset = buffer.length(); if (j < parms.length - 1) { buffer.append(", "); } if (context.isUIComponentEnabled() && j == currentParameter) { highlightStartOffset = startOffset; highlightEndOffset = endOffset; } } } else { buffer.append(CodeInsightBundle.message("parameter.info.no.parameters")); } if (settings.SHOW_FULL_SIGNATURES_IN_PARAMETER_INFO) { buffer.append(")"); PsiType returnType = method.getReturnType(); if (returnType != null && returnType != PsiType.VOID) { buffer.append(" : "); buffer.append(returnType.getPresentableText()); } } if (context != null && method != null) { context.setupUIComponentPresentation( buffer.toString(), highlightStartOffset, highlightEndOffset, !context.isUIComponentEnabled(), method.isDeprecated(), false, context.getDefaultParameterColor() ); } } finally { TypeSystem.popModule(module); } } private static String getStrType(IType iType) { String s = iType.toString(); int begin = s.lastIndexOf('.'); return s.substring(begin + 1); } public static void updatePropertyUI(@NotNull ParameterInfoUIContext context, @NotNull PsiElement property) { IModule module = GosuModuleUtil.findModuleForPsiElement(property); TypeSystem.pushModule(module); try { StringBuilder buffer = new StringBuilder(); final int currentParameter = context.getCurrentParameterIndex(); int highlightStartOffset = -1; int highlightEndOffset = -1; int paramsCount = findParamCount(property); if (paramsCount > 0) { for (int j = 0; j < paramsCount; j++) { int startOffset = buffer.length(); buffer.append("{").append(j).append("}").append(": ").append("String"); int endOffset = buffer.length(); if (j < paramsCount - 1) { buffer.append(", "); } if (context.isUIComponentEnabled() && j == currentParameter) { highlightStartOffset = startOffset; highlightEndOffset = endOffset; } } } else { buffer.append(CodeInsightBundle.message("parameter.info.no.parameters")); } context.setupUIComponentPresentation( buffer.toString(), highlightStartOffset, highlightEndOffset, !context.isUIComponentEnabled(), false, false, context.getDefaultParameterColor() ); } finally { TypeSystem.popModule(module); } } private static int findParamCount(PsiElement property) { String propertyText = property.getText(); boolean insideBrackets = false; StringBuilder text = new StringBuilder(); int paramIndex = 0; for (int i = 0; i < propertyText.length(); i++) { char c = propertyText.charAt(i); switch (c) { case '{': insideBrackets = true; text.setLength(0); break; case '}': if (insideBrackets) { Integer index = getInteger(text.toString().trim()); if (index != null) { paramIndex = index; } text.setLength(0); insideBrackets = false; } break; default: text.append(c); } } return paramIndex + 1; } private static Integer getInteger(String text) { try { return Integer.parseInt(text); } catch (NumberFormatException e) { return null; } } }