type inference for closures in groovy1.8
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / completion / GroovyInsertHandler.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.jetbrains.plugins.groovy.lang.completion;
18
19 import com.intellij.codeInsight.TailType;
20 import com.intellij.codeInsight.completion.InsertHandler;
21 import com.intellij.codeInsight.completion.InsertionContext;
22 import com.intellij.codeInsight.completion.util.MethodParenthesesHandler;
23 import com.intellij.codeInsight.lookup.Lookup;
24 import com.intellij.codeInsight.lookup.LookupElement;
25 import com.intellij.openapi.editor.CaretModel;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.psi.*;
29 import com.intellij.psi.util.PsiTreeUtil;
30 import org.jetbrains.annotations.NonNls;
31 import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
32 import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner;
33 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
34 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
35 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair;
36 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
37 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression;
38 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
39 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
40 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
41 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
42 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
43 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
44 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
45
46 import java.util.Arrays;
47
48 /**
49  * @author ven
50  */
51 public class GroovyInsertHandler implements InsertHandler<LookupElement> {
52   public static final GroovyInsertHandler INSTANCE = new GroovyInsertHandler();
53   private static final String CLOSURE_CLASS = "groovy.lang.Closure";
54
55   public void handleInsert(InsertionContext context, LookupElement item) {
56     @NonNls Object obj = item.getObject();
57
58     if (obj instanceof GroovyResolveResult) {
59       obj = ((GroovyResolveResult)obj).getElement();
60     }
61
62     if (obj instanceof PsiMethod) {
63       PsiMethod method = (PsiMethod)obj;
64       PsiParameter[] parameters = method.getParameterList().getParameters();
65       Editor editor = context.getEditor();
66       Document document = editor.getDocument();
67       if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) {
68         handleOverwrite(editor.getCaretModel().getOffset(), document);
69       }
70
71       CaretModel caretModel = editor.getCaretModel();
72       int offset = context.getStartOffset() + method.getName().length();
73       PsiFile file = PsiDocumentManager.getInstance(method.getProject()).getPsiFile(document);
74       PsiElement elementAt = file.findElementAt(context.getStartOffset());
75       PsiElement parent = elementAt != null ? elementAt.getParent() : null;
76       if (parent instanceof GrReferenceExpression &&
77           ((GrReferenceExpression)parent).getDotTokenType() == GroovyElementTypes.mMEMBER_POINTER) {
78         return;
79       }
80
81       if (parent instanceof GrAnnotationNameValuePair || parent.getParent() instanceof GrAnnotationNameValuePair) {
82         document.insertString(offset, " = ");
83         caretModel.moveToOffset(offset + 3);
84         return;
85       }
86
87       if (PsiTreeUtil.getParentOfType(elementAt, GrImportStatement.class) != null) return;
88
89       if (parameters.length == 1) {
90         final PsiType type = parameters[0].getType();
91         if (type instanceof PsiClassType) {
92           final PsiClass psiClass = ((PsiClassType)type).resolve();
93           if (psiClass != null && CLOSURE_CLASS.equals(psiClass.getQualifiedName())) {
94             document.insertString(offset, " {}");
95             caretModel.moveToOffset(offset + 2);
96             return;
97           }
98         }
99       }
100
101       PsiDocumentManager docManager = PsiDocumentManager.getInstance(method.getProject());
102       docManager.commitDocument(document);
103       PsiFile psiFile = docManager.getPsiFile(document);
104       if (method instanceof GrMethod &&
105           method.getParameterList().getParametersCount() > 0 &&
106           method.getContainingClass() instanceof GroovyScriptClass &&
107           isExpressionStatement(psiFile, context.getStartOffset())) {
108         return;
109       }
110       if (isExpressionStatement(psiFile, context.getStartOffset()) &&
111           (PsiType.VOID.equals(PsiUtil.getSmartReturnType(method)) ||
112            method instanceof GrMethod && ((GrMethod)method).getReturnTypeElementGroovy() == null) &&
113           '(' != context.getCompletionChar() &&
114           parameters.length > 0) {
115         TailType.insertChar(editor, offset, ' ');
116         return;
117       }
118
119       new MethodParenthesesHandler(method, true).handleInsert(context, item);
120       return;
121     }
122
123     if (obj instanceof String && !"assert".equals(obj)) {
124       Editor editor = context.getEditor();
125       Document document = editor.getDocument();
126       if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) {
127         handleOverwrite(editor.getCaretModel().getOffset(), document);
128       }
129     } else if (obj instanceof PsiClass) {
130       final PsiClass clazz = (PsiClass)obj;
131       Editor editor = context.getEditor();
132       Document document = editor.getDocument();
133       PsiFile file = PsiDocumentManager.getInstance(clazz.getProject()).getPsiFile(document);
134       PsiElement elementAt = file.findElementAt(context.getStartOffset());
135       CaretModel caretModel = editor.getCaretModel();
136       int offset = context.getStartOffset() + elementAt.getTextLength();
137
138       final String text = document.getText();
139       final PsiElement parent = elementAt.getParent();
140       if (parent instanceof GrCodeReferenceElement &&
141           parent.getParent() instanceof GrNewExpression &&
142           (offset == text.length() || !text.substring(offset).trim().startsWith("("))) {
143         document.insertString(offset, "()");
144         final PsiMethod[] methods = ResolveUtil.getAllClassConstructors(clazz, (GroovyPsiElement)parent, PsiSubstitutor.EMPTY);
145         for (PsiMethod method : methods) {
146           if (method.getParameterList().getParameters().length > 0) {
147             caretModel.moveToOffset(offset + 1);
148             return;
149           }
150         }
151         caretModel.moveToOffset(offset + 2);
152         return;
153       }
154     }
155
156     addTailType(item).processTail(context.getEditor(), context.getTailOffset());
157   }
158
159   private static boolean isExpressionStatement(PsiFile psiFile, int offset) {
160     PsiElement elementAt = psiFile.findElementAt(offset);
161     if (elementAt == null) return false;
162     GrExpression expr = PsiTreeUtil.getParentOfType(elementAt, GrExpression.class);
163     if (expr == null) return false;
164     return expr.getParent() instanceof GrControlFlowOwner;
165   }
166
167   private static void handleOverwrite(final int offset, final Document document) {
168     final CharSequence sequence = document.getCharsSequence();
169     int i = offset;
170     while (i < sequence.length() && (Character.isJavaIdentifierPart(sequence.charAt(i)) || sequence.charAt(i) == '\'')) i++;
171     document.deleteString(offset, i);
172   }
173
174   private static TailType addTailType(LookupElement item) {
175     if ("default".equals(item.toString())) {
176       return TailType.CASE_COLON;
177     }
178     @NonNls String[] withSpace =
179       {"private", "public", "protected", "static", "transient", "abstract", "native", "volatile", "strictfp", "boolean", "byte", "char",
180         "short", "int", "float", "long", "double", "void", "new", "try", "while", "with", "switch", "for", "return", "throw", "throws",
181         "assert", "synchronized", "package", "class", "interface", "enum", "extends", "implements", "case", "catch", "finally", "else",
182         "instanceof", "import", "final", "def"};
183     if (Arrays.asList(withSpace).contains(item.toString())) {
184       return TailType.SPACE;
185     }
186     return TailType.NONE;
187   }
188 }