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