[groovy] move method to util class
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / completion / GroovyCompletionUtil.java
1 /*
2  * Copyright 2000-2016 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.CodeInsightUtilCore;
20 import com.intellij.codeInsight.TailType;
21 import com.intellij.codeInsight.completion.*;
22 import com.intellij.codeInsight.completion.originInfo.OriginInfoProvider;
23 import com.intellij.codeInsight.lookup.LookupElement;
24 import com.intellij.codeInsight.lookup.LookupElementBuilder;
25 import com.intellij.openapi.editor.Document;
26 import com.intellij.openapi.editor.Editor;
27 import com.intellij.openapi.editor.RangeMarker;
28 import com.intellij.openapi.editor.ex.EditorEx;
29 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
30 import com.intellij.openapi.progress.ProgressManager;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.util.Condition;
33 import com.intellij.openapi.util.Conditions;
34 import com.intellij.openapi.util.Iconable;
35 import com.intellij.openapi.util.text.StringUtil;
36 import com.intellij.psi.*;
37 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
38 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
39 import com.intellij.psi.filters.FilterPositionUtil;
40 import com.intellij.psi.impl.source.tree.LeafPsiElement;
41 import com.intellij.psi.tree.IElementType;
42 import com.intellij.psi.tree.TokenSet;
43 import com.intellij.psi.util.*;
44 import com.intellij.util.Consumer;
45 import com.intellij.util.Function;
46 import com.intellij.util.IncorrectOperationException;
47 import com.intellij.util.containers.ContainerUtil;
48 import com.intellij.util.text.CharArrayUtil;
49 import org.jetbrains.annotations.NonNls;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
52 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
53 import org.jetbrains.plugins.groovy.lang.lexer.TokenSets;
54 import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner;
55 import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
56 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
57 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
58 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
59 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrLabeledStatement;
60 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
61 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
62 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
63 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
64 import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseSection;
65 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
66 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression;
67 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
68 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
69 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList;
70 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody;
71 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
72 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
73 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
74 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition;
75 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClassTypeElement;
76 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
77 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
78 import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper;
79 import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
80 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.GrReferenceExpressionImpl;
81 import org.jetbrains.plugins.groovy.lang.psi.impl.types.GrCodeReferenceElementImpl;
82 import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil;
83 import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
84 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
85 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
86
87 import java.util.Arrays;
88 import java.util.Collections;
89 import java.util.List;
90
91 /**
92  * @author ilyas
93  */
94 public class GroovyCompletionUtil {
95
96   private GroovyCompletionUtil() {
97   }
98
99   /**
100    * Return true if last element of current statement is expression
101    *
102    * @param statement
103    * @return
104    */
105   public static boolean endsWithExpression(PsiElement statement) {
106     while (statement != null &&
107            !(statement instanceof GrExpression)) {
108       statement = statement.getLastChild();
109       if (statement instanceof PsiErrorElement) {
110         statement = nearestLeftSibling(statement);
111       }
112     }
113     return statement != null;
114   }
115
116   @Nullable
117   public static PsiElement nearestLeftSibling(PsiElement elem) {
118     elem = elem.getPrevSibling();
119     while (elem != null &&
120            (elem instanceof PsiWhiteSpace ||
121             elem instanceof PsiComment ||
122             GroovyTokenTypes.mNLS.equals(elem.getNode().getElementType()))) {
123       elem = elem.getPrevSibling();
124     }
125     return elem;
126   }
127
128   @Nullable
129   public static PsiElement nearestLeftLeaf(PsiElement elem) {
130     elem = PsiTreeUtil.prevLeaf(elem);
131     while (elem != null &&
132            (elem instanceof PsiWhiteSpace ||
133             elem instanceof PsiComment ||
134             GroovyTokenTypes.mNLS.equals(elem.getNode().getElementType()))) {
135       elem = PsiTreeUtil.prevLeaf(elem);
136     }
137     return elem;
138   }
139
140   /**
141    * Shows whether keyword may be placed as a new statement beginning
142    *
143    * @param element
144    * @param canBeAfterBrace May be after '{' symbol or not
145    * @return
146    */
147   public static boolean isNewStatement(PsiElement element, boolean canBeAfterBrace) {
148     PsiElement previousLeaf = getLeafByOffset(element.getTextRange().getStartOffset() - 1, element);
149     previousLeaf = PsiImplUtil.realPrevious(previousLeaf);
150     if (previousLeaf != null) {
151       if (canBeAfterBrace && GroovyTokenTypes.mLCURLY.equals(previousLeaf.getNode().getElementType())) {
152         return true;
153       }
154       if (GroovyTokenTypes.mCOLON.equals(previousLeaf.getNode().getElementType()) && previousLeaf.getParent() instanceof GrLabeledStatement) {
155         return true;
156       }
157     }
158     return (previousLeaf == null || SEPARATORS.contains(previousLeaf.getNode().getElementType()));
159   }
160
161   @Nullable
162   public static PsiElement getLeafByOffset(int offset, PsiElement element) {
163     if (offset < 0) {
164       return null;
165     }
166     PsiElement candidate = element.getContainingFile();
167     while (candidate.getNode().getFirstChildNode() != null) {
168       candidate = candidate.findElementAt(offset);
169     }
170     return candidate;
171   }
172
173   /**
174    * return true, if the element is first element after modifiers and there is no type element
175    */
176   public static boolean isFirstElementAfterPossibleModifiersInVariableDeclaration(PsiElement element, boolean acceptParameter) {
177     if (element.getParent() instanceof GrTypeDefinitionBody && !(element instanceof PsiComment)) {
178       //is first on the line?
179       String text = element.getContainingFile().getText();
180       int i = CharArrayUtil.shiftBackward(text, element.getTextRange().getStartOffset() - 1, " \t");
181       return i >= 0 && (text.charAt(i) == '\n' || text.charAt(i) == '{');
182     }
183
184     final PsiElement parent = element.getParent();
185     if (!(parent instanceof GrVariable)) return false;
186
187     if (acceptParameter && parent instanceof GrParameter) {
188       return ((GrParameter)parent).getTypeElementGroovy() == null;
189     }
190
191     final PsiElement pparent = parent.getParent();
192     if (!(pparent instanceof GrVariableDeclaration)) return false;
193     if (((GrVariableDeclaration)pparent).isTuple()) return false;
194
195     final GrVariableDeclaration variableDeclaration = (GrVariableDeclaration)pparent;
196     if (variableDeclaration.getTypeElementGroovy() != null) return false;
197
198     return variableDeclaration.getVariables()[0] == parent;
199   }
200
201   private static final TokenSet SEPARATORS = TokenSet.create(GroovyTokenTypes.mNLS,
202                                                              GroovyTokenTypes.mSEMI);
203
204   public static boolean asSimpleVariable(PsiElement context) {
205     return isInTypeDefinitionBody(context) &&
206            isNewStatement(context, true);
207   }
208
209   public static boolean isInTypeDefinitionBody(PsiElement context) {
210     return (context.getParent() instanceof GrCodeReferenceElement &&
211             context.getParent().getParent() instanceof GrClassTypeElement &&
212             context.getParent().getParent().getParent() instanceof GrTypeDefinitionBody) ||
213            context.getParent() instanceof GrTypeDefinitionBody;
214   }
215
216   public static boolean asVariableInBlock(PsiElement context) {
217     if (context.getParent() instanceof GrReferenceExpression) {
218       PsiElement parent = context.getParent().getParent();
219       while (parent instanceof GrStatement) {
220         parent = parent.getParent();
221       }
222       if ((parent instanceof GrControlFlowOwner || parent instanceof GrCaseSection) && isNewStatement(context, true)) {
223         return true;
224       }
225     }
226
227     return context.getParent() instanceof GrTypeDefinitionBody && isNewStatement(context, true);
228   }
229
230   public static boolean asTypedMethod(PsiElement context) {
231     return context.getParent() instanceof GrReferenceElement &&
232            context.getParent().getParent() instanceof GrTypeElement &&
233            context.getParent().getParent().getParent() instanceof GrMethod &&
234            context.getParent().getParent().getParent().getParent() instanceof GrTypeDefinitionBody &&
235            context.getTextRange().getStartOffset() ==
236            context.getParent().getParent().getParent().getParent().getTextRange().getStartOffset();
237   }
238
239
240   public static List<LookupElement> getCompletionVariants(GroovyResolveResult[] candidates,
241                                                           boolean afterNew,
242                                                           PrefixMatcher matcher,
243                                                           PsiElement position) {
244     List<LookupElement> result = ContainerUtil.newArrayList();
245     for (GroovyResolveResult candidate : candidates) {
246       result.addAll(createLookupElements(candidate, afterNew, matcher, position));
247       ProgressManager.checkCanceled();
248     }
249
250     return result;
251   }
252
253   public static List<LookupElement> getCompletionVariants(List<GroovyResolveResult> candidates,
254                                                           boolean afterNew,
255                                                           PrefixMatcher matcher,
256                                                           PsiElement position) {
257     List<LookupElement> result = ContainerUtil.newArrayList();
258     for (GroovyResolveResult candidate : candidates) {
259       result.addAll(createLookupElements(candidate, afterNew, matcher, position));
260       ProgressManager.checkCanceled();
261     }
262
263     return result;
264   }
265
266
267   public static List<? extends LookupElement> createLookupElements(@NotNull GroovyResolveResult candidate,
268                                                                    boolean afterNew,
269                                                                    @NotNull PrefixMatcher matcher,
270                                                                    @Nullable PsiElement position) {
271     final PsiElement element = candidate.getElement();
272     final PsiElement context = candidate.getCurrentFileResolveContext();
273     if (context instanceof GrImportStatement && element != null) {
274       if (element instanceof PsiPackage) {
275         return Collections.emptyList();
276       }
277
278       final String importedName = ((GrImportStatement)context).getImportedName();
279       if (importedName != null) {
280         if (!(matcher.prefixMatches(importedName) ||
281               element instanceof PsiMethod && getterMatches(matcher, (PsiMethod)element, importedName) ||
282               element instanceof PsiMethod && setterMatches(matcher, (PsiMethod)element, importedName))
283           ) {
284           return Collections.emptyList();
285         }
286
287         final GrCodeReferenceElement importReference = ((GrImportStatement)context).getImportReference();
288         if (importReference != null) {
289           boolean alias = ((GrImportStatement)context).isAliasedImport();
290           for (GroovyResolveResult r : importReference.multiResolve(false)) {
291             final PsiElement resolved = r.getElement();
292             if (context.getManager().areElementsEquivalent(resolved, element) && (alias || !(element instanceof PsiClass))) {
293               return generateLookupForImportedElement(candidate, importedName);
294             }
295             else {
296               if (resolved instanceof PsiField && element instanceof PsiMethod && GroovyPropertyUtils
297                 .isAccessorFor((PsiMethod)element, (PsiField)resolved)) {
298                 return generateLookupForImportedElement(candidate, GroovyPropertyUtils.getAccessorPrefix((PsiMethod)element) + GroovyPropertyUtils
299                   .capitalize(importedName));
300               }
301             }
302           }
303         }
304       }
305     }
306
307     String name = element instanceof PsiNamedElement ? ((PsiNamedElement)element).getName() :   element.getText();
308     if (name == null || !matcher.prefixMatches(name)) {
309       return Collections.emptyList();
310     }
311
312     if (element instanceof PsiClass) {
313       return JavaClassNameCompletionContributor
314         .createClassLookupItems((PsiClass)element, afterNew, new GroovyClassNameInsertHandler(), Conditions.<PsiClass>alwaysTrue());
315     }
316
317     LookupElementBuilder builder = LookupElementBuilder.create(element instanceof PsiPackage ? element : candidate, name);
318     return Arrays.asList(setupLookupBuilder(element, candidate.getSubstitutor(), builder, position));
319   }
320
321   private static boolean setterMatches(PrefixMatcher matcher, PsiMethod element, String importedName) {
322     return GroovyPropertyUtils.isSimplePropertySetter(element) && matcher.prefixMatches(GroovyPropertyUtils.getSetterName(importedName));
323   }
324
325   private static boolean getterMatches(PrefixMatcher matcher, PsiMethod element, String importedName) {
326     return GroovyPropertyUtils.isSimplePropertyGetter(element) &&
327            (matcher.prefixMatches(GroovyPropertyUtils.getGetterNameNonBoolean(importedName)) ||
328             PsiType.BOOLEAN.equals(element.getReturnType()) && matcher.prefixMatches(GroovyPropertyUtils.getGetterNameBoolean(importedName)));
329   }
330
331   public static LookupElement createClassLookupItem(PsiClass psiClass) {
332     assert psiClass.isValid();
333     return AllClassesGetter.createLookupItem(psiClass, new GroovyClassNameInsertHandler());
334   }
335
336   private static List<? extends LookupElement> generateLookupForImportedElement(GroovyResolveResult resolveResult, String importedName) {
337     final PsiElement element = resolveResult.getElement();
338     assert element != null;
339     final PsiSubstitutor substitutor = resolveResult.getSubstitutor();
340     LookupElementBuilder builder = LookupElementBuilder.create(resolveResult, importedName).withPresentableText(importedName);
341     return Arrays.asList(setupLookupBuilder(element, substitutor, builder, null));
342   }
343
344   public static LookupElement createLookupElement(PsiNamedElement o) {
345     return setupLookupBuilder(o, PsiSubstitutor.EMPTY, LookupElementBuilder.create(o, o.getName()), null);
346   }
347
348   private static LookupElementBuilder setupLookupBuilder(PsiElement element,
349                                                          PsiSubstitutor substitutor,
350                                                          LookupElementBuilder builder,
351                                                          @Nullable PsiElement position) {
352     builder = builder.withIcon(element.getIcon(Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS))
353       .withInsertHandler(GroovyInsertHandler.INSTANCE);
354     builder = setTailText(element, builder, substitutor);
355     builder = setTypeText(element, builder, substitutor, position);
356     return builder;
357   }
358
359   private static LookupElementBuilder setTailText(PsiElement element, LookupElementBuilder builder, PsiSubstitutor substitutor) {
360     if (element instanceof PsiMethod) {
361       PsiClass aClass = ((PsiMethod)element).getContainingClass();
362       if (aClass != null && aClass.isAnnotationType()) {
363         return builder;
364       }
365       builder = builder.withTailText(PsiFormatUtil.formatMethod((PsiMethod)element, substitutor, PsiFormatUtilBase.SHOW_PARAMETERS,
366                                                                 PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_TYPE));
367     }
368     else if (element instanceof PsiClass) {
369       String tailText = getPackageText((PsiClass)element);
370       final PsiClass psiClass = (PsiClass)element;
371       if ((substitutor == null || substitutor.getSubstitutionMap().isEmpty()) && psiClass.getTypeParameters().length > 0) {
372         tailText = "<" + StringUtil.join(psiClass.getTypeParameters(), new Function<PsiTypeParameter, String>() {
373           @Override
374           public String fun(PsiTypeParameter psiTypeParameter) {
375             return psiTypeParameter.getName();
376           }
377         }, "," + (showSpaceAfterComma(psiClass) ? " " : "")) + ">" + tailText;
378       }
379       builder = builder.withTailText(tailText, true);
380     }
381
382     String originInfo = OriginInfoProvider.getOriginInfo(element);
383     if (originInfo != null) {
384       builder = builder.appendTailText(" " + originInfo, true);
385     }
386
387     return builder;
388   }
389
390   private static String getPackageText(PsiClass psiClass) {
391     @NonNls String packageName = PsiFormatUtil.getPackageDisplayName(psiClass);
392     return " (" + packageName + ")";
393   }
394
395
396   private static boolean showSpaceAfterComma(PsiClass element) {
397     return CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_AFTER_COMMA;
398   }
399
400
401   private static LookupElementBuilder setTypeText(PsiElement element,
402                                                   LookupElementBuilder builder,
403                                                   PsiSubstitutor substitutor,
404                                                   @Nullable PsiElement position) {
405     PsiType type = null;
406     if (element instanceof GrVariable) {
407       if (position != null && org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil.isLocalVariable(element)) {
408         type = TypeInferenceHelper.getInferredType(position, ((GrVariable)element).getName());
409       }
410       else {
411         type = ((GrVariable)element).getTypeGroovy();
412       }
413     }
414     else if (element instanceof PsiVariable) {
415       type = ((PsiVariable)element).getType();
416     }
417     else if (element instanceof PsiMethod) {
418       type = substitutor.substitute(((PsiMethod)element).getReturnType());
419     }
420     return type != null ? builder.withTypeText(type.getPresentableText()) : builder;
421   }
422
423   public static boolean hasConstructorParameters(@NotNull PsiClass clazz, @NotNull PsiElement place) {
424     final GroovyResolveResult[] constructors = ResolveUtil.getAllClassConstructors(clazz, PsiSubstitutor.EMPTY, null, place);
425
426
427     boolean hasSetters = ContainerUtil.find(clazz.getAllMethods(), new Condition<PsiMethod>() {
428       @Override
429       public boolean value(PsiMethod method) {
430         return GroovyPropertyUtils.isSimplePropertySetter(method);
431       }
432     }) != null;
433
434     boolean hasParameters = false;
435     boolean hasAccessibleConstructors = false;
436     for (GroovyResolveResult result : constructors) {
437       final PsiElement element = result.getElement();
438       if (element instanceof PsiMethod) {
439         if (((PsiMethod)element).getParameterList().getParametersCount() > 0) {
440           hasParameters = true;
441         }
442         if (result.isAccessible()) {
443           hasAccessibleConstructors = true;
444         }
445         if (hasAccessibleConstructors && hasParameters) return true;
446       }
447     }
448
449     return !hasAccessibleConstructors && (hasParameters || hasSetters);
450   }
451
452   public static void addImportForItem(PsiFile file, int startOffset, LookupElement item) throws IncorrectOperationException {
453     PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments();
454
455     Object o = item.getObject();
456     if (o instanceof PsiClass) {
457       PsiClass aClass = (PsiClass)o;
458       if (aClass.getQualifiedName() == null) return;
459       final String lookupString = item.getLookupString();
460       int length = lookupString.length();
461       final int i = lookupString.indexOf('<');
462       if (i >= 0) length = i;
463       final int newOffset = addImportForClass(file, startOffset, startOffset + length, aClass);
464       shortenReference(file, newOffset);
465     }
466     else if (o instanceof PsiType) {
467       PsiType type = ((PsiType)o).getDeepComponentType();
468       if (type instanceof PsiClassType) {
469         PsiClass refClass = ((PsiClassType)type).resolve();
470         if (refClass != null) {
471           int length = refClass.getName().length();
472           addImportForClass(file, startOffset, startOffset + length, refClass);
473         }
474       }
475     }
476   }
477
478   public static int addImportForClass(PsiFile file, int startOffset, int endOffset, PsiClass aClass) throws IncorrectOperationException {
479 //    LOG.assertTrue(CommandProcessor.getInstance().getCurrentCommand() != null);
480 //    LOG.assertTrue(
481 //      ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().getCurrentWriteAction(null) != null);
482
483     final PsiManager manager = file.getManager();
484
485     final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
486
487     int newStartOffset = startOffset;
488
489     final PsiReference reference = file.findReferenceAt(endOffset - 1);
490     if (reference != null) {
491       final PsiElement resolved = reference.resolve();
492       if (resolved instanceof PsiClass) {
493         if (((PsiClass)resolved).getQualifiedName() == null || manager.areElementsEquivalent(aClass, resolved)) {
494           return newStartOffset;
495         }
496       }
497     }
498
499     String name = aClass.getName();
500     document.replaceString(startOffset, endOffset, name);
501
502     final RangeMarker toDelete = JavaCompletionUtil.insertTemporary(endOffset, document, " ");
503
504     PsiDocumentManager.getInstance(manager.getProject()).commitAllDocuments();
505
506     final PsiReference ref = file.findReferenceAt(startOffset);
507     if (ref instanceof GrReferenceElement && aClass.isValid()) {
508       PsiElement newElement = ref.bindToElement(aClass);
509       RangeMarker marker = document.createRangeMarker(newElement.getTextRange());
510       CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(newElement);
511       newStartOffset = marker.getStartOffset();
512     }
513
514     if (toDelete.isValid()) {
515       document.deleteString(toDelete.getStartOffset(), toDelete.getEndOffset());
516     }
517
518     return newStartOffset;
519   }
520
521   //need to shorten references in type argument list
522   public static void shortenReference(final PsiFile file, final int offset) throws IncorrectOperationException {
523     final Project project = file.getProject();
524     final PsiDocumentManager manager = PsiDocumentManager.getInstance(project);
525     final Document document = manager.getDocument(file);
526     assert document != null;
527     manager.commitDocument(document);
528     final PsiReference ref = file.findReferenceAt(offset);
529     if (ref instanceof GrCodeReferenceElement) {
530       JavaCodeStyleManager.getInstance(project).shortenClassReferences((GroovyPsiElement)ref);
531     }
532   }
533
534   public static int addRParenth(Editor editor, int oldTail, boolean space_within_cast_parentheses) {
535     int offset = -1;
536
537     final HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(oldTail);
538     while (!iterator.atEnd()) {
539       final IElementType tokenType = iterator.getTokenType();
540       if (TokenSets.WHITE_SPACES_OR_COMMENTS.contains(tokenType)) {
541         iterator.advance();
542         continue;
543       }
544       if (tokenType == GroovyTokenTypes.mRPAREN) {
545         offset = iterator.getEnd();
546       }
547       break;
548     }
549     if (offset != -1) return offset;
550     offset = oldTail;
551     if (space_within_cast_parentheses) {
552       offset = TailType.insertChar(editor, oldTail, ' ');
553     }
554     return TailType.insertChar(editor, offset, ')');
555   }
556
557   public static boolean skipDefGroovyMethod(GrGdkMethod gdkMethod, PsiSubstitutor substitutor, @Nullable PsiType type) {
558     if (type == null) return false;
559     String name = gdkMethod.getStaticMethod().getName();
560
561     final PsiType baseType = gdkMethod.getStaticMethod().getParameterList().getParameters()[0].getType();
562     if (!TypeConversionUtil.erasure(baseType).equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) return false;
563
564     final PsiType substituted = substitutor != null ? substitutor.substitute(baseType) : baseType;
565
566     if (GdkMethodUtil.COLLECTION_METHOD_NAMES.contains(name)) {
567       return !(type instanceof PsiArrayType ||
568                InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ITERABLE) ||
569                substituted instanceof PsiArrayType ||
570                InheritanceUtil.isInheritor(substituted, CommonClassNames.JAVA_LANG_ITERABLE));
571     }
572     if (GdkMethodUtil.isWithName(name)) return false;
573
574     return true;
575   }
576
577   /*
578   we are here:  foo(List<? <caret> ...
579    */
580   public static boolean isWildcardCompletion(PsiElement position) {
581     PsiElement prev = PsiUtil.getPreviousNonWhitespaceToken(position);
582     if (prev instanceof PsiErrorElement) prev = PsiUtil.getPreviousNonWhitespaceToken(prev);
583
584     if (prev == null || prev.getNode().getElementType() != GroovyTokenTypes.mQUESTION) return false;
585
586     final PsiElement pprev = PsiUtil.getPreviousNonWhitespaceToken(prev);
587     if (pprev == null) return false;
588
589     final IElementType t = pprev.getNode().getElementType();
590     return t == GroovyTokenTypes.mLT || t == GroovyTokenTypes.mCOMMA;
591   }
592
593   static boolean isNewStatementInScript(PsiElement context) {
594     final PsiElement leaf = getLeafByOffset(context.getTextRange().getStartOffset() - 1, context);
595     if (leaf != null && isNewStatement(context, false)) {
596       PsiElement parent = leaf.getParent();
597       if (parent instanceof GroovyFile) {
598         return true;
599       }
600     }
601     return false;
602   }
603
604   public static boolean isReferenceElementInNewExpr(PsiElement context) {
605     if (context.getParent() instanceof GrCodeReferenceElement) {
606       PsiElement pparent = context.getParent().getParent();
607       if (pparent instanceof GrNewExpression) return true;
608     }
609
610     return false;
611   }
612
613   static boolean isCodeReferenceElementApplicableToModifierCompletion(PsiElement context) {
614     return context.getParent() instanceof GrCodeReferenceElement &&
615            !(context.getParent().getParent() instanceof GrImportStatement) &&
616            !(context.getParent().getParent() instanceof GrPackageDefinition) &&
617            !(context.getParent().getParent() instanceof GrNewExpression);
618   }
619
620   static boolean isTypelessParameter(PsiElement context) {
621     return (context.getParent() instanceof GrParameter && ((GrParameter)context.getParent()).getTypeElementGroovy() == null);
622   }
623
624   public static boolean isTupleVarNameWithoutTypeDeclared(PsiElement position) {
625     PsiElement parent = position.getParent();
626     PsiElement pparent = parent.getParent();
627     return parent instanceof GrVariable &&
628            ((GrVariable)parent).getNameIdentifierGroovy() == position &&
629            ((GrVariable)parent).getTypeElementGroovy() == null &&
630            pparent instanceof GrVariableDeclaration &&
631            ((GrVariableDeclaration)pparent).isTuple();
632   }
633
634   public static void processVariants(GrReferenceElement referenceElement, PrefixMatcher matcher, CompletionParameters parameters, Consumer<LookupElement> consumer) {
635     if (referenceElement instanceof GrCodeReferenceElementImpl) {
636       CompleteCodeReferenceElement.processVariants((GrCodeReferenceElementImpl)referenceElement, consumer, matcher);
637     }
638     else if (referenceElement instanceof GrReferenceExpressionImpl) {
639       CompleteReferenceExpression.processVariants(matcher, consumer, (GrReferenceExpressionImpl)referenceElement, parameters);
640     }
641   }
642
643   public static boolean isInPossibleClosureParameter(PsiElement position) { //Closure cl={String x, <caret>...
644     if (position == null) return false;
645
646     if (position instanceof PsiWhiteSpace || position.getNode().getElementType() == GroovyTokenTypes.mNLS) {
647       position = FilterPositionUtil.searchNonSpaceNonCommentBack(position);
648     }
649
650     boolean hasCommas = false;
651     while (position != null) {
652       PsiElement parent = position.getParent();
653       if (parent instanceof GrVariable) {
654         PsiElement prev = FilterPositionUtil.searchNonSpaceNonCommentBack(parent);
655         hasCommas = prev != null && prev.getNode().getElementType() == GroovyTokenTypes.mCOMMA;
656       }
657
658       if (parent instanceof GrClosableBlock) {
659         PsiElement sibling = position.getPrevSibling();
660         while (sibling != null) {
661           if (sibling instanceof GrParameterList) {
662             return hasCommas;
663           }
664
665           boolean isComma = sibling instanceof LeafPsiElement && GroovyTokenTypes.mCOMMA == ((LeafPsiElement)sibling).getElementType();
666           hasCommas |= isComma;
667
668           if (isComma ||
669               sibling instanceof PsiWhiteSpace ||
670               sibling instanceof PsiErrorElement ||
671               sibling instanceof GrVariableDeclaration ||
672               sibling instanceof GrReferenceExpression && !((GrReferenceExpression)sibling).isQualified()
673             ) {
674             sibling = sibling.getPrevSibling();
675           }
676           else {
677             return false;
678           }
679         }
680         return false;
681       }
682       position = parent;
683     }
684     return false;
685   }
686
687   @Nullable
688   public static PsiType getQualifierType(@Nullable PsiElement qualifier) {
689     PsiType qualifierType = qualifier instanceof GrExpression ? ((GrExpression)qualifier).getType() : null;
690     if (ResolveUtil.resolvesToClass(qualifier)) {
691       PsiType type = ResolveUtil.unwrapClassType(qualifierType);
692       if (type != null) {
693         qualifierType = type;
694       }
695     }
696     return qualifierType;
697   }
698 }