don't insert pair ) when choosing a lookup item by ( before an identifier
[idea/community.git] / platform / lang-api / src / com / intellij / codeInsight / completion / util / ParenthesesInsertHandler.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.util;
17
18 import com.intellij.codeInsight.completion.InsertHandler;
19 import com.intellij.codeInsight.completion.InsertionContext;
20 import com.intellij.codeInsight.lookup.LookupElement;
21 import com.intellij.openapi.editor.Document;
22 import com.intellij.openapi.editor.Editor;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.psi.PsiElement;
25 import com.intellij.psi.PsiFile;
26 import com.intellij.psi.PsiWhiteSpace;
27 import org.jetbrains.annotations.Nullable;
28
29 /**
30  * @author peter
31  */
32 public abstract class ParenthesesInsertHandler<T extends LookupElement> implements InsertHandler<T> {
33   public static final ParenthesesInsertHandler<LookupElement> WITH_PARAMETERS = new ParenthesesInsertHandler<LookupElement>() {
34     protected boolean placeCaretInsideParentheses(final InsertionContext context, final LookupElement item) {
35       return true;
36     }
37   };
38   public static final ParenthesesInsertHandler<LookupElement> NO_PARAMETERS = new ParenthesesInsertHandler<LookupElement>() {
39     protected boolean placeCaretInsideParentheses(final InsertionContext context, final LookupElement item) {
40       return false;
41     }
42   };
43
44   public static ParenthesesInsertHandler<LookupElement> getInstance(boolean hasParameters) {
45     return hasParameters ? WITH_PARAMETERS : NO_PARAMETERS;
46   }
47
48   public static ParenthesesInsertHandler<LookupElement> getInstance(final boolean hasParameters, final boolean spaceBeforeParentheses,
49                                                                     final boolean spaceBetweenParentheses,
50                                                                     final boolean insertRightParenthesis, boolean allowParametersOnNextLine) {
51     return new ParenthesesInsertHandler<LookupElement>(spaceBeforeParentheses, spaceBetweenParentheses, insertRightParenthesis, allowParametersOnNextLine) {
52       @Override
53       protected boolean placeCaretInsideParentheses(InsertionContext context, LookupElement item) {
54         return hasParameters;
55       }
56     };
57   }
58
59   private final boolean mySpaceBeforeParentheses;
60   private final boolean mySpaceBetweenParentheses;
61   private final boolean myMayInsertRightParenthesis;
62   private final boolean myAllowParametersOnNextLine;
63
64   protected ParenthesesInsertHandler(final boolean spaceBeforeParentheses,
65                                      final boolean spaceBetweenParentheses,
66                                      final boolean mayInsertRightParenthesis) {
67     this(spaceBeforeParentheses, spaceBetweenParentheses, mayInsertRightParenthesis, false);
68   }
69
70   protected ParenthesesInsertHandler(boolean spaceBeforeParentheses,
71                                      boolean spaceBetweenParentheses,
72                                      boolean mayInsertRightParenthesis,
73                                      boolean allowParametersOnNextLine) {
74     mySpaceBeforeParentheses = spaceBeforeParentheses;
75     mySpaceBetweenParentheses = spaceBetweenParentheses;
76     myMayInsertRightParenthesis = mayInsertRightParenthesis;
77     myAllowParametersOnNextLine = allowParametersOnNextLine;
78   }
79
80   protected ParenthesesInsertHandler() {
81     this(false, false, true);
82   }
83
84   private static boolean isToken(@Nullable final PsiElement element, final String text) {
85     return element != null && text.equals(element.getText());
86   }
87
88   protected abstract boolean placeCaretInsideParentheses(final InsertionContext context, final T item);
89
90   public void handleInsert(final InsertionContext context, final T item) {
91     final Editor editor = context.getEditor();
92     final Document document = editor.getDocument();
93     PsiElement element = findNextToken(context);
94
95     final char completionChar = context.getCompletionChar();
96     final boolean putCaretInside = completionChar == '(' || placeCaretInsideParentheses(context, item);
97
98     if (completionChar == '(') {
99       context.setAddCompletionChar(false);
100     }
101
102     if (isToken(element, "(")) {
103       int lparenthOffset = element.getTextRange().getStartOffset();
104       if (mySpaceBeforeParentheses && lparenthOffset == context.getTailOffset()) {
105         document.insertString(context.getTailOffset(), " ");
106         lparenthOffset++;
107       }
108
109       if (completionChar == '(' || completionChar == '\t') {
110         editor.getCaretModel().moveToOffset(lparenthOffset + 1);
111       } else {
112         editor.getCaretModel().moveToOffset(context.getTailOffset());
113       }
114
115       context.setTailOffset(lparenthOffset + 1);
116
117       PsiElement list = element.getParent();
118       PsiElement last = list.getLastChild();
119       if (isToken(last, ")")) {
120         int rparenthOffset = last.getTextRange().getStartOffset();
121         context.setTailOffset(rparenthOffset + 1);
122         if (!putCaretInside) {
123           for (int i = lparenthOffset + 1; i < rparenthOffset; i++) {
124             if (!Character.isWhitespace(document.getCharsSequence().charAt(i))) {
125               return;
126             }
127           }
128           editor.getCaretModel().moveToOffset(context.getTailOffset());
129         } else if (mySpaceBetweenParentheses && document.getCharsSequence().charAt(lparenthOffset) == ' ') {
130           editor.getCaretModel().moveToOffset(lparenthOffset + 2);
131         } else {
132           editor.getCaretModel().moveToOffset(lparenthOffset + 1);
133         }
134         return;
135       }
136     } else {
137       document.insertString(context.getTailOffset(), getSpace(mySpaceBeforeParentheses) + "(" + getSpace(mySpaceBetweenParentheses));
138       editor.getCaretModel().moveToOffset(context.getTailOffset());
139     }
140
141     if (!myMayInsertRightParenthesis) return;
142
143     if (context.getCompletionChar() == '(') {
144       //todo use BraceMatchingUtil.isPairedBracesAllowedBeforeTypeInFileType
145       int tail = context.getTailOffset();
146       if (tail < document.getTextLength() && StringUtil.isJavaIdentifierPart(document.getCharsSequence().charAt(tail))) {
147         return;
148       }
149     }
150
151     document.insertString(context.getTailOffset(), getSpace(mySpaceBetweenParentheses) + ")");
152     if (!putCaretInside) {
153       editor.getCaretModel().moveToOffset(context.getTailOffset());
154     }
155   }
156
157   private static String getSpace(boolean needSpace) {
158     return needSpace ? " " : "";
159   }
160
161   @Nullable
162   protected PsiElement findNextToken(final InsertionContext context) {
163     final PsiFile file = context.getFile();
164     PsiElement element = file.findElementAt(context.getTailOffset());
165     if (element instanceof PsiWhiteSpace) {
166       if (!myAllowParametersOnNextLine && element.getText().contains("\n")) {
167         return null;
168       }
169       element = file.findElementAt(element.getTextRange().getEndOffset());
170     }
171     return element;
172   }
173
174 }