Create from usage: Added file templates.

This commit is contained in:
Jack Zhou
2013-04-08 20:19:14 -04:00
parent 28ac582fde
commit 1e573235e8
4 changed files with 95 additions and 7 deletions
@@ -272,4 +272,9 @@ public class JetPsiFactory {
public static JetExpressionCodeFragment createExpressionCodeFragment(Project project, String text, PsiElement context) {
return new JetExpressionCodeFragmentImpl(project, "fragment.kt", text, context);
}
public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) {
JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}");
return func.getBodyExpression();
}
}
@@ -0,0 +1 @@
//To change body of created methods use File | Settings | File Templates.
@@ -0,0 +1,38 @@
<html>
<body>
<table border="0" cellpadding="2" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">This is a built-in template used for filling the body of a Kotlin method
each time it is generated by the program, e.g. when using the <b>Create Method from Usage</b> intention action.<br>
The template is editable. Along with Kotlin expressions and comments, you can also use the predefined variables
that will be then expanded into the corresponding values.</font>
</td>
</tr>
</table>
<table border="0" cellpadding="5" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111">
<tr>
<td colspan="3"><font face="verdana" size="-1">Predefined variables will take the following values:</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2" color="#7F0000"><b><i>${RETURN_TYPE}</i></b></font></nobr></td>
<td width="10">&nbsp;</td>
<td valign="top"><font face="verdana" size="-1">a return type of a created method</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2" color="#7F0000"><b><i>${METHOD_NAME}</i></b></font></nobr></td>
<td width="10">&nbsp;</td>
<td valign="top"><font face="verdana" size="-1">name of the created method</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2" color="#7F0000"><b><i>${CLASS_NAME}</i></b></font></nobr></td>
<td width="10">&nbsp;</td>
<td valign="top"><font face="verdana" size="-1">qualified name of the class where method is created</font></td>
</tr>
<tr>
<td valign="top"><nobr><font face="verdana" size="-2" color="#7F0000"><b><i>${SIMPLE_CLASS_NAME}</i></b></font></nobr></td>
<td width="10">&nbsp;</td>
<td valign="top"><font face="verdana" size="-1">non-qualified name of the class where method is implemented</font></td>
</tr>
</table>
</body>
</html>
@@ -22,8 +22,11 @@ import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.template.*;
import com.intellij.codeInsight.template.impl.TemplateImpl;
import com.intellij.codeInsight.template.impl.Variable;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
@@ -33,12 +36,14 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.diagnostics.Diagnostic;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.scopes.JetScope;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.TypeProjection;
@@ -55,6 +60,7 @@ import java.util.*;
public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
private static final String TYPE_PARAMETER_LIST_VARIABLE_NAME = "typeParameterList";
private static final String TEMPLATE_FROM_USAGE_METHOD_BODY = "New Kotlin Method Body.kt";
/**
* Represents a concrete type or a set of types yet to be inferred from an expression.
@@ -360,7 +366,7 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
public void invoke(@NotNull final Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
assert file instanceof JetFile;
JetFile jetFile = (JetFile) file;
BindingContext context = AnalyzerFacadeWithCache.analyzeFileWithCache(jetFile).getBindingContext();
@@ -376,7 +382,8 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
ownerType = possibleOwnerTypes[0];
}
ClassifierDescriptor ownerTypeDescriptor = ownerType.getConstructor().getDeclarationDescriptor();
final ClassifierDescriptor ownerTypeDescriptor = ownerType.getConstructor().getDeclarationDescriptor();
assert ownerTypeDescriptor != null;
PsiElement typeDeclaration = DescriptorToDeclarationUtil.getDeclaration(jetFile, ownerTypeDescriptor, context);
JetClass klass = (JetClass) typeDeclaration;
@@ -387,24 +394,28 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
}
String parametersString = StringUtil.join(parameterStrings,", ");
boolean isUnit = returnType.isType() && isUnit(returnType.getType());
final boolean isUnit = returnType.isType() && isUnit(returnType.getType());
String returnTypeString = isUnit ? "" : ": Any";
String ownerTypeString;
String methodText;
JetNamedFunction func;
PsiElement owner;
final JetFile containingFile;
boolean isExtension = !klass.isWritable();
if (isExtension) { // create as extension function
ownerTypeString = renderType(ownerType);
methodText = String.format("fun %s.%s(%s)%s { }", ownerTypeString, methodName, parametersString, returnTypeString);
func = JetPsiFactory.createFunction(project, methodText);
owner = file;
owner = containingFile = jetFile;
func = (JetNamedFunction) file.add(func);
} else { // create as method
methodText = String.format("fun %s(%s)%s { }", methodName, parametersString, returnTypeString);
func = JetPsiFactory.createFunction(project, methodText);
owner = klass;
PsiFile classContainingFile = klass.getContainingFile();
assert classContainingFile instanceof JetFile;
containingFile = (JetFile) classContainingFile;
JetClassBody classBody = klass.getBody();
assert classBody != null;
PsiElement rBrace = classBody.getRBrace();
@@ -439,7 +450,7 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
TypeParameterListExpression expression = setupTypeParameterListTemplate(builder, func, ownerType, parameterTypeExpressions, scope);
// the template built by TemplateBuilderImpl is ordered by element position, but we want types to be first, so hack it
TemplateImpl template = (TemplateImpl) builder.buildInlineTemplate();
final TemplateImpl template = (TemplateImpl) builder.buildInlineTemplate();
ArrayList<Variable> variables = template.getVariables();
for (int i = 0; i < parameters.size(); i++) {
Collections.swap(variables, i * 2, i * 2 + 1);
@@ -451,14 +462,47 @@ public class CreateMethodFromUsageFix extends CreateFromUsageFixBase {
// run the template
TemplateManager.getInstance(project).startTemplate(editor, template, new TemplateEditingAdapter() {
@Override
public void templateFinished(Template template, boolean brokenOff) {
// TODO: file templates
public void templateFinished(Template _, boolean brokenOff) {
int offset = template.getSegmentOffset(0);
JetNamedFunction func = PsiTreeUtil.findElementOfClassAtOffset(containingFile, offset, JetNamedFunction.class, false);
assert func != null;
setupFunctionBody(project, func, isUnit, ownerTypeDescriptor);
caretModel.moveToOffset(oldOffset);
}
});
}
private void setupFunctionBody(@NotNull Project project, @NotNull JetNamedFunction func, boolean isUnit,
@NotNull ClassifierDescriptor ownerTypeDescriptor
) {
FileTemplate fileTemplate = FileTemplateManager.getInstance().getCodeTemplate(TEMPLATE_FROM_USAGE_METHOD_BODY);
Properties properties = new Properties();
if (isUnit) {
properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, "Unit");
} else {
JetTypeReference returnTypeRef = func.getReturnTypeRef();
assert returnTypeRef != null;
properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, returnTypeRef.getText());
}
properties.setProperty(FileTemplate.ATTRIBUTE_CLASS_NAME, DescriptorUtils.getFQName(ownerTypeDescriptor).getFqName());
properties.setProperty(FileTemplate.ATTRIBUTE_SIMPLE_CLASS_NAME, ownerTypeDescriptor.getName().getName());
properties.setProperty(FileTemplate.ATTRIBUTE_METHOD_NAME, methodName);
@NonNls String bodyText;
try {
bodyText = fileTemplate.getText(properties);
} catch (ProcessCanceledException e) {
throw e;
} catch (Exception e) {
throw new IncorrectOperationException("Failed to parse file template", e);
}
JetExpression newBodyExpression = JetPsiFactory.createFunctionBody(project, bodyText);
JetExpression oldBodyExpression = func.getBodyExpression();
assert oldBodyExpression != null;
oldBodyExpression.replace(newBodyExpression);
}
@NotNull
private static TypeExpression setupReturnTypeTemplate(@NotNull TemplateBuilder builder, @NotNull JetNamedFunction func,
@NotNull TypeOrExpressionThereof returnType) {