21a3ccab81ef173ea1a8065386bae8e642a3b006
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / lookup / impl / LookupManagerImpl.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2
3 package com.intellij.codeInsight.lookup.impl;
4
5 import com.intellij.codeInsight.CodeInsightSettings;
6 import com.intellij.codeInsight.completion.CompletionProcess;
7 import com.intellij.codeInsight.completion.CompletionService;
8 import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
9 import com.intellij.codeInsight.documentation.DocumentationManager;
10 import com.intellij.codeInsight.hint.EditorHintListener;
11 import com.intellij.codeInsight.hint.HintManager;
12 import com.intellij.codeInsight.lookup.*;
13 import com.intellij.openapi.Disposable;
14 import com.intellij.openapi.application.ApplicationManager;
15 import com.intellij.openapi.diagnostic.Logger;
16 import com.intellij.openapi.editor.Editor;
17 import com.intellij.openapi.editor.EditorFactory;
18 import com.intellij.openapi.editor.event.EditorFactoryEvent;
19 import com.intellij.openapi.editor.event.EditorFactoryListener;
20 import com.intellij.openapi.project.DumbService;
21 import com.intellij.openapi.project.IndexNotReadyException;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.util.Disposer;
24 import com.intellij.openapi.util.Key;
25 import com.intellij.ui.LightweightHint;
26 import com.intellij.util.Alarm;
27 import com.intellij.util.BitUtil;
28 import com.intellij.util.messages.MessageBus;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.TestOnly;
31
32 import java.beans.PropertyChangeListener;
33 import java.beans.PropertyChangeSupport;
34
35 public class LookupManagerImpl extends LookupManager {
36   private static final Logger LOG = Logger.getInstance(LookupManagerImpl.class);
37   private final Project myProject;
38   private LookupImpl myActiveLookup = null;
39   private Editor myActiveLookupEditor = null;
40   private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
41
42   public static final Key<Boolean> SUPPRESS_AUTOPOPUP_JAVADOC = Key.create("LookupManagerImpl.suppressAutopopupJavadoc");
43
44   public LookupManagerImpl(Project project, MessageBus bus) {
45     myProject = project;
46
47     bus.connect().subscribe(EditorHintListener.TOPIC, new EditorHintListener() {
48       @Override
49       public void hintShown(final Project project, @NotNull final LightweightHint hint, final int flags) {
50         if (project == myProject) {
51           Lookup lookup = getActiveLookup();
52           if (lookup != null && BitUtil.isSet(flags, HintManager.HIDE_BY_LOOKUP_ITEM_CHANGE)) {
53             lookup.addLookupListener(new LookupListener() {
54               @Override
55               public void currentItemChanged(@NotNull LookupEvent event) {
56                 hint.hide();
57               }
58
59               @Override
60               public void itemSelected(@NotNull LookupEvent event) {
61                 hint.hide();
62               }
63
64               @Override
65               public void lookupCanceled(@NotNull LookupEvent event) {
66                 hint.hide();
67               }
68             });
69           }
70         }
71       }
72     });
73
74     bus.connect().subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
75       @Override
76       public void enteredDumbMode() {
77         hideActiveLookup();
78       }
79
80       @Override
81       public void exitDumbMode() {
82         hideActiveLookup();
83       }
84     });
85
86
87     EditorFactory.getInstance().addEditorFactoryListener(new EditorFactoryListener() {
88       @Override
89       public void editorReleased(@NotNull EditorFactoryEvent event) {
90         if (event.getEditor() == myActiveLookupEditor) {
91           hideActiveLookup();
92         }
93       }
94     }, myProject);
95   }
96
97   @Override
98   public LookupEx showLookup(@NotNull final Editor editor,
99                            LookupElement @NotNull [] items,
100                            @NotNull final String prefix,
101                            @NotNull final LookupArranger arranger) {
102     for (LookupElement item : items) {
103       assert item != null;
104     }
105
106     LookupImpl lookup = createLookup(editor, items, prefix, arranger);
107     return lookup.showLookup() ? lookup : null;
108   }
109
110   @NotNull
111   @Override
112   public LookupImpl createLookup(@NotNull final Editor editor,
113                                  LookupElement @NotNull [] items,
114                                  @NotNull final String prefix,
115                                  @NotNull final LookupArranger arranger) {
116     hideActiveLookup();
117
118     final LookupImpl lookup = createLookup(editor, arranger, myProject);
119
120     final Alarm alarm = new Alarm();
121
122     ApplicationManager.getApplication().assertIsDispatchThread();
123
124     myActiveLookup = lookup;
125     myActiveLookupEditor = editor;
126     myActiveLookup.addLookupListener(new LookupListener() {
127       @Override
128       public void itemSelected(@NotNull LookupEvent event) {
129         lookupClosed();
130       }
131
132       @Override
133       public void lookupCanceled(@NotNull LookupEvent event) {
134         lookupClosed();
135       }
136
137       @Override
138       public void currentItemChanged(@NotNull LookupEvent event) {
139         alarm.cancelAllRequests();
140         CodeInsightSettings settings = CodeInsightSettings.getInstance();
141         if (settings.AUTO_POPUP_JAVADOC_INFO && DocumentationManager.getInstance(myProject).getDocInfoHint() == null) {
142           alarm.addRequest(() -> showJavadoc(lookup), settings.JAVADOC_INFO_DELAY);
143         }
144       }
145
146       private void lookupClosed() {
147         ApplicationManager.getApplication().assertIsDispatchThread();
148         alarm.cancelAllRequests();
149         lookup.removeLookupListener(this);
150       }
151     });
152     Disposer.register(lookup, new Disposable() {
153       @Override
154       public void dispose() {
155         myActiveLookup = null;
156         myActiveLookupEditor = null;
157         myPropertyChangeSupport.firePropertyChange(PROP_ACTIVE_LOOKUP, lookup, null);
158       }
159     });
160
161     if (items.length > 0) {
162       CamelHumpMatcher matcher = new CamelHumpMatcher(prefix);
163       for (LookupElement item : items) {
164         myActiveLookup.addItem(item, matcher);
165       }
166       myActiveLookup.refreshUi(true, true);
167     }
168     else {
169       alarm.cancelAllRequests(); // no items -> no doc
170     }
171
172     myPropertyChangeSupport.firePropertyChange(PROP_ACTIVE_LOOKUP, null, myActiveLookup);
173     return lookup;
174   }
175
176   private void showJavadoc(LookupImpl lookup) {
177     if (myActiveLookup != lookup) return;
178
179     DocumentationManager docManager = DocumentationManager.getInstance(myProject);
180     if (docManager.getDocInfoHint() != null) return; // will auto-update
181
182     LookupElement currentItem = lookup.getCurrentItem();
183     CompletionProcess completion = CompletionService.getCompletionService().getCurrentCompletion();
184     if (currentItem != null && currentItem.isValid() && isAutoPopupJavadocSupportedBy(currentItem) && completion != null) {
185       try {
186         boolean hideLookupWithDoc = completion.isAutopopupCompletion() || CodeInsightSettings.getInstance().JAVADOC_INFO_DELAY == 0;
187         docManager.showJavaDocInfo(lookup.getEditor(), lookup.getPsiFile(), false, () -> {
188           if (hideLookupWithDoc && completion == CompletionService.getCompletionService().getCurrentCompletion()) {
189             hideActiveLookup();
190           }
191         });
192       }
193       catch (IndexNotReadyException ignored) {
194       }
195     }
196   }
197
198   protected boolean isAutoPopupJavadocSupportedBy(@SuppressWarnings("unused") LookupElement lookupItem) {
199     return lookupItem.getUserData(SUPPRESS_AUTOPOPUP_JAVADOC) == null;
200   }
201
202   @NotNull
203   protected LookupImpl createLookup(@NotNull Editor editor, @NotNull LookupArranger arranger, Project project) {
204     return new LookupImpl(project, editor, arranger);
205   }
206
207   @Override
208   public void hideActiveLookup() {
209     LookupImpl lookup = myActiveLookup;
210     if (lookup != null) {
211       lookup.checkValid();
212       lookup.hide();
213       LOG.assertTrue(lookup.isLookupDisposed(), "Should be disposed");
214     }
215   }
216
217   @Override
218   public LookupEx getActiveLookup() {
219     if (myActiveLookup != null && myActiveLookup.isLookupDisposed()) {
220       LookupImpl lookup = myActiveLookup;
221       myActiveLookup = null;
222       lookup.checkValid();
223     }
224
225     return myActiveLookup;
226   }
227
228   @Override
229   public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
230     myPropertyChangeSupport.addPropertyChangeListener(listener);
231   }
232
233   @Override
234   public void addPropertyChangeListener(@NotNull final PropertyChangeListener listener, @NotNull Disposable disposable) {
235     addPropertyChangeListener(listener);
236     Disposer.register(disposable, new Disposable() {
237       @Override
238       public void dispose() {
239         removePropertyChangeListener(listener);
240       }
241     });
242   }
243
244   @Override
245   public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
246     myPropertyChangeSupport.removePropertyChangeListener(listener);
247   }
248
249
250   @TestOnly
251   public void forceSelection(char completion, int index){
252     if(myActiveLookup == null) throw new RuntimeException("There are no items in this lookup");
253     final LookupElement lookupItem = myActiveLookup.getItems().get(index);
254     myActiveLookup.setCurrentItem(lookupItem);
255     myActiveLookup.finishLookup(completion);
256   }
257
258   @TestOnly
259   public void forceSelection(char completion, LookupElement item){
260     myActiveLookup.setCurrentItem(item);
261     myActiveLookup.finishLookup(completion);
262   }
263
264   @TestOnly
265   public void clearLookup() {
266     if (myActiveLookup != null) {
267       myActiveLookup.hide();
268       myActiveLookup = null;
269     }
270   }
271 }