update copyrights
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / completion / CompletionProgressIndicator.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 com.intellij.codeInsight.completion;
18
19 import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
20 import com.intellij.codeInsight.lookup.LookupAdapter;
21 import com.intellij.codeInsight.lookup.LookupElement;
22 import com.intellij.codeInsight.lookup.LookupEvent;
23 import com.intellij.codeInsight.lookup.LookupManager;
24 import com.intellij.codeInsight.lookup.impl.LookupImpl;
25 import com.intellij.openapi.application.Application;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.application.Result;
28 import com.intellij.openapi.command.CommandProcessor;
29 import com.intellij.openapi.command.WriteCommandAction;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.editor.CaretModel;
32 import com.intellij.openapi.editor.Document;
33 import com.intellij.openapi.editor.Editor;
34 import com.intellij.openapi.editor.SelectionModel;
35 import com.intellij.openapi.editor.event.*;
36 import com.intellij.openapi.progress.ProgressManager;
37 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
38 import com.intellij.openapi.util.Computable;
39 import com.intellij.ui.HintListener;
40 import com.intellij.ui.LightweightHint;
41 import com.intellij.util.concurrency.Semaphore;
42 import com.intellij.util.ui.AsyncProcessIcon;
43 import com.intellij.util.ui.update.MergingUpdateQueue;
44 import com.intellij.util.ui.update.Update;
45 import org.jetbrains.annotations.Nullable;
46 import org.jetbrains.annotations.TestOnly;
47
48 import javax.swing.*;
49 import java.awt.event.KeyAdapter;
50 import java.awt.event.KeyEvent;
51 import java.util.EventObject;
52
53 /**
54  * @author peter
55  */
56 public class CompletionProgressIndicator extends ProgressIndicatorBase implements CompletionProcess{
57   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionProgressIndicator");
58   private final Editor myEditor;
59   private final CompletionParameters myParameters;
60   private final CodeCompletionHandlerBase myHandler;
61   private final LookupImpl myLookup;
62   private final MergingUpdateQueue myQueue;
63   private boolean myDisposed;
64   private boolean myInitialized;
65   private int myCount;
66   private final Update myUpdate = new Update("update") {
67     public void run() {
68       updateLookup();
69     }
70   };
71   private LightweightHint myHint;
72   private final CompletionContext myContextOriginal;
73   private final Semaphore myFreezeSemaphore;
74
75   private boolean myModifiersReleased;
76
77   private String myOldDocumentText;
78   private int myOldCaret;
79   private int myOldStart;
80   private int myOldEnd;
81
82   public CompletionProgressIndicator(final Editor editor, CompletionParameters parameters, CodeCompletionHandlerBase handler,
83                                      final CompletionContext contextOriginal, Semaphore freezeSemaphore) {
84     myEditor = editor;
85     myParameters = parameters;
86     myHandler = handler;
87     myContextOriginal = contextOriginal;
88     myFreezeSemaphore = freezeSemaphore;
89
90     myLookup = (LookupImpl)LookupManager.getInstance(editor.getProject()).createLookup(editor, LookupElement.EMPTY_ARRAY, "", new CompletionLookupArranger(parameters));
91
92     myLookup.addLookupListener(new LookupAdapter() {
93       public void itemSelected(LookupEvent event) {
94         cancel();
95         finishCompletion();
96
97         LookupElement item = event.getItem();
98         if (item == null) return;
99
100         setMergeCommand();
101
102         contextOriginal.setStartOffset(myEditor.getCaretModel().getOffset() - item.getLookupString().length());
103         CodeCompletionHandlerBase.selectLookupItem(item, event.getCompletionChar(), contextOriginal, myLookup.getItems());
104       }
105
106
107       public void lookupCanceled(final LookupEvent event) {
108         cancel();
109         finishCompletion();
110       }
111     });
112     myLookup.setCalculating(true);
113
114     myQueue = new MergingUpdateQueue("completion lookup progress", 200, true, myEditor.getContentComponent());
115
116     ApplicationManager.getApplication().assertIsDispatchThread();
117     registerItself();
118
119     scheduleAdvertising();
120
121     trackModifiers();
122   }
123
124   private void scheduleAdvertising() {
125     ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
126       public void run() {
127         if (myEditor.isDisposed()) return; //tests?
128         for (final CompletionContributor contributor : CompletionContributor.forParameters(myParameters)) {
129           if (myLookup.getAdvertisementText() != null) return;
130           if (!myLookup.isCalculating() && !myLookup.isVisible()) return;
131
132           String s = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
133             public String compute() {
134               return contributor.advertise(myParameters);
135             }
136           });
137           if (myLookup.getAdvertisementText() != null) return;
138
139           if (s != null) {
140             myLookup.setAdvertisementText(s);
141             ApplicationManager.getApplication().invokeLater(new Runnable() {
142               public void run() {
143                 if (myEditor.isDisposed() || myEditor.getComponent().getRootPane() == null) {
144                   return;
145                 }
146                 updateLookup();
147               }
148             }, myQueue.getModalityState());
149             return;
150           }
151         }
152       }
153     });
154   }
155
156   private void trackModifiers() {
157     final JComponent contentComponent = myEditor.getContentComponent();
158     contentComponent.addKeyListener(new KeyAdapter() {
159       public void keyPressed(KeyEvent e) {
160         processModifier(e);
161       }
162
163       public void keyReleased(KeyEvent e) {
164         processModifier(e);
165       }
166
167       private void processModifier(KeyEvent e) {
168         final int code = e.getKeyCode();
169         if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_META || code == KeyEvent.VK_ALT || code == KeyEvent.VK_SHIFT) {
170           myModifiersReleased = true;
171           if (myOldDocumentText != null) {
172             cleanup();
173           }
174           contentComponent.removeKeyListener(this);
175         }
176       }
177     });
178   }
179
180   private void setMergeCommand() {
181     CommandProcessor.getInstance().setCurrentCommandGroupId("Completion" + hashCode());
182   }
183
184   public void showLookup() {
185     updateLookup();
186   }
187
188   public CompletionParameters getParameters() {
189     return myParameters;
190   }
191
192   private void registerItself() {
193     CompletionServiceImpl.getCompletionService().setCurrentCompletion(this);
194   }
195
196   public void liveAfterDeath(@Nullable final LightweightHint hint) {
197     if (myModifiersReleased || ApplicationManager.getApplication().isUnitTestMode()) {
198       return;
199     }
200
201     registerItself();
202     myHint = hint;
203     if (hint != null) {
204       hint.addHintListener(new HintListener() {
205         public void hintHidden(final EventObject event) {
206           hint.removeHintListener(this);
207           cleanup();
208         }
209       });
210     }
211     final Document document = myEditor.getDocument();
212     document.addDocumentListener(new DocumentAdapter() {
213       @Override
214       public void beforeDocumentChange(DocumentEvent e) {
215         document.removeDocumentListener(this);
216         cleanup();
217       }
218     });
219     final SelectionModel selectionModel = myEditor.getSelectionModel();
220     selectionModel.addSelectionListener(new SelectionListener() {
221       public void selectionChanged(SelectionEvent e) {
222         selectionModel.removeSelectionListener(this);
223         cleanup();
224       }
225     });
226     final CaretModel caretModel = myEditor.getCaretModel();
227     caretModel.addCaretListener(new CaretListener() {
228       public void caretPositionChanged(CaretEvent e) {
229         caretModel.removeCaretListener(this);
230         cleanup();
231       }
232     });
233   }
234
235   public CodeCompletionHandlerBase getHandler() {
236     return myHandler;
237   }
238
239   public LookupImpl getLookup() {
240     return myLookup;
241   }
242
243   private void updateLookup() {
244     ApplicationManager.getApplication().assertIsDispatchThread();
245     if (myEditor.isDisposed() || myDisposed) return;
246
247     if (!myInitialized) {
248       myInitialized = true;
249       if (myLookup.isCalculating()) {
250         final AsyncProcessIcon processIcon = myLookup.getProcessIcon();
251         processIcon.setVisible(true);
252         processIcon.resume();
253       }
254       myLookup.show();
255     }
256     myLookup.refreshUi();
257   }
258
259   public int getCount() {
260     return myCount;
261   }
262
263   private boolean isInsideIdentifier() {
264     return myContextOriginal.getOffsetMap().getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) != myContextOriginal.getSelectionEndOffset();
265   }
266
267
268   public synchronized void addItem(final LookupElement item) {
269     if (!isRunning()) return;
270     ProgressManager.getInstance().checkCanceled();
271
272     final boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode();
273     if (!unitTestMode) {
274       assert !ApplicationManager.getApplication().isDispatchThread();
275     }
276
277     myLookup.addItem(item);
278     myCount++;
279     if (unitTestMode) return;
280
281     if (myCount == 1) {
282       ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
283         public void run() {
284           try {
285             Thread.sleep(300);
286           }
287           catch (InterruptedException e) {
288             LOG.error(e);
289           }
290           myFreezeSemaphore.up();
291         }
292       });
293     }
294     myQueue.queue(myUpdate);
295   }
296
297   public void closeAndFinish() {
298     if (myHint != null) {
299       myHint.hide();
300     }
301     LookupManager.getInstance(myEditor.getProject()).hideActiveLookup();
302   }
303
304   private void finishCompletion() {
305     assert !myDisposed;
306     myDisposed = true;
307
308     ApplicationManager.getApplication().assertIsDispatchThread();
309     myQueue.dispose();
310     cleanup();
311   }
312
313   @TestOnly
314   public static void cleanupForNextTest() {
315     CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
316     if (currentCompletion != null) {
317       currentCompletion.finishCompletion();
318     }
319   }
320
321   private void cleanup() {
322     myHint = null;
323     myOldDocumentText = null;
324     CompletionServiceImpl.getCompletionService().setCurrentCompletion(null);
325   }
326
327   public void stop() {
328     myQueue.cancelAllUpdates();
329     super.stop();
330
331     invokeLaterIfNotDispatch(new Runnable() {
332       public void run() {
333         if (isCanceled()) return;
334
335         if (myLookup.isVisible()) {
336           myLookup.getProcessIcon().suspend();
337           myLookup.getProcessIcon().setVisible(false);
338           updateLookup();
339         }
340       }
341     });
342
343   }
344
345   private void invokeLaterIfNotDispatch(final Runnable runnable) {
346     final Application application = ApplicationManager.getApplication();
347     if (application.isDispatchThread() || application.isUnitTestMode()) {
348       runnable.run();
349     } else {
350       application.invokeLater(runnable, myQueue.getModalityState());
351     }
352   }
353
354   public boolean fillInCommonPrefix(final boolean explicit) {
355     if (isInsideIdentifier()) {
356       return false;
357     }
358
359     final Boolean aBoolean = new WriteCommandAction<Boolean>(myEditor.getProject()) {
360       protected void run(Result<Boolean> result) throws Throwable {
361         if (!explicit) {
362           setMergeCommand();
363         }
364         try {
365           result.setResult(myLookup.fillInCommonPrefix(explicit));
366         }
367         catch (Exception e) {
368           LOG.error(e);
369         }
370       }
371     }.execute().getResultObject();
372     return aBoolean.booleanValue();
373   }
374
375   public boolean isInitialized() {
376     return myInitialized;
377   }
378
379   public void restorePrefix() {
380     setMergeCommand();
381
382     if (myOldDocumentText != null) {
383       myEditor.getDocument().setText(myOldDocumentText);
384       myEditor.getSelectionModel().setSelection(myOldStart, myOldEnd);
385       myEditor.getCaretModel().moveToOffset(myOldCaret);
386       myOldDocumentText = null;
387       return;
388     }
389
390     getLookup().restorePrefix();
391   }
392
393   public Editor getEditor() {
394     return myEditor;
395   }
396
397   public void rememberDocumentState() {
398     if (myModifiersReleased) {
399       return;
400     }
401
402     myOldDocumentText = myEditor.getDocument().getText();
403     myOldCaret = myEditor.getCaretModel().getOffset();
404     myOldStart = myEditor.getSelectionModel().getSelectionStart();
405     myOldEnd = myEditor.getSelectionModel().getSelectionEnd();
406   }
407
408 }