change signature: do not add comma if only one parameter added
[idea/community.git] / platform / lang-impl / src / com / intellij / refactoring / changeSignature / ChangeSignatureGestureDetector.java
1 /*
2  * Copyright 2000-2010 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.refactoring.changeSignature;
17
18 import com.intellij.openapi.command.CommandProcessor;
19 import com.intellij.openapi.components.ProjectComponent;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.editor.Document;
22 import com.intellij.openapi.editor.EditorBundle;
23 import com.intellij.openapi.editor.EditorFactory;
24 import com.intellij.openapi.editor.actions.EditorActionUtil;
25 import com.intellij.openapi.editor.event.DocumentAdapter;
26 import com.intellij.openapi.editor.event.DocumentEvent;
27 import com.intellij.openapi.editor.event.EditorFactoryEvent;
28 import com.intellij.openapi.editor.event.EditorFactoryListener;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.Comparing;
31 import com.intellij.psi.*;
32 import com.intellij.util.containers.HashMap;
33 import org.jetbrains.annotations.NotNull;
34
35 import java.util.Map;
36
37 /**
38  * User: anna
39  * Date: Sep 6, 2010
40  */
41 public class ChangeSignatureGestureDetector extends PsiTreeChangeAdapter implements ProjectComponent, EditorFactoryListener {
42   private final Project myProject;
43   private final Map<PsiFile, MyDocumentChangeAdapter> myListenerMap = new HashMap<PsiFile, MyDocumentChangeAdapter>();
44   private static final Logger LOG = Logger.getInstance("#" + ChangeSignatureGestureDetector.class.getName());
45   private boolean myDeaf = false;
46
47   public ChangeSignatureGestureDetector(Project project) {
48     myProject = project;
49   }
50
51   public static ChangeSignatureGestureDetector getInstance(Project project){
52     return project.getComponent(ChangeSignatureGestureDetector.class);
53   }
54
55   public boolean isChangeSignatureAvailable(@NotNull PsiElement element) {
56     final MyDocumentChangeAdapter adapter = myListenerMap.get(element.getContainingFile());
57     if (adapter != null && adapter.getCurrentInfo() != null) {
58       final LanguageChangeSignatureDetector detector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(element.getLanguage());
59       LOG.assertTrue(detector != null);
60       return detector.isChangeSignatureAvailable(element, adapter.getCurrentInfo());
61     }
62     return false;
63   }
64
65   public boolean containsChangeSignatureChange(@NotNull PsiFile file) {
66     final MyDocumentChangeAdapter adapter = myListenerMap.get(file);
67     return adapter != null && adapter.getCurrentInfo() != null;
68   }
69
70   public void changeSignature(PsiFile file) {
71     try {
72       myDeaf = true;
73       final MyDocumentChangeAdapter changeBean = myListenerMap.get(file);
74       final ChangeInfo currentInfo = changeBean.getCurrentInfo();
75       final LanguageChangeSignatureDetector detector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(currentInfo.getLanguage());
76       if (detector.showDialog(currentInfo, changeBean.getInitialText())) {
77         changeBean.reinit();
78       }
79     }
80     finally {
81       myDeaf = false;
82     }
83   }
84
85   @Override
86   public void projectOpened() {
87     PsiManager.getInstance(myProject).addPsiTreeChangeListener(this);
88     EditorFactory.getInstance().addEditorFactoryListener(this);
89   }
90
91   @Override
92   public void projectClosed() {
93     final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
94     for (PsiFile file : myListenerMap.keySet()) {
95       final MyDocumentChangeAdapter adapter = myListenerMap.get(file);
96       if (adapter != null) {
97         final Document document = documentManager.getDocument(file);
98         if (document != null) {
99           document.removeDocumentListener(adapter);
100         }
101       }
102     }
103     myListenerMap.clear();
104
105     PsiManager.getInstance(myProject).removePsiTreeChangeListener(this);
106     EditorFactory.getInstance().removeEditorFactoryListener(this);
107   }
108
109   @NotNull
110   @Override
111   public String getComponentName() {
112     return "ChangeSignatureGestureDetector";
113   }
114
115   @Override
116   public void initComponent() {
117   }
118
119   @Override
120   public void disposeComponent() {
121   }
122
123   @Override
124   public void childRemoved(PsiTreeChangeEvent event) {
125     if (myDeaf) return;
126     change(event.getParent());
127   }
128
129   @Override
130   public void childReplaced(PsiTreeChangeEvent event) {
131     if (myDeaf) return;
132     change(event.getChild());
133   }
134
135   private void change(PsiElement child) {
136     if (child == null) return;
137     final PsiFile file = child.getContainingFile();
138     if (file != null) {
139       final MyDocumentChangeAdapter changeBean = myListenerMap.get(file);
140       if (changeBean != null && changeBean.getInitialText() != null) {
141         final ChangeInfo info = LanguageChangeSignatureDetectors.createCurrentChangeInfo(child, changeBean.getInitialChangeInfo());
142         if (info == null) {
143           changeBean.reinit();
144         } else if (!info.equals(changeBean.getInitialChangeInfo())) {
145           changeBean.setCurrentInfo(info);
146         }
147       }
148     }
149   }
150
151   @Override
152   public void editorCreated(EditorFactoryEvent event) {
153     addDocListener(event.getEditor().getDocument());
154   }
155
156   public void addDocListener(Document document) {
157     final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
158     if (file != null) {
159       final MyDocumentChangeAdapter adapter = new MyDocumentChangeAdapter();
160       document.addDocumentListener(adapter);
161       myListenerMap.put(file, adapter);
162     }
163   }
164
165   @Override
166   public void editorReleased(EditorFactoryEvent event) {
167     removeDocListener(event.getEditor().getDocument());
168   }
169
170   public void removeDocListener(Document document) {
171     final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
172     final MyDocumentChangeAdapter adapter = myListenerMap.remove(file);
173     if (adapter != null) {
174       document.removeDocumentListener(adapter);
175     }
176   }
177
178   public void clearSignatureChange(PsiFile file) {
179     final MyDocumentChangeAdapter adapter = myListenerMap.get(file);
180     if (adapter != null) {
181       adapter.setBannedChangeInfo(adapter.getCurrentInfo());
182       adapter.reinit();
183     }
184   }
185
186   private class MyDocumentChangeAdapter extends DocumentAdapter {
187     private String myInitialText;
188     private ChangeInfo myInitialChangeInfo;
189     private ChangeInfo myCurrentInfo;
190     private ChangeInfo myBannedChangeInfo;
191
192     public void setCurrentInfo(ChangeInfo currentInfo) {
193       myCurrentInfo = currentInfo;
194     }
195
196     public void setInitialText(String initialText) {
197       myInitialText = initialText;
198     }
199
200     public String getInitialText() {
201       return myInitialText;
202     }
203
204     public ChangeInfo getCurrentInfo() {
205       return myCurrentInfo;
206     }
207
208     @Override
209     public void beforeDocumentChange(DocumentEvent e) {
210       if (myDeaf) return;
211       if (myInitialText == null) {
212         final Document document = e.getDocument();
213         final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
214         if (!documentManager.isUncommited(document)) {
215           final CommandProcessor processor = CommandProcessor.getInstance();
216           final String currentCommandName = processor.getCurrentCommandName();
217           if (!Comparing.strEqual(EditorBundle.message("typing.in.editor.command.name"), currentCommandName) &&
218               !Comparing.strEqual(EditorBundle.message("paste.command.name"), currentCommandName) &&
219               !Comparing.strEqual(LanguageChangeSignatureDetector.MOVE_PARAMETER, currentCommandName) &&
220               !Comparing.equal(EditorActionUtil.DELETE_COMMAND_GROUP, processor.getCurrentCommandGroupId())) {
221             return;
222           }
223           final PsiFile file = documentManager.getPsiFile(document);
224           if (file != null) {
225             final PsiElement element = file.findElementAt(e.getOffset());
226             if (element != null) {
227               if (myBannedChangeInfo != null && LanguageChangeSignatureDetectors.wasBanned(element, myBannedChangeInfo)) return;
228               myBannedChangeInfo = null;
229               final ChangeInfo info = LanguageChangeSignatureDetectors.createCurrentChangeInfo(element, myCurrentInfo);
230               if (info != null) {
231                 myInitialText = document.getText();
232                 myInitialChangeInfo = info;
233               }
234             }
235           }
236         }
237       }
238     }
239
240     public ChangeInfo getInitialChangeInfo() {
241       return myInitialChangeInfo;
242     }
243
244     public void setBannedChangeInfo(ChangeInfo bannedChangeInfo) {
245       myBannedChangeInfo = bannedChangeInfo;
246     }
247
248     public void reinit() {
249       myInitialText = null;
250       myInitialChangeInfo = null;
251       myCurrentInfo = null;
252     }
253   }
254
255 }