6ff52c50a8373ff6a6c5871b101a04a8e0d45e67
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / completion / CodeCompletionHandlerBase.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.CodeInsightActionHandler;
20 import com.intellij.codeInsight.CodeInsightSettings;
21 import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
22 import com.intellij.codeInsight.editorActions.CompletionAutoPopupHandler;
23 import com.intellij.codeInsight.editorActions.smartEnter.SmartEnterProcessor;
24 import com.intellij.codeInsight.editorActions.smartEnter.SmartEnterProcessors;
25 import com.intellij.codeInsight.lookup.*;
26 import com.intellij.codeInsight.lookup.impl.LookupImpl;
27 import com.intellij.diagnostic.LogMessageEx;
28 import com.intellij.diagnostic.errordialog.Attachment;
29 import com.intellij.featureStatistics.FeatureUsageTracker;
30 import com.intellij.ide.DataManager;
31 import com.intellij.injected.editor.EditorWindow;
32 import com.intellij.lang.Language;
33 import com.intellij.lang.injection.InjectedLanguageManager;
34 import com.intellij.openapi.actionSystem.DataContext;
35 import com.intellij.openapi.application.AccessToken;
36 import com.intellij.openapi.application.ApplicationManager;
37 import com.intellij.openapi.application.WriteAction;
38 import com.intellij.openapi.application.ex.ApplicationManagerEx;
39 import com.intellij.openapi.command.CommandProcessor;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.editor.*;
42 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
43 import com.intellij.openapi.editor.event.DocumentEvent;
44 import com.intellij.openapi.editor.ex.DocumentEx;
45 import com.intellij.openapi.editor.ex.RangeMarkerEx;
46 import com.intellij.openapi.editor.ex.util.EditorUtil;
47 import com.intellij.openapi.fileEditor.FileDocumentManager;
48 import com.intellij.openapi.progress.ProcessCanceledException;
49 import com.intellij.openapi.progress.ProgressManager;
50 import com.intellij.openapi.project.DumbService;
51 import com.intellij.openapi.project.IndexNotReadyException;
52 import com.intellij.openapi.project.Project;
53 import com.intellij.openapi.util.*;
54 import com.intellij.openapi.vfs.LocalFileSystem;
55 import com.intellij.openapi.vfs.VirtualFile;
56 import com.intellij.psi.PsiDocumentManager;
57 import com.intellij.psi.PsiElement;
58 import com.intellij.psi.PsiFile;
59 import com.intellij.psi.impl.DebugUtil;
60 import com.intellij.psi.impl.PsiFileEx;
61 import com.intellij.psi.impl.PsiModificationTrackerImpl;
62 import com.intellij.psi.impl.source.PostprocessReformattingAspect;
63 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
64 import com.intellij.psi.util.PsiUtilBase;
65 import com.intellij.reference.SoftReference;
66 import com.intellij.util.Consumer;
67 import com.intellij.util.ThreeState;
68 import com.intellij.util.concurrency.Semaphore;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
71
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.List;
75 import java.util.concurrent.atomic.AtomicReference;
76
77 public class CodeCompletionHandlerBase implements CodeInsightActionHandler {
78   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CodeCompletionHandlerBase");
79   private final CompletionType myCompletionType;
80   final boolean invokedExplicitly;
81   final boolean synchronous;
82   final boolean autopopup;
83
84   public CodeCompletionHandlerBase(final CompletionType completionType) {
85     this(completionType, true, false, true);
86   }
87
88   public CodeCompletionHandlerBase(CompletionType completionType, boolean invokedExplicitly, boolean autopopup, boolean synchronous) {
89     myCompletionType = completionType;
90     this.invokedExplicitly = invokedExplicitly;
91     this.autopopup = autopopup;
92     this.synchronous = synchronous;
93
94     if (invokedExplicitly) {
95       assert synchronous;
96     }
97     if (autopopup) {
98       assert !invokedExplicitly;
99     }
100   }
101
102   @Override
103   public final void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile psiFile) {
104     invokeCompletion(project, editor);
105   }
106
107   public final void invokeCompletion(final Project project, final Editor editor) {
108     try {
109       invokeCompletion(project, editor, 1, false);
110     }
111     catch (IndexNotReadyException e) {
112       DumbService.getInstance(project).showDumbModeNotification("Code completion is not available here while indices are being built");
113     }
114   }
115
116   public final void invokeCompletion(final Project project, final Editor editor, int time, boolean hasModifiers) {
117     final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project);
118     assert psiFile != null : "no PSI file: " + FileDocumentManager.getInstance().getFile(editor.getDocument());
119
120     if (!ApplicationManager.getApplication().isUnitTestMode()) {
121       if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
122         throw new AssertionError("Completion should not be invoked inside write action");
123       }
124     }
125
126     if (editor.isViewer()) {
127       editor.getDocument().fireReadOnlyModificationAttempt();
128       return;
129     }
130
131     final Document document = editor.getDocument();
132     if (!FileDocumentManager.getInstance().requestWriting(document, project)) {
133       return;
134     }
135
136     psiFile.putUserData(PsiFileEx.BATCH_REFERENCE_PROCESSING, Boolean.TRUE);
137
138     CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
139     boolean repeated = phase.indicator != null && phase.indicator.isRepeatedInvocation(myCompletionType, editor);
140     if (repeated && isAutocompleteCommonPrefixOnInvocation() && phase.fillInCommonPrefix()) {
141       return;
142     }
143
144     int newTime = phase.newCompletionStarted(time, repeated);
145     if (invokedExplicitly) {
146       time = newTime;
147     }
148     if (CompletionServiceImpl.isPhase(CompletionPhase.InsertedSingleItem.class)) {
149       CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
150     }
151     CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass(), CompletionPhase.CommittingDocuments.class);
152
153     if (time > 1) {
154       if (myCompletionType == CompletionType.CLASS_NAME) {
155         FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.SECOND_CLASS_NAME_COMPLETION);
156       }
157       else if (myCompletionType == CompletionType.BASIC) {
158         FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.SECOND_BASIC_COMPLETION);
159       }
160     }
161
162     final CompletionInitializationContext[] initializationContext = {null};
163
164
165     Runnable initCmd = new Runnable() {
166       @Override
167       public void run() {
168
169         Runnable runnable = new Runnable() {
170           @Override
171           public void run() {
172             final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
173
174             EditorUtil.fillVirtualSpaceUntilCaret(editor);
175             documentManager.commitAllDocuments();
176
177             int docLength = editor.getDocument().getTextLength();
178             int psiLength = psiFile.getTextLength();
179             if (docLength != psiLength) {
180               if (ApplicationManagerEx.getApplicationEx().isInternal()) {
181                 String docText = editor.getDocument().getText();
182                 String psiText = psiFile.getText();
183                 String message = "unsuccessful commit: (injected=" +(editor instanceof EditorWindow) +"); document " + System.identityHashCode(editor.getDocument()) + "; " +
184                                  "docText=\n'" + docText +"' (" + docText.length() +" chars; .length()="+ docLength+")\n" +
185                                  "; fileText=\n'" + psiText + "' (" + psiText.length() +" chars; .length()="+ psiLength+")\n"
186                                  ;
187                 throw new AssertionError(message);
188               }
189
190               throw new AssertionError("unsuccessful commit: injected=" + (editor instanceof EditorWindow));
191             }
192
193             final Ref<CompletionContributor> current = Ref.create(null);
194             initializationContext[0] = new CompletionInitializationContext(editor, psiFile, myCompletionType) {
195               CompletionContributor dummyIdentifierChanger;
196               @Override
197               public void setFileCopyPatcher(@NotNull FileCopyPatcher fileCopyPatcher) {
198                 super.setFileCopyPatcher(fileCopyPatcher);
199
200                 if (dummyIdentifierChanger != null) {
201                   LOG.error("Changing the dummy identifier twice, already changed by " + dummyIdentifierChanger);
202                 }
203                 dummyIdentifierChanger = current.get();
204               }
205             };
206             for (final CompletionContributor contributor : CompletionContributor.forLanguage(initializationContext[0].getPositionLanguage())) {
207               if (DumbService.getInstance(project).isDumb() && !DumbService.isDumbAware(contributor)) {
208                 continue;
209               }
210
211               current.set(contributor);
212               contributor.beforeCompletion(initializationContext[0]);
213               assert !documentManager.isUncommited(document) : "Contributor " + contributor + " left the document uncommitted";
214             }
215           }
216         };
217         ApplicationManager.getApplication().runWriteAction(runnable);
218       }
219     };
220     if (autopopup) {
221       CommandProcessor.getInstance().runUndoTransparentAction(initCmd);
222
223       int offset = editor.getCaretModel().getOffset();
224       int psiOffset = Math.max(0, offset);
225
226       PsiElement elementAt = InjectedLanguageUtil.findInjectedElementNoCommit(psiFile, psiOffset);
227       if (elementAt == null) {
228         elementAt = psiFile.findElementAt(psiOffset);
229       }
230
231       Language language = elementAt != null ? PsiUtilBase.findLanguageFromElement(elementAt):psiFile.getLanguage();
232
233       for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) {
234         final ThreeState result = confidence.shouldSkipAutopopup(elementAt, psiFile, offset); // TODO: Peter Lazy API
235         if (result == ThreeState.YES) return;
236         if (result == ThreeState.NO) break;
237       }
238     } else {
239       CommandProcessor.getInstance().executeCommand(project, initCmd, null, null);
240     }
241
242     insertDummyIdentifier(initializationContext[0], hasModifiers, time);
243   }
244
245   @NotNull
246   private LookupImpl obtainLookup(Editor editor) {
247     LookupImpl existing = (LookupImpl)LookupManager.getActiveLookup(editor);
248     if (existing != null && existing.isCompletion()) {
249       existing.markReused();
250       if (!autopopup) {
251         existing.setFocused(true);
252       }
253       return existing;
254     }
255
256     LookupImpl lookup = (LookupImpl)LookupManager.getInstance(editor.getProject()).createLookup(editor, LookupElement.EMPTY_ARRAY, "", LookupArranger.DEFAULT);
257     if (editor.isOneLineMode()) {
258       lookup.setCancelOnClickOutside(true);
259       lookup.setCancelOnOtherWindowOpen(true);
260       lookup.setForceLightweightPopup(false);
261     }
262     lookup.setFocused(!autopopup);
263     return lookup;
264   }
265
266   private void doComplete(CompletionInitializationContext initContext,
267                           boolean hasModifiers,
268                           int invocationCount,
269                           PsiFile hostFile,
270                           int hostStartOffset, Editor hostEditor, OffsetMap hostMap, OffsetTranslator translator) {
271     CompletionContext context = createCompletionContext(hostFile, hostStartOffset, hostEditor, hostMap);
272     CompletionParameters parameters = createCompletionParameters(invocationCount, initContext, context);
273
274     CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
275     if (phase instanceof CompletionPhase.CommittingDocuments) {
276       if (phase.indicator != null) {
277         phase.indicator.closeAndFinish(false);
278       }
279       ((CompletionPhase.CommittingDocuments)phase).replaced = true;
280     } else {
281       CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());
282     }
283
284     final Editor editor = initContext.getEditor();
285     final Semaphore freezeSemaphore = new Semaphore();
286     freezeSemaphore.down();
287     final CompletionProgressIndicator indicator = new CompletionProgressIndicator(editor, parameters, this, freezeSemaphore,
288                                                                                   initContext.getOffsetMap(), hasModifiers);
289     Disposer.register(indicator, hostMap);
290     Disposer.register(indicator, context.getOffsetMap());
291     Disposer.register(indicator, translator);
292
293     CompletionServiceImpl.setCompletionPhase(synchronous ? new CompletionPhase.Synchronous(indicator) : new CompletionPhase.BgCalculation(indicator));
294
295     final AtomicReference<LookupElement[]> data = startCompletionThread(parameters, indicator, initContext);
296
297     if (!synchronous) {
298       return;
299     }
300
301     if (freezeSemaphore.waitFor(2000)) {
302       final LookupElement[] allItems = data.get();
303       if (allItems != null) { // the completion is really finished, now we may auto-insert or show lookup
304         completionFinished(initContext.getStartOffset(), initContext.getSelectionEndOffset(), indicator, allItems, hasModifiers);
305         checkNotSync(indicator, allItems);
306         return;
307       }
308     }
309
310     CompletionServiceImpl.setCompletionPhase(new CompletionPhase.BgCalculation(indicator));
311     indicator.showLookup();
312   }
313
314   private static void checkNotSync(CompletionProgressIndicator indicator, LookupElement[] allItems) {
315     if (CompletionServiceImpl.isPhase(CompletionPhase.Synchronous.class)) {
316       LOG.error("sync phase survived: " + Arrays.toString(allItems) + "; indicator=" + CompletionServiceImpl.getCompletionPhase().indicator + "; myIndicator=" + indicator);
317       CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
318     }
319   }
320
321   private static AtomicReference<LookupElement[]> startCompletionThread(final CompletionParameters parameters,
322                                                                         final CompletionProgressIndicator indicator,
323                                                                         final CompletionInitializationContext initContext) {
324
325     final Semaphore startSemaphore = new Semaphore();
326     startSemaphore.down();
327
328     final AtomicReference<LookupElement[]> data = new AtomicReference<LookupElement[]>(null);
329     final Runnable computeRunnable = new Runnable() {
330       @Override
331       public void run() {
332         ProgressManager.getInstance().runProcess(new Runnable() {
333           @Override
334           public void run() {
335             try {
336               ApplicationManager.getApplication().runReadAction(new Runnable() {
337                 @Override
338                 public void run() {
339                   startSemaphore.up();
340                   ProgressManager.checkCanceled();
341
342                   indicator.duringCompletion(initContext);
343                   ProgressManager.checkCanceled();
344
345                   data.set(CompletionService.getCompletionService().performCompletion(parameters, new Consumer<CompletionResult>() {
346                     @Override
347                     public void consume(final CompletionResult result) {
348                       indicator.addItem(result);
349                     }
350                   }));
351                 }
352               });
353             }
354             catch (ProcessCanceledException ignored) {
355             }
356           }
357         }, indicator);
358       }
359     };
360
361     if (ApplicationManager.getApplication().isUnitTestMode() && !CompletionAutoPopupHandler.ourTestingAutopopup) {
362       computeRunnable.run();
363     } else {
364       ApplicationManager.getApplication().executeOnPooledThread(computeRunnable);
365     }
366
367     startSemaphore.waitFor();
368     return data;
369   }
370
371   private CompletionParameters createCompletionParameters(int invocationCount,
372                                                           CompletionInitializationContext initContext,
373                                                           final CompletionContext newContext) {
374
375     final int offset = newContext.getStartOffset();
376     final PsiFile fileCopy = newContext.file;
377     PsiFile originalFile = fileCopy.getOriginalFile();
378     final PsiElement insertedElement = findCompletionPositionLeaf(newContext, offset, fileCopy, originalFile);
379     insertedElement.putUserData(CompletionContext.COMPLETION_CONTEXT_KEY, newContext);
380     return new CompletionParameters(insertedElement, originalFile, myCompletionType, offset, invocationCount, obtainLookup(initContext.getEditor()), false);
381   }
382
383   @NotNull
384   private static PsiElement findCompletionPositionLeaf(CompletionContext newContext, int offset, PsiFile fileCopy, PsiFile originalFile) {
385     final PsiElement insertedElement = newContext.file.findElementAt(offset);
386     if (insertedElement == null) {
387       LOG.error(LogMessageEx.createEvent("No element at insertion offset", "offset=" + newContext.getStartOffset() + "\n" + DebugUtil.currentStackTrace(),
388                                          createFileTextAttachment(fileCopy, originalFile), createAstAttachment(fileCopy, originalFile)));
389     }
390
391     LOG.assertTrue(fileCopy.findElementAt(offset) == insertedElement, "wrong offset");
392
393     final TextRange range = insertedElement.getTextRange();
394     if (!range.substring(fileCopy.getText()).equals(insertedElement.getText())) {
395       LOG.error(LogMessageEx.createEvent("Inconsistent completion tree", "range=" + range + "\n" + DebugUtil.currentStackTrace(),
396                                          createFileTextAttachment(fileCopy, originalFile), createAstAttachment(fileCopy, originalFile),
397                                          new Attachment("Element at caret.txt", insertedElement.getText())));
398     }
399     return insertedElement;
400   }
401
402   private static Attachment createAstAttachment(PsiFile fileCopy, final PsiFile originalFile) {
403     return new Attachment(originalFile.getViewProvider().getVirtualFile().getPath() + " syntactic tree.txt", DebugUtil.psiToString(fileCopy, false));
404   }
405
406   private static Attachment createFileTextAttachment(PsiFile fileCopy, final PsiFile originalFile) {
407     return new Attachment(originalFile.getViewProvider().getVirtualFile().getPath(), fileCopy.getText());
408   }
409
410   private AutoCompletionDecision shouldAutoComplete(final CompletionProgressIndicator indicator, final LookupElement[] items) {
411     if (!invokedExplicitly) {
412       return AutoCompletionDecision.SHOW_LOOKUP;
413     }
414     final CompletionParameters parameters = indicator.getParameters();
415     final LookupElement item = items[0];
416     if (items.length == 1) {
417       final AutoCompletionPolicy policy = getAutocompletionPolicy(item);
418       if (policy == AutoCompletionPolicy.NEVER_AUTOCOMPLETE) return AutoCompletionDecision.SHOW_LOOKUP;
419       if (policy == AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE) return AutoCompletionDecision.insertItem(item);
420     }
421     if (!isAutocompleteOnInvocation(parameters.getCompletionType())) {
422       return AutoCompletionDecision.SHOW_LOOKUP;
423     }
424     if (isInsideIdentifier(indicator.getOffsetMap())) {
425       return AutoCompletionDecision.SHOW_LOOKUP;
426     }
427     if (items.length == 1 && getAutocompletionPolicy(item) == AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE) {
428       return AutoCompletionDecision.insertItem(item);
429     }
430
431     AutoCompletionContext context = new AutoCompletionContext(parameters, items, indicator.getOffsetMap(), indicator.getLookup());
432     for (final CompletionContributor contributor : CompletionContributor.forParameters(parameters)) {
433       final AutoCompletionDecision decision = contributor.handleAutoCompletionPossibility(context);
434       if (decision != null) {
435         return decision;
436       }
437     }
438
439     return AutoCompletionDecision.SHOW_LOOKUP;
440   }
441
442   @Nullable
443   private static AutoCompletionPolicy getAutocompletionPolicy(LookupElement element) {
444     final AutoCompletionPolicy policy = AutoCompletionPolicy.getPolicy(element);
445     if (policy != null) {
446       return policy;
447     }
448
449     final LookupItem item = element.as(LookupItem.CLASS_CONDITION_KEY);
450     if (item != null) {
451       return item.getAutoCompletionPolicy();
452     }
453
454     return null;
455   }
456
457   private static boolean isInsideIdentifier(final OffsetMap offsetMap) {
458     return offsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) != offsetMap.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET);
459   }
460
461
462   protected void completionFinished(final int offset1,
463                                     final int offset2,
464                                     final CompletionProgressIndicator indicator,
465                                     final LookupElement[] items, boolean hasModifiers) {
466     if (items.length == 0) {
467       LookupManager.getInstance(indicator.getProject()).hideActiveLookup();
468       indicator.handleEmptyLookup(true);
469       checkNotSync(indicator, items);
470       return;
471     }
472
473     LOG.assertTrue(!indicator.isRunning(), "running");
474     LOG.assertTrue(!indicator.isCanceled(), "canceled");
475
476     indicator.getLookup().refreshUi(true);
477     final AutoCompletionDecision decision = shouldAutoComplete(indicator, items);
478     if (decision == AutoCompletionDecision.SHOW_LOOKUP) {
479       CompletionServiceImpl.setCompletionPhase(new CompletionPhase.ItemsCalculated(indicator));
480       indicator.getLookup().setCalculating(false);
481       if (indicator.showLookup() && isAutocompleteCommonPrefixOnInvocation() && items.length > 1) {
482         indicator.fillInCommonPrefix(false);
483       }
484     }
485     else if (decision instanceof AutoCompletionDecision.InsertItem) {
486       final Runnable restorePrefix = rememberDocumentState(indicator.getEditor());
487
488       final LookupElement item = ((AutoCompletionDecision.InsertItem)decision).getElement();
489       CommandProcessor.getInstance().executeCommand(indicator.getProject(), new Runnable() {
490                                                       @Override
491                                                       public void run() {
492                                                         indicator.setMergeCommand();
493                                                         indicator.getLookup().finishLookup(Lookup.AUTO_INSERT_SELECT_CHAR, item);
494                                                       }
495                                                     }, "Autocompletion", null);
496
497
498
499       // the insert handler may have started a live template with completion
500       if (CompletionService.getCompletionService().getCurrentCompletion() == null &&
501           !ApplicationManager.getApplication().isUnitTestMode()) {
502         CompletionServiceImpl.setCompletionPhase(hasModifiers? new CompletionPhase.InsertedSingleItem(indicator, restorePrefix) : CompletionPhase.NoCompletion);
503       }
504       checkNotSync(indicator, items);
505     } else if (decision == AutoCompletionDecision.CLOSE_LOOKUP) {
506       LookupManager.getInstance(indicator.getProject()).hideActiveLookup();
507       checkNotSync(indicator, items);
508     }
509   }
510
511   private void insertDummyIdentifier(final CompletionInitializationContext initContext,
512                                      final boolean hasModifiers,
513                                      final int invocationCount) {
514     final PsiFile originalFile = initContext.getFile();
515     final PsiFile[] fileCopy = {null};
516     CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
517       @Override
518       public void run() {
519         AccessToken token = WriteAction.start();
520         try {
521           fileCopy[0] = createFileCopy(originalFile);
522         }
523         finally {
524           token.finish();
525         }
526       }
527     });
528     final PsiFile hostFile = InjectedLanguageUtil.getTopLevelFile(fileCopy[0]);
529     final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(hostFile.getProject());
530     final int hostStartOffset = injectedLanguageManager.injectedToHost(fileCopy[0], initContext.getStartOffset());
531     final Editor hostEditor = InjectedLanguageUtil.getTopLevelEditor(initContext.getEditor());
532
533     final OffsetMap hostMap = new OffsetMap(hostEditor.getDocument());
534     final OffsetMap original = initContext.getOffsetMap();
535     for (final OffsetKey key : new ArrayList<OffsetKey>(original.keySet())) {
536       hostMap.addOffset(key, injectedLanguageManager.injectedToHost(fileCopy[0], original.getOffset(key)));
537     }
538
539     final Document document = fileCopy[0].getViewProvider().getDocument();
540     assert document != null : "no document";
541     final OffsetTranslator translator = new OffsetTranslator(initContext.getEditor().getDocument(), initContext.getFile(), document);
542
543     CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
544       @Override
545       public void run() {
546         ApplicationManager.getApplication().runWriteAction(new Runnable() {
547           @Override
548           public void run() {
549             initContext.getFileCopyPatcher().patchFileCopy(fileCopy[0], document, initContext.getOffsetMap());
550           }
551         });
552       }
553     });
554     final Document hostDocument = hostFile.getViewProvider().getDocument();
555     assert hostDocument != null : "no host document";
556
557     final Project project = hostFile.getProject();
558
559     if (!synchronous) {
560       if (!CompletionServiceImpl.assertPhase(CompletionPhase.CommittingDocuments.class)) {
561         CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
562         Disposer.dispose(translator);
563         return;
564       }
565       
566       final CompletionPhase.CommittingDocuments phase = (CompletionPhase.CommittingDocuments)CompletionServiceImpl.getCompletionPhase();
567
568       CompletionAutoPopupHandler.runLaterWithCommitted(project, hostDocument, new Runnable() {
569         @Override
570         public void run() {
571           if (phase.checkExpired()) {
572             Disposer.dispose(translator);
573             return;
574           }
575           doComplete(initContext, hasModifiers, invocationCount, hostFile, hostStartOffset, hostEditor, hostMap, translator);
576         }
577       });
578     }
579     else {
580       PsiDocumentManager.getInstance(hostFile.getProject()).commitDocument(hostDocument);
581
582       doComplete(initContext, hasModifiers, invocationCount, hostFile, hostStartOffset, hostEditor, hostMap, translator);
583     }
584   }
585
586   private static CompletionContext createCompletionContext(PsiFile hostFile,
587                                                            int hostStartOffset,
588                                                            Editor hostEditor,
589                                                            OffsetMap hostMap) {
590     assert hostFile.isValid() : "file became invalid";
591     assert hostMap.getOffset(CompletionInitializationContext.START_OFFSET) < hostFile.getTextLength() : "startOffset outside the host file";
592
593     InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(hostFile.getProject());
594     CompletionContext context;
595     PsiFile injected = InjectedLanguageUtil.findInjectedPsiNoCommit(hostFile, hostStartOffset);
596     if (injected != null) {
597       TextRange host = injectedLanguageManager.injectedToHost(injected, injected.getTextRange());
598       assert hostStartOffset >= host.getStartOffset() : "startOffset before injected";
599       assert hostStartOffset <= host.getEndOffset() : "startOffset after injected";
600
601       EditorWindow injectedEditor = (EditorWindow)InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(hostEditor, hostFile, hostStartOffset);
602       assert injected == injectedEditor.getInjectedFile();
603       final OffsetMap map = new OffsetMap(injectedEditor.getDocument());
604       for (final OffsetKey key : new ArrayList<OffsetKey>(hostMap.keySet())) {
605         map.addOffset(key, injectedEditor.logicalPositionToOffset(injectedEditor.hostToInjected(hostEditor.offsetToLogicalPosition(hostMap.getOffset(key)))));
606       }
607       context = new CompletionContext(hostFile.getProject(), injectedEditor, injected, map);
608       assert hostStartOffset == injectedLanguageManager.injectedToHost(injected, context.getStartOffset()) : "inconsistent injected offset translation";
609     } else {
610       context = new CompletionContext(hostFile.getProject(), hostEditor, hostFile, hostMap);
611     }
612
613     assert context.getStartOffset() < context.file.getTextLength() : "start outside the file";
614     assert context.getStartOffset() >= 0 : "start < 0";
615
616     return context;
617   }
618
619   private boolean isAutocompleteCommonPrefixOnInvocation() {
620     return invokedExplicitly && CodeInsightSettings.getInstance().AUTOCOMPLETE_COMMON_PREFIX;
621   }
622
623   protected static void lookupItemSelected(final CompletionProgressIndicator indicator, @NotNull final LookupElement item, final char completionChar,
624                                          final List<LookupElement> items) {
625     if (indicator.isAutopopupCompletion()) {
626       FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_BASIC);
627     }
628
629     final CompletionLookupArranger.StatisticsUpdate update = CompletionLookupArranger.collectStatisticChanges(indicator, item);
630
631     final Editor editor = indicator.getEditor();
632
633     final int caretOffset = editor.getCaretModel().getOffset();
634     final int idDelta = indicator.getIdentifierEndOffset() - caretOffset;
635
636     WatchingInsertionContext context = null;
637     if (editor.getSelectionModel().hasBlockSelection() && editor.getSelectionModel().getBlockSelectionEnds().length > 0) {
638       List<RangeMarker> insertionPoints = new ArrayList<RangeMarker>();
639       for (int endOffset : editor.getSelectionModel().getBlockSelectionEnds()) {
640         insertionPoints.add(editor.getDocument().createRangeMarker(endOffset, endOffset));
641       }
642
643       List<RangeMarker> caretsAfter = new ArrayList<RangeMarker>();
644       for (RangeMarker insertionPoint : insertionPoints) {
645         if (insertionPoint.isValid()) {
646           context = insertItem(indicator, item, completionChar, items, update, editor, insertionPoint.getStartOffset(), idDelta);
647           int offset = editor.getCaretModel().getOffset();
648           caretsAfter.add(editor.getDocument().createRangeMarker(offset, offset));
649         }
650       }
651       assert context != null;
652
653       restoreBlockSelection(editor, caretsAfter);
654
655       for (RangeMarker insertionPoint : insertionPoints) {
656         insertionPoint.dispose();
657       }
658       for (RangeMarker marker : caretsAfter) {
659         marker.dispose();
660       }
661       
662     } else {
663       context = insertItem(indicator, item, completionChar, items, update, editor, caretOffset, idDelta);
664     }
665
666     final Runnable runnable = context.getLaterRunnable();
667     if (runnable != null) {
668       final Runnable runnable1 = new Runnable() {
669         @Override
670         public void run() {
671           if (!indicator.getProject().isDisposed()) {
672             runnable.run();
673           }
674           indicator.disposeIndicator();
675         }
676       };
677       if (ApplicationManager.getApplication().isUnitTestMode()) {
678         runnable1.run();
679       }
680       else {
681         ApplicationManager.getApplication().invokeLater(runnable1);
682       }
683     }
684     else {
685       indicator.disposeIndicator();
686     }
687   }
688
689   private static void restoreBlockSelection(Editor editor, List<RangeMarker> caretsAfter) {
690     int column = -1;
691     int minLine = Integer.MAX_VALUE;
692     int maxLine = -1;
693     for (RangeMarker marker : caretsAfter) {
694       if (marker.isValid()) {
695         LogicalPosition lp = editor.offsetToLogicalPosition(marker.getStartOffset());
696         if (column == -1) {
697           column = lp.column;
698         } else if (column != lp.column) {
699           return;
700         }
701         minLine = Math.min(minLine, lp.line);
702         maxLine = Math.max(maxLine, lp.line);
703       }
704     }
705     editor.getSelectionModel().setBlockSelection(new LogicalPosition(minLine, column), new LogicalPosition(maxLine, column));
706   }
707
708   private static WatchingInsertionContext insertItem(final CompletionProgressIndicator indicator,
709                                                      final LookupElement item,
710                                                      final char completionChar,
711                                                      List<LookupElement> items,
712                                                      final CompletionLookupArranger.StatisticsUpdate update,
713                                                      final Editor editor, final int caretOffset, final int idDelta) {
714     editor.getCaretModel().moveToOffset(caretOffset);
715     final int initialStartOffset = caretOffset - item.getLookupString().length();
716     assert initialStartOffset >= 0 : "negative startOffset: " + caretOffset + "; " + item.getLookupString();
717     final int idEndOffset = caretOffset + Math.max(idDelta, 0);
718     
719     indicator.getOffsetMap().addOffset(CompletionInitializationContext.START_OFFSET, initialStartOffset);
720     indicator.getOffsetMap().addOffset(CompletionInitializationContext.SELECTION_END_OFFSET, caretOffset);
721     indicator.getOffsetMap().addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, idEndOffset);
722
723     final WatchingInsertionContext context = new WatchingInsertionContext(indicator, completionChar, items, editor);
724     ApplicationManager.getApplication().runWriteAction(new Runnable() {
725       @Override
726       public void run() {
727         if (caretOffset != idEndOffset && completionChar == Lookup.REPLACE_SELECT_CHAR) {
728           editor.getDocument().deleteString(caretOffset, idEndOffset);
729         }
730
731         assert context.getStartOffset() >= 0 : "stale startOffset: was " + initialStartOffset + "; selEnd=" + caretOffset + "; idEnd=" + idEndOffset + "; file=" + context.getFile();
732         assert context.getTailOffset() >= 0 : "stale tail: was " + initialStartOffset + "; selEnd=" + caretOffset + "; idEnd=" + idEndOffset + "; file=" + context.getFile();
733
734         item.handleInsert(context);
735         Project project = indicator.getProject();
736         PostprocessReformattingAspect.getInstance(project).doPostponedFormatting();
737
738         if (context.shouldAddCompletionChar()) {
739           int tailOffset = context.getTailOffset();
740           if (tailOffset < 0) {
741             LOG.info("tailOffset<0 after inserting " + item + " of " + item.getClass() + "; invalidated at: " + context.invalidateTrace + "\n--------");
742             tailOffset = editor.getCaretModel().getOffset();
743           }
744           else {
745             editor.getCaretModel().moveToOffset(tailOffset);
746           }
747           if (context.getCompletionChar() == Lookup.COMPLETE_STATEMENT_SELECT_CHAR) {
748             final Language language = PsiUtilBase.getLanguageInEditor(editor, project);
749             final List<SmartEnterProcessor> processors = SmartEnterProcessors.INSTANCE.forKey(language);
750             if (processors.size() > 0) {
751               for (SmartEnterProcessor processor : processors) {
752                 processor.process(project, editor, indicator.getParameters().getOriginalFile());
753               }
754             }
755           }
756           else {
757             DataContext dataContext = DataManager.getInstance().getDataContext(editor.getContentComponent());
758             EditorActionManager.getInstance().getTypedAction().getHandler().execute(editor, completionChar, dataContext);
759           }
760         }
761         context.stopWatching();
762         editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
763         CompletionLookupArranger.trackStatistics(context, update);
764       }
765     });
766     return context;
767   }
768
769   @Override
770   public boolean startInWriteAction() {
771     return false;
772   }
773
774   public static final Key<SoftReference<Pair<PsiFile, Document>>> FILE_COPY_KEY = Key.create("CompletionFileCopy");
775
776   private static PsiFile createFileCopy(PsiFile file) {
777     final VirtualFile virtualFile = file.getVirtualFile();
778     if (file.isPhysical() && virtualFile != null && virtualFile.getFileSystem() == LocalFileSystem.getInstance()
779         // must not cache injected file copy, since it does not reflect changes in host document
780         && !InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) {
781       final SoftReference<Pair<PsiFile, Document>> reference = file.getUserData(FILE_COPY_KEY);
782       if (reference != null) {
783         final Pair<PsiFile, Document> pair = reference.get();
784         if (pair != null && pair.first.isValid() && pair.first.getClass().equals(file.getClass())) {
785           final PsiFile copy = pair.first;
786           if (copy.getModificationStamp() > file.getModificationStamp()) {
787             ((PsiModificationTrackerImpl) file.getManager().getModificationTracker()).incCounter();
788           }
789           final Document document = pair.second;
790           assert document != null;
791           document.setText(file.getText());
792           return copy;
793         }
794       }
795     }
796
797     final PsiFile copy = (PsiFile)file.copy();
798     final Document document = copy.getViewProvider().getDocument();
799     assert document != null;
800     file.putUserData(FILE_COPY_KEY, new SoftReference<Pair<PsiFile,Document>>(Pair.create(copy, document)));
801     return copy;
802   }
803
804   private static boolean isAutocompleteOnInvocation(final CompletionType type) {
805     final CodeInsightSettings settings = CodeInsightSettings.getInstance();
806     switch (type) {
807       case CLASS_NAME: return settings.AUTOCOMPLETE_ON_CLASS_NAME_COMPLETION;
808       case SMART: return settings.AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION;
809       case BASIC:
810       default: return settings.AUTOCOMPLETE_ON_CODE_COMPLETION;
811     }
812   }
813
814   public static Runnable rememberDocumentState(final Editor _editor) {
815     final Editor editor = InjectedLanguageUtil.getTopLevelEditor(_editor);
816     final String documentText = editor.getDocument().getText();
817     final int caret = editor.getCaretModel().getOffset();
818     final int selStart = editor.getSelectionModel().getSelectionStart();
819     final int selEnd = editor.getSelectionModel().getSelectionEnd();
820
821     final int vOffset = editor.getScrollingModel().getVerticalScrollOffset();
822     final int hOffset = editor.getScrollingModel().getHorizontalScrollOffset();
823
824     return new Runnable() {
825       @Override
826       public void run() {
827         DocumentEx document = (DocumentEx) editor.getDocument();
828
829         document.replaceString(0, document.getTextLength(), documentText);
830         editor.getCaretModel().moveToOffset(caret);
831         editor.getSelectionModel().setSelection(selStart, selEnd);
832
833         editor.getScrollingModel().scrollHorizontally(hOffset);
834         editor.getScrollingModel().scrollVertically(vOffset);
835       }
836     };
837   }
838
839   private static class WatchingInsertionContext extends InsertionContext {
840     private RangeMarkerEx tailWatcher;
841     String invalidateTrace;
842     DocumentEvent killer;
843     private RangeMarkerSpy spy;
844
845     public WatchingInsertionContext(CompletionProgressIndicator indicator, char completionChar, List<LookupElement> items, Editor editor) {
846       super(indicator.getOffsetMap(), completionChar, items.toArray(new LookupElement[items.size()]),
847             indicator.getParameters().getOriginalFile(), editor,
848             completionChar != Lookup.AUTO_INSERT_SELECT_CHAR && completionChar != Lookup.REPLACE_SELECT_CHAR &&
849             completionChar != Lookup.NORMAL_SELECT_CHAR);
850     }
851
852     @Override
853     public void setTailOffset(int offset) {
854       super.setTailOffset(offset);
855       watchTail(offset);
856     }
857
858     private void watchTail(int offset) {
859       stopWatching();
860       tailWatcher = (RangeMarkerEx)getDocument().createRangeMarker(offset, offset);
861       if (!tailWatcher.isValid()) {
862         throw new AssertionError(getDocument() + "; offset=" + offset);
863       }
864       tailWatcher.setGreedyToRight(true);
865       spy = new RangeMarkerSpy(tailWatcher) {
866         @Override
867         protected void invalidated(DocumentEvent e) {
868           if (ApplicationManager.getApplication().isUnitTestMode()) {
869             LOG.error("Tail offset invalidated, say thanks to the "+ e);
870           }
871
872           if (invalidateTrace == null) {
873             invalidateTrace = DebugUtil.currentStackTrace();
874             killer = e;
875           }
876         }
877       };
878       getDocument().addDocumentListener(spy);
879     }
880
881     void stopWatching() {
882       if (tailWatcher != null) {
883         getDocument().removeDocumentListener(spy);
884         tailWatcher.dispose();
885       }
886     }
887
888     @Override
889     public int getTailOffset() {
890       int offset = super.getTailOffset();
891       if (tailWatcher.getStartOffset() != tailWatcher.getEndOffset() && offset > 0) {
892         watchTail(offset);
893       }
894
895       return offset;
896     }
897   }
898 }