0f36ce3ec8fcc448cfd302fe26bdb99d3577d15e
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / completion / DefaultInsertHandler.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 package com.intellij.codeInsight.completion;
17
18 import com.intellij.codeInsight.AutoPopupController;
19 import com.intellij.codeInsight.CharTailType;
20 import com.intellij.codeInsight.TailType;
21 import com.intellij.codeInsight.TailTypes;
22 import com.intellij.codeInsight.lookup.Lookup;
23 import com.intellij.codeInsight.lookup.LookupElement;
24 import com.intellij.codeInsight.lookup.LookupItem;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.psi.*;
30 import com.intellij.psi.codeStyle.CodeStyleSettings;
31 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
32 import com.intellij.psi.impl.source.PostprocessReformattingAspect;
33 import org.jetbrains.annotations.NotNull;
34
35 public class DefaultInsertHandler extends TemplateInsertHandler implements Cloneable {
36   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.DefaultInsertHandler");
37
38   public static final DefaultInsertHandler NO_TAIL_HANDLER = new DefaultInsertHandler(){
39     @Override
40     protected TailType getTailType(char completionChar, LookupItem item) {
41       return TailType.NONE;
42     }
43   };
44
45   public void handleInsert(final InsertionContext context, LookupElement item) {
46     super.handleInsert(context, item);
47
48     handleInsertInner(context, (LookupItem)item, context.getCompletionChar());
49   }
50
51   private void handleInsertInner(InsertionContext context, LookupItem item, final char completionChar) {
52     final Project project = context.getProject();
53     final Editor editor = context.getEditor();
54     final Document document = editor.getDocument();
55     PsiDocumentManager.getInstance(project).commitDocument(document);
56
57     final PsiFile file = context.getFile();
58
59     TailType tailType = getTailType(completionChar, item);
60
61     InsertHandlerState state = new InsertHandlerState(context.getSelectionEndOffset(), context.getSelectionEndOffset());
62
63     if (completionChar == Lookup.REPLACE_SELECT_CHAR) {
64       removeEndOfIdentifier(context);
65     }
66     else if(context.getOffsetMap().getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) != context.getSelectionEndOffset()) {
67       JavaCompletionUtil.resetParensInfo(context.getOffsetMap());
68     }
69
70     handleParentheses(false, false, tailType, context, state);
71
72     context.setTailOffset(state.tailOffset);
73     state.caretOffset = processTail(tailType, state.caretOffset, state.tailOffset, editor);
74     editor.getSelectionModel().removeSelection();
75
76     addImportForItem(context, item);
77
78     if (tailType == TailType.DOT || context.getCompletionChar() == '.') {
79       AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, null);
80     }
81
82   }
83
84   private static void handleParentheses(final boolean hasParams, final boolean needParenth, TailType tailType, InsertionContext context, InsertHandlerState myState){
85     final Document document = context.getEditor().getDocument();
86     boolean insertRightParenth = context.getCompletionChar() != Lookup.COMPLETE_STATEMENT_SELECT_CHAR;
87
88     if (needParenth){
89       if (context.getOffsetMap().getOffset(JavaCompletionUtil.LPAREN_OFFSET) >= 0 && context.getOffsetMap().getOffset(JavaCompletionUtil.ARG_LIST_END_OFFSET) >= 0){
90         myState.tailOffset = context.getOffsetMap().getOffset(JavaCompletionUtil.ARG_LIST_END_OFFSET);
91         if (context.getOffsetMap().getOffset(JavaCompletionUtil.RPAREN_OFFSET) < 0 && insertRightParenth){
92           document.insertString(myState.tailOffset, ")");
93           myState.tailOffset += 1;
94         }
95         if (hasParams){
96           myState.caretOffset = context.getOffsetMap().getOffset(JavaCompletionUtil.LPAREN_OFFSET) + 1;
97         }
98         else{
99           myState.caretOffset = context.getOffsetMap().getOffset(JavaCompletionUtil.ARG_LIST_END_OFFSET);
100         }
101       }
102       else{
103         final CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(context.getProject());
104         myState.tailOffset = context.getSelectionEndOffset();
105         myState.caretOffset = context.getSelectionEndOffset();
106
107         if(styleSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES){
108           document.insertString(myState.tailOffset++, " ");
109           myState.caretOffset ++;
110         }
111         if (insertRightParenth) {
112           final CharSequence charsSequence = document.getCharsSequence();
113           if (charsSequence.length() <= myState.tailOffset || charsSequence.charAt(myState.tailOffset) != '(') {
114             document.insertString(myState.tailOffset, "(");
115           }
116
117           document.insertString(myState.tailOffset + 1, ")");
118           if (hasParams){
119             myState.tailOffset += 2;
120             myState.caretOffset++;
121           }
122           else{
123             if (tailType != TailTypes.CALL_RPARENTH) {
124               myState.tailOffset += 2;
125               myState.caretOffset += 2;
126             }
127             else {
128               myState.tailOffset++;
129               myState.caretOffset++;
130             }
131           }
132         }
133         else{
134           document.insertString(myState.tailOffset++, "(");
135           myState.caretOffset ++;
136         }
137
138         if(hasParams && styleSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES){
139           document.insertString(myState.caretOffset++, " ");
140           myState.tailOffset++;
141         }
142       }
143     }
144   }
145
146   public static void removeEndOfIdentifier(InsertionContext context){
147     final Document document = context.getEditor().getDocument();
148     JavaCompletionUtil.initOffsets(context.getFile(), context.getProject(), context.getOffsetMap());
149     document.deleteString(context.getSelectionEndOffset(), context.getOffsetMap().getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET));
150     if(context.getOffsetMap().getOffset(JavaCompletionUtil.LPAREN_OFFSET) > 0){
151       document.deleteString(context.getOffsetMap().getOffset(JavaCompletionUtil.LPAREN_OFFSET),
152                               context.getOffsetMap().getOffset(JavaCompletionUtil.ARG_LIST_END_OFFSET));
153       JavaCompletionUtil.resetParensInfo(context.getOffsetMap());
154     }
155   }
156
157   protected TailType getTailType(final char completionChar, LookupItem item){
158     switch(completionChar){
159       case '.': return new CharTailType('.', false);
160       case ',': return TailType.COMMA;
161       case ';': return TailType.SEMICOLON;
162       case '=': return TailType.EQ;
163       case ' ': return TailType.SPACE;
164       case ':': return TailType.CASE_COLON; //?
165       case '<':
166       case '>':
167       case '\"':
168     }
169     final TailType attr = item.getTailType();
170     return attr == TailType.UNKNOWN ? TailType.NONE : attr;
171   }
172
173   private static int processTail(TailType tailType, int caretOffset, int tailOffset, Editor editor) {
174     editor.getCaretModel().moveToOffset(caretOffset);
175     tailType.processTail(editor, tailOffset);
176     return editor.getCaretModel().getOffset();
177   }
178
179   @Override
180   protected void populateInsertMap(@NotNull final PsiFile file, @NotNull final OffsetMap offsetMap) {
181     JavaCompletionUtil.initOffsets(file, file.getProject(), offsetMap);
182   }
183
184   public static void addImportForItem(InsertionContext context, LookupElement item) {
185     PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
186
187     PsiFile file = context.getFile();
188     Object o = item.getObject();
189     if (o instanceof PsiClass){
190       PsiClass aClass = (PsiClass)o;
191       if (aClass.getQualifiedName() == null) return;
192       final String lookupString = item.getLookupString();
193       int length = lookupString.length();
194       final int i = lookupString.indexOf('<');
195       if (i >= 0) length = i;
196       addImportForClass(aClass, context, length);
197       JavaCompletionUtil.shortenReference(file, context.getStartOffset());
198     }
199     else if (o instanceof PsiType){
200       PsiType type = ((PsiType)o).getDeepComponentType();
201       if (type instanceof PsiClassType) {
202         PsiClass refClass = ((PsiClassType) type).resolve();
203         if (refClass != null){
204           int length = refClass.getName().length();
205           addImportForClass(refClass, context, length);
206         }
207       }
208     }
209     else if (o instanceof PsiMethod){
210       PsiMethod method = (PsiMethod)o;
211       if (method.isConstructor()){
212         PsiClass aClass = method.getContainingClass();
213         if (aClass != null){
214           int length = method.getName().length();
215           addImportForClass(aClass, context, length);
216         }
217       }
218     }
219   }
220
221   private static void addImportForClass(PsiClass aClass, InsertionContext context, int nameLength) {
222     context.setTailOffset(JavaCompletionUtil.insertClassReference(aClass, context.getFile(), context.getStartOffset(),
223                                                                   context.getStartOffset() + nameLength));
224     PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting();
225   }
226
227
228   public static class InsertHandlerState{
229     int tailOffset;
230     int caretOffset;
231
232     public InsertHandlerState(int caretOffset, int tailOffset){
233       this.caretOffset = caretOffset;
234       this.tailOffset = tailOffset;
235     }
236   }
237 }