blob: 64e2c6675990f2d08d818e2399d9fc6cfe6f3805 [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 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(new Runnable() {
@Override
public void run() {
PsiDocumentManager.getInstance(project).commitDocument(document);
final CaretModel caretModel = editor.getCaretModel();
final int offset = caretModel.getOffset();
GrClosableBlock block = PsiTreeUtil.findElementOfClassAtOffset(file, offset - 1, GrClosableBlock.class, false);
if (block != null) {
final PsiElement arrow = block.getArrow();
if (arrow != null) {
caretModel.moveToOffset(arrow.getTextRange().getEndOffset());
}
// fix space before closure lbrace
final TextRange range = block.getTextRange();
CodeStyleManager.getInstance(project).reformatRange(block.getParent(), range.getStartOffset() - 1, range.getEndOffset(), true);
}
}
});
}
};
TemplateManager manager = TemplateManager.getInstance(project);
manager.startTemplate(editor, template, templateListener);
}
}