/* * 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 org.jetbrains.plugins.groovy.lang.completion; import com.intellij.codeInsight.CodeInsightUtilCore; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.template.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.compiled.ClsMethodImpl; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.completion.closureParameters.ClosureParameterInfo; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.SupertypeConstraint; import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.TypeConstraint; import org.jetbrains.plugins.groovy.template.expressions.ChooseTypeExpression; import org.jetbrains.plugins.groovy.template.expressions.ParameterNameExpression; import java.util.List; /** * @author Max Medvedev */ public abstract class ClosureCompleter { private static final ExtensionPointName<ClosureCompleter> EP_NAME = ExtensionPointName.create("org.intellij.groovy.closureCompleter"); @Nullable protected abstract List<ClosureParameterInfo> getParameterInfos(InsertionContext context, PsiMethod method, PsiSubstitutor substitutor, PsiElement place); public static boolean runClosureCompletion(InsertionContext context, PsiMethod method, PsiSubstitutor substitutor, Document document, int offset, PsiElement parent) { for (ClosureCompleter completer : EP_NAME.getExtensions()) { final List<ClosureParameterInfo> parameterInfos = completer.getParameterInfos(context, method, substitutor, parent); if (parameterInfos != null) { runClosureTemplate(context, document, offset, substitutor, method, parameterInfos); return true; } } return false; } private static boolean runClosureTemplate(InsertionContext context, Document document, int offset, PsiSubstitutor substitutor, PsiMethod method, final List<ClosureParameterInfo> parameters) { document.insertString(offset, "{\n}"); PsiDocumentManager.getInstance(context.getProject()).commitDocument(document); final GrClosableBlock closure = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), offset + 1, GrClosableBlock.class, false); if (closure == null) return false; runTemplate(parameters, closure, substitutor, method, context.getProject(), context.getEditor()); return true; } public static void runTemplate(List<ClosureParameterInfo> parameters, GrClosableBlock block, PsiSubstitutor substitutor, PsiMethod method, final Project project, final Editor editor) { if (method instanceof ClsMethodImpl) method = ((ClsMethodImpl)method).getSourceMirrorMethod(); assert block.getArrow() == null; if (parameters.isEmpty()) return; StringBuilder buffer = new StringBuilder(); buffer.append("{"); List<PsiType> paramTypes = ContainerUtil.newArrayList(); for (ClosureParameterInfo parameter : parameters) { final String type = parameter.getType(); final String name = parameter.getName(); if (type != null) { final PsiType fromText = JavaPsiFacade.getElementFactory(project).createTypeFromText(type, method); final PsiType substituted = substitutor.substitute(fromText); paramTypes.add(substituted); buffer.append(substituted.getCanonicalText()).append(" "); } else { buffer.append("def "); } buffer.append(name); buffer.append(", "); } buffer.replace(buffer.length() - 2, buffer.length(), " ->}"); final Document document = editor.getDocument(); final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(document); assert file != null; final GrClosableBlock closure = GroovyPsiElementFactory.getInstance(project).createClosureFromText(buffer.toString()); final GrClosableBlock templateClosure = (GrClosableBlock)block.replaceWithExpression(closure, false); final TemplateBuilderImpl builder = new TemplateBuilderImpl(templateClosure); int i = 0; for (GrParameter p : templateClosure.getParameters()) { final GrTypeElement typeElement = p.getTypeElementGroovy(); final PsiElement nameIdentifier = p.getNameIdentifierGroovy(); if (typeElement != null) { final TypeConstraint[] typeConstraints = {SupertypeConstraint.create(paramTypes.get(i++))}; final ChooseTypeExpression expression = new ChooseTypeExpression(typeConstraints, PsiManager.getInstance(project), nameIdentifier.getResolveScope()); builder.replaceElement(typeElement, expression); } else { final ChooseTypeExpression expression = new ChooseTypeExpression(TypeConstraint.EMPTY_ARRAY, PsiManager.getInstance(project), nameIdentifier.getResolveScope()); builder.replaceElement(p.getModifierList(), expression); } builder.replaceElement(nameIdentifier, new ParameterNameExpression(nameIdentifier.getText())); } final GrClosableBlock afterPostprocess = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(templateClosure); final Template template = builder.buildTemplate(); TextRange range = afterPostprocess.getTextRange(); document.deleteString(range.getStartOffset(), range.getEndOffset()); TemplateEditingListener templateListener = new TemplateEditingAdapter() { @Override public void templateFinished(Template template, boolean brokenOff) { ApplicationManager.getApplication().runWriteAction(() -> { PsiDocumentManager.getInstance(project).commitDocument(document); final CaretModel caretModel = editor.getCaretModel(); final int offset = caretModel.getOffset(); GrClosableBlock block1 = PsiTreeUtil.findElementOfClassAtOffset(file, offset - 1, GrClosableBlock.class, false); if (block1 != null) { final PsiElement arrow = block1.getArrow(); if (arrow != null) { caretModel.moveToOffset(arrow.getTextRange().getEndOffset()); } // fix space before closure lbrace final TextRange range1 = block1.getTextRange(); CodeStyleManager.getInstance(project).reformatRange(block1.getParent(), range1.getStartOffset() - 1, range1.getEndOffset(), true); } }); } }; TemplateManager manager = TemplateManager.getInstance(project); manager.startTemplate(editor, template, templateListener); } }