Merge branch 'master' of git@git.labs.intellij.net:idea/community
[idea/community.git] / platform / lang-impl / src / com / intellij / ide / util / gotoByName / ChooseByNameBase.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.ide.util.gotoByName;
18
19 import com.intellij.Patches;
20 import com.intellij.ide.IdeBundle;
21 import com.intellij.ide.actions.CopyReferenceAction;
22 import com.intellij.ide.ui.UISettings;
23 import com.intellij.openapi.MnemonicHelper;
24 import com.intellij.openapi.actionSystem.*;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ModalityState;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.editor.colors.EditorColorsManager;
29 import com.intellij.openapi.editor.colors.EditorColorsScheme;
30 import com.intellij.openapi.keymap.KeymapManager;
31 import com.intellij.openapi.progress.ProcessCanceledException;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.ui.popup.*;
34 import com.intellij.openapi.util.ActionCallback;
35 import com.intellij.openapi.util.Comparing;
36 import com.intellij.openapi.util.Computable;
37 import com.intellij.openapi.util.Pair;
38 import com.intellij.openapi.util.registry.Registry;
39 import com.intellij.openapi.util.text.StringUtil;
40 import com.intellij.openapi.wm.IdeFocusManager;
41 import com.intellij.openapi.wm.WindowManager;
42 import com.intellij.openapi.wm.ex.WindowManagerEx;
43 import com.intellij.psi.PsiElement;
44 import com.intellij.psi.codeStyle.NameUtil;
45 import com.intellij.psi.statistics.StatisticsInfo;
46 import com.intellij.psi.statistics.StatisticsManager;
47 import com.intellij.psi.util.proximity.PsiProximityComparator;
48 import com.intellij.ui.DocumentAdapter;
49 import com.intellij.ui.ListScrollingUtil;
50 import com.intellij.ui.ScrollPaneFactory;
51 import com.intellij.ui.components.JBList;
52 import com.intellij.ui.popup.PopupOwner;
53 import com.intellij.ui.popup.PopupUpdateProcessor;
54 import com.intellij.util.Alarm;
55 import com.intellij.util.Function;
56 import com.intellij.util.SmartList;
57 import com.intellij.util.containers.ContainerUtil;
58 import com.intellij.util.diff.Diff;
59 import com.intellij.util.ui.UIUtil;
60 import org.jetbrains.annotations.NonNls;
61 import org.jetbrains.annotations.Nullable;
62
63 import javax.swing.*;
64 import javax.swing.border.EmptyBorder;
65 import javax.swing.event.DocumentEvent;
66 import javax.swing.event.ListSelectionEvent;
67 import javax.swing.event.ListSelectionListener;
68 import javax.swing.text.DefaultEditorKit;
69 import java.awt.*;
70 import java.awt.event.*;
71 import java.lang.ref.Reference;
72 import java.lang.ref.WeakReference;
73 import java.util.*;
74 import java.util.List;
75
76 public abstract class ChooseByNameBase {
77   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.gotoByName.ChooseByNameBase");
78
79   protected final Project myProject;
80   protected final ChooseByNameModel myModel;
81   protected final String myInitialText;
82   private boolean myPreselectInitialText;
83   private final Reference<PsiElement> myContext;
84
85   protected Component myPreviouslyFocusedComponent;
86
87   protected JPanelProvider myTextFieldPanel;// Located in the layered pane
88   protected MyTextField myTextField;
89   private JPanel myCardContainer;
90   private CardLayout myCard;
91   protected JCheckBox myCheckBox;
92   /**
93    * the tool area of the popup, it is just after card box
94    */
95   protected JComponent myToolArea;
96
97   protected JScrollPane myListScrollPane; // Located in the layered pane
98   protected JList myList;
99   private DefaultListModel myListModel;
100   private List<Pair<String, Integer>> myHistory;
101   private List<Pair<String, Integer>> myFuture;
102
103   protected ChooseByNamePopupComponent.Callback myActionListener;
104
105   protected final Alarm myAlarm = new Alarm();
106
107   private final ListUpdater myListUpdater = new ListUpdater();
108
109   private volatile boolean myListIsUpToDate = false;
110   protected boolean myDisposedFlag = false;
111   private ActionCallback myPosponedOkAction;
112
113   private final String[][] myNames = new String[2][];
114   private CalcElementsThread myCalcElementsThread;
115   private static int VISIBLE_LIST_SIZE_LIMIT = 10;
116   private static final int MAXIMUM_LIST_SIZE_LIMIT = 30;
117   private int myMaximumListSizeLimit = MAXIMUM_LIST_SIZE_LIMIT;
118   @NonNls private static final String NOT_FOUND_IN_PROJECT_CARD = "syslib";
119   @NonNls private static final String NOT_FOUND_CARD = "nfound";
120   @NonNls private static final String CHECK_BOX_CARD = "chkbox";
121   @NonNls private static final String SEARCHING_CARD = "searching";
122   private static final int REBUILD_DELAY = 300;
123
124   private final Alarm myHideAlarm = new Alarm();
125   private boolean myShowListAfterCompletionKeyStroke = false;
126   protected JBPopup myTextPopup;
127   protected JBPopup myDropdownPopup;
128
129   private static class MatchesComparator implements Comparator<String> {
130     private final String myOriginalPattern;
131
132     private MatchesComparator(final String originalPattern) {
133       myOriginalPattern = originalPattern.trim();
134     }
135
136     public int compare(final String a, final String b) {
137       boolean aStarts = a.startsWith(myOriginalPattern);
138       boolean bStarts = b.startsWith(myOriginalPattern);
139       if (aStarts && bStarts) return a.compareToIgnoreCase(b);
140       if (aStarts && !bStarts) return -1;
141       if (bStarts && !aStarts) return 1;
142       return a.compareToIgnoreCase(b);
143     }
144   }
145
146   /**
147    * @param initialText initial text which will be in the lookup text field
148    * @param context
149    */
150   protected ChooseByNameBase(Project project, ChooseByNameModel model, String initialText, final PsiElement context) {
151     myProject = project;
152     myModel = model;
153     myInitialText = initialText;
154     myContext = new WeakReference<PsiElement>(context);
155   }
156
157   public boolean isPreselectInitialText() {
158     return myPreselectInitialText;
159   }
160
161   public void setPreselectInitialText(boolean preselectInitialText) {
162     myPreselectInitialText = preselectInitialText;
163   }
164
165   public void setShowListAfterCompletionKeyStroke(boolean showListAfterCompletionKeyStroke) {
166     myShowListAfterCompletionKeyStroke = showListAfterCompletionKeyStroke;
167   }
168
169   /**
170    * Set tool area. The method may be called only before invoke.
171    *
172    * @param toolArea a tool area component
173    */
174   public void setToolArea(JComponent toolArea) {
175     if (myCard != null) {
176       throw new IllegalStateException("Tool area is modifiable only before invoke()");
177     }
178     myToolArea = toolArea;
179   }
180
181   public void invoke(final ChooseByNamePopupComponent.Callback callback,
182                      final ModalityState modalityState,
183                      boolean allowMultipleSelection) {
184     initUI(callback, modalityState, allowMultipleSelection);
185   }
186
187   public class JPanelProvider extends JPanel implements DataProvider {
188     JBPopup myHint = null;
189     boolean myFocusRequested = false;
190
191     JPanelProvider() {
192     }
193
194     public Object getData(String dataId) {
195       if (PlatformDataKeys.HELP_ID.is(dataId)) {
196         return myModel.getHelpId();
197       }
198       if (!myListIsUpToDate) {
199         return null;
200       }
201       if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
202         Object element = getChosenElement();
203
204         if (element instanceof PsiElement) {
205           return element;
206         }
207
208         if (element instanceof DataProvider) {
209           return ((DataProvider)element).getData(dataId);
210         }
211       }
212       else if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
213         final List<Object> chosenElements = getChosenElements();
214         if (chosenElements != null) {
215           List<PsiElement> result = new ArrayList<PsiElement>();
216           for (Object element : chosenElements) {
217             if (element instanceof PsiElement) {
218               result.add((PsiElement)element);
219             }
220           }
221           return result.toArray(new PsiElement[result.size()]);
222         }
223       }
224       else if (PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.is(dataId)) {
225         return getBounds();
226       }
227       return null;
228     }
229
230     public void registerHint(JBPopup h) {
231       if (myHint != null && myHint.isVisible() && myHint != h) {
232         myHint.cancel();
233       }
234       myHint = h;
235     }
236
237     public boolean focusRequested() {
238       boolean focusRequested = myFocusRequested;
239
240       myFocusRequested = false;
241
242       return focusRequested;
243     }
244
245     public void requestFocus() {
246       myFocusRequested = true;
247     }
248
249     public void unregisterHint() {
250       myHint = null;
251     }
252
253     public void hideHint() {
254       if (myHint != null) {
255         myHint.cancel();
256       }
257     }
258
259     public JBPopup getHint() {
260       return myHint;
261     }
262
263     public void updateHint(PsiElement element) {
264       if (myHint == null || !myHint.isVisible()) return;
265       final PopupUpdateProcessor updateProcessor = myHint.getUserData(PopupUpdateProcessor.class);
266       if (updateProcessor != null) {
267         myHint.cancel();
268         updateProcessor.updatePopup(element);
269       }
270     }
271   }
272
273   /**
274    * @param callback
275    * @param modalityState          - if not null rebuilds list in given {@link ModalityState}
276    * @param allowMultipleSelection
277    */
278   protected void initUI(final ChooseByNamePopupComponent.Callback callback,
279                         final ModalityState modalityState,
280                         boolean allowMultipleSelection) {
281     myPreviouslyFocusedComponent = WindowManagerEx.getInstanceEx().getFocusedComponent(myProject);
282
283     myActionListener = callback;
284     myTextFieldPanel = new JPanelProvider();
285     myTextFieldPanel.setLayout(new BoxLayout(myTextFieldPanel, BoxLayout.Y_AXIS));
286
287     final JPanel hBox = new JPanel();
288     hBox.setLayout(new BoxLayout(hBox, BoxLayout.X_AXIS));
289
290     JPanel caption2Tools = new JPanel(new BorderLayout());
291
292     if (myModel.getPromptText() != null) {
293       JLabel label = new JLabel(myModel.getPromptText());
294       label.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD));
295       caption2Tools.add(label, BorderLayout.WEST);
296     }
297
298     GridBagLayout gb = new GridBagLayout();
299     JPanel eastWrapper = new JPanel(gb);
300     gb.setConstraints(hBox, new GridBagConstraints(0, 0, 0, 0, 1, 1, GridBagConstraints.SOUTHEAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
301     eastWrapper.add(hBox);
302
303     caption2Tools.add(eastWrapper, BorderLayout.CENTER);
304
305     myCard = new CardLayout();
306     myCardContainer = new JPanel(myCard);
307
308     final JPanel checkBoxPanel = new JPanel();
309     myCheckBox = new JCheckBox(myModel.getCheckBoxName());
310     myCheckBox.setAlignmentX(SwingConstants.RIGHT);
311     myCheckBox.setSelected(myModel.loadInitialCheckBoxState());
312
313     if (myModel.getPromptText() != null) {
314       checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.X_AXIS));
315       checkBoxPanel.add(myCheckBox);
316     }
317     else {
318       checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.LINE_AXIS));
319       checkBoxPanel.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
320       checkBoxPanel.add(myCheckBox);
321     }
322     checkBoxPanel.setVisible(myModel.getCheckBoxName() != null);
323     JPanel panel = new JPanel(new BorderLayout());
324     panel.add(checkBoxPanel, BorderLayout.CENTER);
325     myCardContainer.add(panel, CHECK_BOX_CARD);
326
327     myCardContainer.add(new HintLabel(myModel.getNotInMessage()), NOT_FOUND_IN_PROJECT_CARD);
328     myCardContainer.add(new HintLabel(IdeBundle.message("label.choosebyname.no.matches.found")), NOT_FOUND_CARD);
329     myCardContainer.add(new HintLabel(IdeBundle.message("label.choosebyname.searching")), SEARCHING_CARD);
330     myCard.show(myCardContainer, CHECK_BOX_CARD);
331
332     if (isCheckboxVisible()) {
333       hBox.add(myCardContainer);
334     }
335
336     if (myToolArea != null) {
337       hBox.add(Box.createHorizontalStrut(4));
338       hBox.add(myToolArea);
339     }
340     myTextFieldPanel.add(caption2Tools);
341
342     myHistory = new ArrayList<Pair<String, Integer>>();
343     myFuture = new ArrayList<Pair<String, Integer>>();
344     myTextField = new MyTextField();
345     myTextField.setText(myInitialText);
346     if (myPreselectInitialText) {
347       myTextField.select(0, myInitialText.length());
348     }
349
350     final ActionMap actionMap = new ActionMap();
351     actionMap.setParent(myTextField.getActionMap());
352     actionMap.put(DefaultEditorKit.copyAction, new AbstractAction() {
353       public void actionPerformed(ActionEvent e) {
354         if (myTextField.getSelectedText() != null) {
355           actionMap.getParent().get(DefaultEditorKit.copyAction).actionPerformed(e);
356           return;
357         }
358         final Object chosenElement = getChosenElement();
359         if (chosenElement instanceof PsiElement) {
360           CopyReferenceAction.doCopy((PsiElement)chosenElement, myProject);
361         }
362       }
363     });
364     myTextField.setActionMap(actionMap);
365
366     myTextFieldPanel.add(myTextField);
367     EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
368     Font editorFont = new Font(scheme.getEditorFontName(), Font.PLAIN, scheme.getEditorFontSize());
369     myTextField.setFont(editorFont);
370
371     if (isCloseByFocusLost()) {
372       myTextField.addFocusListener(new FocusAdapter() {
373         public void focusLost(final FocusEvent e) {
374           myHideAlarm.addRequest(new Runnable() {
375             public void run() {
376               JBPopup popup = JBPopupFactory.getInstance().getChildFocusedPopup(e.getComponent());
377               if (popup != null) {
378                 popup.addListener(new JBPopupListener.Adapter() {
379                   @Override
380                   public void onClosed(LightweightWindowEvent event) {
381                     if (event.isOk()) {
382                       hideHint();
383                     }
384                   }
385                 });
386               }
387               else {
388                 hideHint();
389               }
390             }
391           }, 200);
392         }
393       });
394     }
395
396     myCheckBox.addItemListener(new ItemListener() {
397       public void itemStateChanged(ItemEvent e) {
398         rebuildList();
399       }
400     });
401     myCheckBox.setFocusable(false);
402
403     myTextField.getDocument().addDocumentListener(new DocumentAdapter() {
404       protected void textChanged(DocumentEvent e) {
405         clearPosponedOkAction(false);
406         rebuildList();
407       }
408     });
409
410     myTextField.addKeyListener(new KeyAdapter() {
411       public void keyPressed(KeyEvent e) {
412         if (!myListScrollPane.isVisible()) {
413           return;
414         }
415         final int keyCode = e.getKeyCode();
416         switch (keyCode) {
417           case KeyEvent.VK_DOWN:
418             ListScrollingUtil.moveDown(myList, e.getModifiersEx());
419             break;
420           case KeyEvent.VK_UP:
421             ListScrollingUtil.moveUp(myList, e.getModifiersEx());
422             break;
423           case KeyEvent.VK_PAGE_UP:
424             ListScrollingUtil.movePageUp(myList);
425             break;
426           case KeyEvent.VK_PAGE_DOWN:
427             ListScrollingUtil.movePageDown(myList);
428             break;
429           case KeyEvent.VK_ENTER:
430             if (myList.getSelectedValue() == EXTRA_ELEM) {
431               myMaximumListSizeLimit += MAXIMUM_LIST_SIZE_LIMIT;
432               rebuildList(myList.getSelectedIndex(), REBUILD_DELAY, null, ModalityState.current(), e);
433               e.consume();
434             }
435             break;
436         }
437       }
438     });
439
440     myTextField.addActionListener(new ActionListener() {
441       public void actionPerformed(ActionEvent actionEvent) {
442         doClose(true);
443       }
444     });
445
446     myListModel = new DefaultListModel();
447     myList = new JBList(myListModel);
448     myList.setFocusable(false);
449     myList.setSelectionMode(allowMultipleSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION :
450                             ListSelectionModel.SINGLE_SELECTION);
451     myList.addMouseListener(new MouseAdapter() {
452       public void mouseClicked(MouseEvent e) {
453         if (!myTextField.hasFocus()) {
454           myTextField.requestFocus();
455         }
456
457         if (e.getClickCount() == 2) {
458           if (myList.getSelectedValue() == EXTRA_ELEM) {
459             myMaximumListSizeLimit += MAXIMUM_LIST_SIZE_LIMIT;
460             rebuildList(myList.getSelectedIndex(), REBUILD_DELAY, null, ModalityState.current(), e);
461             e.consume();
462           }
463           else {
464             doClose(true);
465           }
466         }
467       }
468     });
469     myList.setCellRenderer(myModel.getListCellRenderer());
470     myList.setFont(editorFont);
471
472     myList.addListSelectionListener(new ListSelectionListener() {
473       public void valueChanged(ListSelectionEvent e) {
474         choosenElementMightChange();
475         updateDocumentation();
476       }
477     });
478
479     myListScrollPane = ScrollPaneFactory.createScrollPane(myList);
480     myListScrollPane.setViewportBorder(new EmptyBorder(0, 0, 0, 0));
481
482     myTextFieldPanel.setBorder(new EmptyBorder(2, 2, 2, 2));
483
484     showTextFieldPanel();
485
486     if (modalityState != null) {
487       rebuildList(0, 0, null, modalityState, null);
488     }
489   }
490
491   private void hideHint() {
492     if (!myTextFieldPanel.focusRequested()) {
493       doClose(false);
494       myTextFieldPanel.hideHint();
495     }
496   }
497
498   /**
499    * Default rebuild list. It uses {@link #REBUILD_DELAY} and current modality state.
500    */
501   public void rebuildList() {
502     // TODO this method is public, because the chooser does not listed for the model.
503     rebuildList(0, REBUILD_DELAY, null, ModalityState.current(), null);
504   }
505
506   private void updateDocumentation() {
507     final JBPopup hint = myTextFieldPanel.getHint();
508     final Object element = getChosenElement();
509     if (hint != null) {
510       if (element instanceof PsiElement) {
511         myTextFieldPanel.updateHint((PsiElement)element);
512       }
513       else if (element instanceof DataProvider) {
514         final Object o = ((DataProvider)element).getData(LangDataKeys.PSI_ELEMENT.getName());
515         if (o instanceof PsiElement) {
516           myTextFieldPanel.updateHint((PsiElement)o);
517         }
518       }
519     }
520   }
521
522   private void doClose(final boolean ok) {
523     if (myDisposedFlag) return;
524
525     if (posponeCloseWhenListReady(ok)) return;
526
527     cancelListUpdater();
528     close(ok);
529
530     clearPosponedOkAction(ok);
531   }
532
533   protected void cancelListUpdater() {
534     myListUpdater.cancelAll();
535   }
536
537   private boolean posponeCloseWhenListReady(boolean ok) {
538     if (!Registry.is("actionSystem.fixLostTyping")) return false;
539
540     final String text = myTextField.getText();
541     if (ok && !myListIsUpToDate && text != null && text.trim().length() > 0) {
542       myPosponedOkAction = new ActionCallback();
543       IdeFocusManager.getInstance(myProject).suspendKeyProcessingUntil(myPosponedOkAction);
544       return true;
545     }
546
547     return false;
548   }
549
550   private synchronized void ensureNamesLoaded(boolean checkboxState) {
551     int index = checkboxState ? 1 : 0;
552     if (myNames[index] != null) return;
553
554     Window window = (Window)SwingUtilities.getAncestorOfClass(Window.class, myTextField);
555     //LOG.assertTrue (myTextField != null);
556     //LOG.assertTrue (window != null);
557     Window ownerWindow = null;
558     if (window != null) {
559       window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
560       ownerWindow = window.getOwner();
561       if (ownerWindow != null) {
562         ownerWindow.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
563       }
564     }
565     myNames[index] = myModel.getNames(checkboxState);
566
567     if (window != null) {
568       window.setCursor(Cursor.getDefaultCursor());
569       if (ownerWindow != null) {
570         ownerWindow.setCursor(Cursor.getDefaultCursor());
571       }
572     }
573   }
574
575   protected abstract boolean isCheckboxVisible();
576
577   protected abstract boolean isShowListForEmptyPattern();
578
579   protected abstract boolean isCloseByFocusLost();
580
581   protected void showTextFieldPanel() {
582     final JLayeredPane layeredPane = getLayeredPane();
583     final Dimension preferredTextFieldPanelSize = myTextFieldPanel.getPreferredSize();
584     final int x = (layeredPane.getWidth() - preferredTextFieldPanelSize.width) / 2;
585     final int paneHeight = layeredPane.getHeight();
586     final int y = paneHeight / 3 - preferredTextFieldPanelSize.height / 2;
587
588     VISIBLE_LIST_SIZE_LIMIT = Math.max
589       (10, (paneHeight - (y + preferredTextFieldPanelSize.height)) / (preferredTextFieldPanelSize.height / 2) - 1);
590
591     ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(myTextFieldPanel, myTextField);
592     builder.setCancelCallback(new Computable<Boolean>() {
593       @Override
594       public Boolean compute() {
595         myTextPopup = null;
596         close(false);
597         return Boolean.TRUE;
598       }
599     }).setFocusable(true).setRequestFocus(true).setForceHeavyweight(true).setModalContext(false).setCancelOnClickOutside(false);
600
601     Point point = new Point(x, y);
602     SwingUtilities.convertPointToScreen(point, layeredPane);
603     Rectangle bounds = new Rectangle(point, new Dimension(preferredTextFieldPanelSize.width + 20, preferredTextFieldPanelSize.height));
604     myTextPopup = builder.createPopup();
605     myTextPopup.setSize(bounds.getSize());
606     myTextPopup.setLocation(bounds.getLocation());
607
608     new MnemonicHelper().register(myTextFieldPanel);
609     myTextPopup.show(layeredPane);
610   }
611
612   private JLayeredPane getLayeredPane() {
613     JLayeredPane layeredPane;
614     final Window window = WindowManager.getInstance().suggestParentWindow(myProject);
615
616     Component parent = UIUtil.findUltimateParent(window);
617
618     if (parent instanceof JFrame) {
619       layeredPane = ((JFrame)parent).getLayeredPane();
620     }
621     else if (parent instanceof JDialog) {
622       layeredPane = ((JDialog)parent).getLayeredPane();
623     }
624     else {
625       throw new IllegalStateException("cannot find parent window: project=" + myProject +
626                                       (myProject != null ? "; open=" + myProject.isOpen() : "") +
627                                       "; window=" + window);
628     }
629     return layeredPane;
630   }
631
632   private final Object myRebuildMutex = new Object();
633
634   protected void rebuildList(final int pos,
635                              final int delay,
636                              final Runnable postRunnable,
637                              final ModalityState modalityState,
638                              final @Nullable ComponentEvent e) {
639     ApplicationManager.getApplication().assertIsDispatchThread();
640     myListIsUpToDate = false;
641     myAlarm.cancelAllRequests();
642     myListUpdater.cancelAll();
643
644     cancelCalcElementsThread();
645     ApplicationManager.getApplication().invokeLater(new Runnable() {
646       public void run() {
647         final String text = myTextField.getText();
648         if (!canShowListForEmptyPattern() &&
649             (text == null || text.trim().length() == 0)) {
650           myListModel.clear();
651           hideList();
652           myCard.show(myCardContainer, CHECK_BOX_CARD);
653           return;
654         }
655         final Runnable request = new Runnable() {
656           public void run() {
657             final CalcElementsCallback callback = new CalcElementsCallback() {
658               public void run(final Set<?> elements) {
659                 synchronized (myRebuildMutex) {
660                   ApplicationManager.getApplication().assertIsDispatchThread();
661                   if (myDisposedFlag) {
662                     return;
663                   }
664
665                   setElementsToList(pos, elements);
666
667                   myListIsUpToDate = true;
668                   choosenElementMightChange();
669
670                   if (postRunnable != null) {
671                     postRunnable.run();
672                   }
673                 }
674               }
675             };
676
677             cancelCalcElementsThread();
678
679             myCalcElementsThread = new CalcElementsThread(text, myCheckBox.isSelected(), callback, modalityState, postRunnable == null);
680             ApplicationManager.getApplication().executeOnPooledThread(myCalcElementsThread);
681           }
682         };
683
684         if (delay > 0) {
685           myAlarm.addRequest(request, delay, ModalityState.stateForComponent(myTextField));
686         }
687         else {
688           request.run();
689         }
690       }
691     }, modalityState);
692   }
693
694   private boolean isShowListAfterCompletionKeyStroke() {
695     return myShowListAfterCompletionKeyStroke;
696   }
697
698   private void cancelCalcElementsThread() {
699     if (myCalcElementsThread != null) {
700       myCalcElementsThread.cancel();
701       myCalcElementsThread = null;
702     }
703   }
704
705   private void setElementsToList(int pos, Set<?> elements) {
706     myListUpdater.cancelAll();
707     if (myDisposedFlag) return;
708     if (elements.isEmpty()) {
709       myListModel.clear();
710       myTextField.setForeground(Color.red);
711       myListUpdater.cancelAll();
712       hideList();
713       clearPosponedOkAction(false);
714       return;
715     }
716
717     Object[] oldElements = myListModel.toArray();
718     Object[] newElements = elements.toArray();
719     Diff.Change change = Diff.buildChanges(oldElements, newElements);
720
721     if (change == null) return; // Nothing changed
722
723     List<Cmd> commands = new ArrayList<Cmd>();
724     int inserted = 0;
725     int deleted = 0;
726     while (change != null) {
727       if (change.deleted > 0) {
728         final int start = change.line0 + inserted - deleted;
729         commands.add(new RemoveCmd(start, start + change.deleted - 1));
730       }
731
732       if (change.inserted > 0) {
733         for (int i = 0; i < change.inserted; i++) {
734           commands.add(new InsertCmd(change.line0 + i + inserted - deleted, newElements[change.line1 + i]));
735         }
736       }
737
738       deleted += change.deleted;
739       inserted += change.inserted;
740       change = change.link;
741     }
742
743     myTextField.setForeground(UIUtil.getTextFieldForeground());
744     if (!commands.isEmpty()) {
745       showList();
746       myListUpdater.appendToModel(commands, pos);
747     }
748     else {
749       if (pos == 0) {
750         pos = detectBestStatisticalPosition();
751       }
752
753       ListScrollingUtil.selectItem(myList, Math.min(pos, myListModel.size() - 1));
754       myList.setVisibleRowCount(Math.min(VISIBLE_LIST_SIZE_LIMIT, myList.getModel().getSize()));
755       showList();
756     }
757   }
758
759   private int detectBestStatisticalPosition() {
760     int best = 0;
761     int bestPosition = 0;
762     final int count = myListModel.getSize();
763
764     final String statContext = statisticsContext();
765     for (int i = 0; i < count; i++) {
766       final Object modelElement = myListModel.getElementAt(i);
767       String text = EXTRA_ELEM.equals(modelElement) ? null : myModel.getFullName(modelElement);
768       if (text != null) {
769         int stats = StatisticsManager.getInstance().getUseCount(new StatisticsInfo(statContext, text));
770         if (stats > best) {
771           best = stats;
772           bestPosition = i;
773         }
774       }
775     }
776
777     return bestPosition;
778   }
779
780   @NonNls
781   protected String statisticsContext() {
782     return "choose_by_name#" + myModel.getPromptText() + "#" + myCheckBox.isSelected() + "#" + myTextField.getText();
783   }
784
785   private String getQualifierPattern(String pattern) {
786     final String[] separators = myModel.getSeparators();
787     int lastSeparatorOccurence = 0;
788     for (String separator : separators) {
789       lastSeparatorOccurence = Math.max(lastSeparatorOccurence, pattern.lastIndexOf(separator));
790     }
791     return pattern.substring(0, lastSeparatorOccurence);
792   }
793
794   public String getNamePattern(String pattern) {
795     final String[] separators = myModel.getSeparators();
796     int lastSeparatorOccurence = 0;
797     for (String separator : separators) {
798       final int idx = pattern.lastIndexOf(separator);
799       lastSeparatorOccurence = Math.max(lastSeparatorOccurence, idx == -1 ? idx : idx + separator.length());
800     }
801
802     return pattern.substring(lastSeparatorOccurence);
803   }
804
805   private interface Cmd {
806     void apply();
807   }
808
809   private class RemoveCmd implements Cmd {
810     private final int start;
811     private final int end;
812
813     private RemoveCmd(final int start, final int end) {
814       this.start = start;
815       this.end = end;
816     }
817
818     public void apply() {
819       myListModel.removeRange(start, end);
820     }
821   }
822
823   private class InsertCmd implements Cmd {
824     private final int idx;
825     private final Object element;
826
827     private InsertCmd(final int idx, final Object element) {
828       this.idx = idx;
829       this.element = element;
830     }
831
832     public void apply() {
833       if (idx < myListModel.size()) {
834         myListModel.add(idx, element);
835       }
836       else {
837         myListModel.addElement(element);
838       }
839     }
840   }
841
842   private class ListUpdater {
843     private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
844     private static final int DELAY = 10;
845     private static final int MAX_BLOCKING_TIME = 30;
846     private final List<Cmd> myCommands = Collections.synchronizedList(new ArrayList<Cmd>());
847
848     public void cancelAll() {
849       myCommands.clear();
850       myAlarm.cancelAllRequests();
851     }
852
853     public void appendToModel(final List<Cmd> commands, final int selectionPos) {
854       myAlarm.cancelAllRequests();
855       myCommands.addAll(commands);
856
857       if (myCommands.isEmpty() || myDisposedFlag) return;
858       myAlarm.addRequest(new Runnable() {
859         public void run() {
860           if (myDisposedFlag) return;
861           final long startTime = System.currentTimeMillis();
862           while (!myCommands.isEmpty() && System.currentTimeMillis() - startTime < MAX_BLOCKING_TIME) {
863             final Cmd cmd = myCommands.remove(0);
864             cmd.apply();
865           }
866
867           myList.setVisibleRowCount(Math.min(VISIBLE_LIST_SIZE_LIMIT, myList.getModel().getSize()));
868           if (!myListModel.isEmpty()) {
869             int pos = selectionPos == 0 ? detectBestStatisticalPosition() : selectionPos;
870             ListScrollingUtil.selectItem(myList, Math.min(pos, myListModel.size() - 1));
871           }
872
873           if (!myCommands.isEmpty()) {
874             myAlarm.addRequest(this, DELAY);
875           }
876           else {
877             doPostponedOkIfNeeded();
878           }
879           if (!myDisposedFlag) {
880             showList();
881           }
882         }
883       }, DELAY);
884     }
885
886     private void doPostponedOkIfNeeded() {
887       if (myPosponedOkAction != null) {
888         if (getChosenElement() != null) {
889           doClose(true);
890           clearPosponedOkAction(myDisposedFlag);
891         }
892       }
893     }
894   }
895
896   private void clearPosponedOkAction(boolean success) {
897     if (myPosponedOkAction != null) {
898       if (success) {
899         myPosponedOkAction.setDone();
900       }
901       else {
902         myPosponedOkAction.setRejected();
903       }
904     }
905
906     myPosponedOkAction = null;
907   }
908
909   protected abstract void showList();
910
911   protected abstract void hideList();
912
913   protected abstract void close(boolean isOk);
914
915   @Nullable
916   public Object getChosenElement() {
917     final List<Object> elements = getChosenElements();
918     return elements != null && elements.size() == 1 ? elements.get(0) : null;
919   }
920
921   protected List<Object> getChosenElements() {
922     if (myListIsUpToDate) {
923       List<Object> values = new ArrayList<Object>(Arrays.asList(myList.getSelectedValues()));
924       values.remove(EXTRA_ELEM);
925       return values;
926     }
927
928     final String text = myTextField.getText();
929     final boolean checkBoxState = myCheckBox.isSelected();
930     //ensureNamesLoaded(checkBoxState);
931     final String[] names = checkBoxState ? myNames[1] : myNames[0];
932     if (names == null) return Collections.emptyList();
933
934     Object uniqueElement = null;
935
936     for (final String name : names) {
937       if (text.equalsIgnoreCase(name)) {
938         final Object[] elements = myModel.getElementsByName(name, checkBoxState, text);
939         if (elements.length > 1) return Collections.emptyList();
940         if (elements.length == 0) continue;
941         if (uniqueElement != null) return Collections.emptyList();
942         uniqueElement = elements[0];
943       }
944     }
945     return uniqueElement == null ? Collections.emptyList() : Collections.singletonList(uniqueElement);
946   }
947
948   protected void choosenElementMightChange() {
949   }
950
951   protected final class MyTextField extends JTextField implements PopupOwner {
952     private final KeyStroke myCompletionKeyStroke;
953     private final KeyStroke forwardStroke;
954     private final KeyStroke backStroke;
955
956     private boolean completionKeyStrokeHappened = false;
957
958     private MyTextField() {
959       super(40);
960       enableEvents(AWTEvent.KEY_EVENT_MASK);
961       myCompletionKeyStroke = getShortcut(IdeActions.ACTION_CODE_COMPLETION);
962       forwardStroke = getShortcut(IdeActions.ACTION_GOTO_FORWARD);
963       backStroke = getShortcut(IdeActions.ACTION_GOTO_BACK);
964
965     }
966
967     private KeyStroke getShortcut(String actionCodeCompletion) {
968       final Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(actionCodeCompletion);
969       for (final Shortcut shortcut : shortcuts) {
970         if (shortcut instanceof KeyboardShortcut) {
971           return ((KeyboardShortcut)shortcut).getFirstKeyStroke();
972         }
973       }
974       return null;
975     }
976
977     protected void processKeyEvent(KeyEvent e) {
978       final KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
979       
980       if (myCompletionKeyStroke != null && keyStroke.equals(myCompletionKeyStroke)) {
981         completionKeyStrokeHappened = true;
982         e.consume();
983         final String pattern = myTextField.getText();
984         final String oldText = myTextField.getText();
985         final int oldPos = myList.getSelectedIndex();
986         myHistory.add(Pair.create(oldText, oldPos));
987         final Runnable postRunnable = new Runnable() {
988           public void run() {
989             fillInCommonPrefix(pattern);
990           }
991         };
992         rebuildList(0, 0, postRunnable, ModalityState.current(), e);
993         return;
994       }
995       if (backStroke != null && keyStroke.equals(backStroke)) {
996         e.consume();
997         if (!myHistory.isEmpty()) {
998           final String oldText = myTextField.getText();
999           final int oldPos = myList.getSelectedIndex();
1000           final Pair<String, Integer> last = myHistory.remove(myHistory.size() - 1);
1001           myTextField.setText(last.first);
1002           myFuture.add(Pair.create(oldText, oldPos));
1003           rebuildList(0, 0, null, ModalityState.current(), e);
1004         }
1005         return;
1006       }
1007       if (forwardStroke != null && keyStroke.equals(forwardStroke)) {
1008         e.consume();
1009         if (!myFuture.isEmpty()) {
1010           final String oldText = myTextField.getText();
1011           final int oldPos = myList.getSelectedIndex();
1012           final Pair<String, Integer> next = myFuture.remove(myFuture.size() - 1);
1013           myTextField.setText(next.first);
1014           myHistory.add(Pair.create(oldText, oldPos));
1015           rebuildList(0, 0, null, ModalityState.current(), e);
1016         }
1017         return;
1018       }
1019       try {
1020         super.processKeyEvent(e);
1021       }
1022       catch (NullPointerException e1) {
1023         if (!Patches.SUN_BUG_6322854) {
1024           throw e1;
1025         }
1026       }
1027     }
1028
1029     private void fillInCommonPrefix(final String pattern) {
1030       final ArrayList<String> list = new ArrayList<String>();
1031       getNamesByPattern(myCheckBox.isSelected(), null, list, pattern);
1032
1033       if (isComplexPattern(pattern)) return; //TODO: support '*'
1034       final String oldText = myTextField.getText();
1035       final int oldPos = myList.getSelectedIndex();
1036
1037       String commonPrefix = null;
1038       if (!list.isEmpty()) {
1039         for (String name : list) {
1040           final String string = name.toLowerCase();
1041           if (commonPrefix == null) {
1042             commonPrefix = string;
1043           }
1044           else {
1045             while (commonPrefix.length() > 0) {
1046               if (string.startsWith(commonPrefix)) {
1047                 break;
1048               }
1049               commonPrefix = commonPrefix.substring(0, commonPrefix.length() - 1);
1050             }
1051             if (commonPrefix.length() == 0) break;
1052           }
1053         }
1054         commonPrefix = list.get(0).substring(0, commonPrefix.length());
1055         for (int i = 1; i < list.size(); i++) {
1056           final String string = list.get(i).substring(0, commonPrefix.length());
1057           if (!string.equals(commonPrefix)) {
1058             commonPrefix = commonPrefix.toLowerCase();
1059             break;
1060           }
1061         }
1062       }
1063       if (commonPrefix == null) commonPrefix = "";
1064       final String newPattern = commonPrefix;
1065
1066       myHistory.add(Pair.create(oldText, oldPos));
1067       myTextField.setText(newPattern);
1068       myTextField.setCaretPosition(newPattern.length());
1069
1070       rebuildList();
1071     }
1072
1073     private boolean isComplexPattern(final String pattern) {
1074       if (pattern.indexOf('*') >= 0) return true;
1075       for (String s : myModel.getSeparators()) {
1076         if (pattern.contains(s)) return true;
1077       }
1078
1079       return false;
1080     }
1081
1082     @Nullable
1083     public Point getBestPopupPosition() {
1084       return new Point(myTextFieldPanel.getWidth(), getHeight());
1085     }
1086
1087     protected void paintComponent(final Graphics g) {
1088       UISettings.setupAntialiasing(g);
1089       super.paintComponent(g);
1090     }
1091
1092     public boolean isCompletionKeyStroke() {
1093       return completionKeyStrokeHappened;
1094     }
1095   }
1096
1097   private static final String EXTRA_ELEM = "...";
1098
1099   private class CalcElementsThread implements Runnable {
1100     private final String myPattern;
1101     private boolean myCheckboxState;
1102     private final CalcElementsCallback myCallback;
1103     private final ModalityState myModalityState;
1104
1105     private Set<Object> myElements = null;
1106
1107     private volatile boolean myCancelled = false;
1108     private final boolean myCanCancel;
1109
1110     private CalcElementsThread(String pattern,
1111                                boolean checkboxState,
1112                                CalcElementsCallback callback,
1113                                ModalityState modalityState,
1114                                boolean canCancel) {
1115       myPattern = pattern;
1116       myCheckboxState = checkboxState;
1117       myCallback = callback;
1118       myModalityState = modalityState;
1119       myCanCancel = canCancel;
1120     }
1121
1122     private final Alarm myShowCardAlarm = new Alarm();
1123
1124     public void run() {
1125       showCard(SEARCHING_CARD, 200);
1126
1127       final Set<Object> elements = new LinkedHashSet<Object>();
1128       Runnable action = new Runnable() {
1129         public void run() {
1130           try {
1131             ensureNamesLoaded(myCheckboxState);
1132             addElementsByPattern(elements, myPattern);
1133             for (Object elem : elements) {
1134               if (myCancelled) {
1135                 break;
1136               }
1137               if (elem instanceof PsiElement) {
1138                 final PsiElement psiElement = (PsiElement)elem;
1139                 psiElement.isWritable(); // That will cache writable flag in VirtualFile. Taking the action here makes it canceleable.
1140               }
1141             }
1142           }
1143           catch (ProcessCanceledException e) {
1144             //OK
1145           }
1146         }
1147       };
1148       ApplicationManager.getApplication().runReadAction(action);
1149
1150       if (myCancelled) {
1151         myShowCardAlarm.cancelAllRequests();
1152         return;
1153       }
1154
1155       final String cardToShow;
1156       if (elements.isEmpty() && !myCheckboxState) {
1157         myCheckboxState = true;
1158         ApplicationManager.getApplication().runReadAction(action);
1159         cardToShow = elements.isEmpty() ? NOT_FOUND_CARD : NOT_FOUND_IN_PROJECT_CARD;
1160       }
1161       else {
1162         cardToShow = elements.isEmpty() ? NOT_FOUND_CARD : CHECK_BOX_CARD;
1163       }
1164       showCard(cardToShow, 0);
1165
1166       myElements = elements;
1167
1168       ApplicationManager.getApplication().invokeLater(new Runnable() {
1169         public void run() {
1170           myCallback.run(myElements);
1171         }
1172       }, myModalityState);
1173     }
1174
1175     private void showCard(final String card, final int delay) {
1176       myShowCardAlarm.cancelAllRequests();
1177       myShowCardAlarm.addRequest(new Runnable() {
1178         public void run() {
1179           myCard.show(myCardContainer, card);
1180         }
1181       }, delay, myModalityState);
1182     }
1183
1184     private void addElementsByPattern(Set<Object> elementsArray, String pattern) {
1185       String namePattern = getNamePattern(pattern);
1186       String qualifierPattern = getQualifierPattern(pattern);
1187
1188       boolean empty = namePattern.length() == 0 || namePattern.equals("@");    // TODO[yole]: remove implicit dependency
1189       if (empty && !canShowListForEmptyPattern()) return;
1190
1191       List<String> namesList = new ArrayList<String>();
1192       getNamesByPattern(myCheckboxState, this, namesList, namePattern);
1193       if (myCancelled) {
1194         throw new ProcessCanceledException();
1195       }
1196       Collections.sort(namesList, new MatchesComparator(pattern));
1197
1198       boolean overflow = false;
1199       List<Object> sameNameElements = new SmartList<Object>();
1200       All:
1201       for (String name : namesList) {
1202         if (myCancelled) {
1203           throw new ProcessCanceledException();
1204         }
1205         final Object[] elements = myModel.getElementsByName(name, myCheckboxState, namePattern);
1206         if (elements.length > 1) {
1207           sameNameElements.clear();
1208           for (final Object element : elements) {
1209             if (matchesQualifier(element, qualifierPattern)) {
1210               sameNameElements.add(element);
1211             }
1212           }
1213           sortByProximity(sameNameElements);
1214           for (Object element : sameNameElements) {
1215             elementsArray.add(element);
1216             if (elementsArray.size() >= myMaximumListSizeLimit) {
1217               overflow = true;
1218               break All;
1219             }
1220           }
1221         }
1222         else if (elements.length == 1 && matchesQualifier(elements[0], qualifierPattern)) {
1223           elementsArray.add(elements[0]);
1224           if (elementsArray.size() >= myMaximumListSizeLimit) {
1225             overflow = true;
1226             break;
1227           }
1228         }
1229       }
1230
1231       if (overflow) {
1232         elementsArray.add(EXTRA_ELEM);
1233       }
1234     }
1235
1236     private void cancel() {
1237       if (myCanCancel) {
1238         myCancelled = true;
1239       }
1240     }
1241   }
1242
1243   private void sortByProximity(final List<Object> sameNameElements) {
1244     Collections.sort(sameNameElements, new PathProximityComparator(myModel, myContext.get()));
1245   }
1246
1247   private List<String> split(String s) {
1248     List<String> answer = new ArrayList<String>();
1249     for (String token : StringUtil.tokenize(s, StringUtil.join(myModel.getSeparators(), ""))) {
1250       if (token.length() > 0) {
1251         answer.add(token);
1252       }
1253     }
1254
1255     return answer.isEmpty() ? Collections.singletonList(s) : answer;
1256   }
1257
1258   private boolean matchesQualifier(final Object element, final String qualifierPattern) {
1259     final String name = myModel.getFullName(element);
1260     if (name == null) return false;
1261
1262     final List<String> suspects = split(name);
1263     final List<Pair<String, NameUtil.Matcher>> patternsAndMatchers =
1264       ContainerUtil.map2List(split(qualifierPattern), new Function<String, Pair<String, NameUtil.Matcher>>() {
1265         public Pair<String, NameUtil.Matcher> fun(String s) {
1266           final String pattern = getNamePattern(s);
1267           final NameUtil.Matcher matcher = buildPatternMatcher(pattern);
1268
1269           return new Pair<String, NameUtil.Matcher>(pattern, matcher);
1270         }
1271       });
1272
1273     int matchPosition = 0;
1274
1275     try {
1276       patterns:
1277       for (Pair<String, NameUtil.Matcher> patternAndMatcher : patternsAndMatchers) {
1278         final String pattern = patternAndMatcher.first;
1279         final NameUtil.Matcher matcher = patternAndMatcher.second;
1280         if (pattern.length() > 0) {
1281           for (int j = matchPosition; j < suspects.size() - 1; j++) {
1282             String suspect = suspects.get(j);
1283             if (matches(pattern, matcher, suspect)) {
1284               matchPosition = j + 1;
1285               continue patterns;
1286             }
1287           }
1288
1289           return false;
1290         }
1291       }
1292     }
1293     catch (Exception e) {
1294       // Do nothing. No matches appears valid result for "bad" pattern
1295       return false;
1296     }
1297
1298     return true;
1299   }
1300
1301   private void getNamesByPattern(final boolean checkboxState,
1302                                  CalcElementsThread calcElementsThread,
1303                                  final List<String> list,
1304                                  String pattern) throws ProcessCanceledException {
1305     if (!canShowListForEmptyPattern()) {
1306       LOG.assertTrue(pattern.length() > 0);
1307     }
1308
1309     if (pattern.startsWith("@")) {
1310       pattern = pattern.substring(1);
1311     }
1312
1313     final String[] names = checkboxState ? myNames[1] : myNames[0];
1314     final NameUtil.Matcher matcher = buildPatternMatcher(pattern);
1315
1316     try {
1317       for (String name : names) {
1318         if (calcElementsThread != null && calcElementsThread.myCancelled) {
1319           break;
1320         }
1321         if (matches(pattern, matcher, name)) {
1322           list.add(name);
1323         }
1324       }
1325     }
1326     catch (Exception e) {
1327       // Do nothing. No matches appears valid result for "bad" pattern
1328     }
1329   }
1330
1331   private boolean canShowListForEmptyPattern() {
1332     return isShowListForEmptyPattern() || (isShowListAfterCompletionKeyStroke() && lastKeyStrokeIsCompletion());
1333   }
1334
1335   protected boolean lastKeyStrokeIsCompletion() {
1336      return myTextField.isCompletionKeyStroke();
1337   }
1338
1339   private boolean matches(String pattern, NameUtil.Matcher matcher, String name) {
1340     boolean matches = false;
1341     if (name != null) {
1342       if (myModel instanceof CustomMatcherModel) {
1343         if (((CustomMatcherModel)myModel).matches(name, pattern)) {
1344           matches = true;
1345         }
1346       }
1347       else if (pattern.length() == 0 || matcher.matches(name)) {
1348         matches = true;
1349       }
1350     }
1351     return matches;
1352   }
1353
1354   private NameUtil.Matcher buildPatternMatcher(String pattern) {
1355     return NameUtil.buildMatcher(pattern, 0, true, true, pattern.toLowerCase().equals(pattern));
1356   }
1357
1358   private interface CalcElementsCallback {
1359     void run(Set<?> elements);
1360   }
1361
1362   private static class PathProximityComparator implements Comparator<Object> {
1363     private final ChooseByNameModel myModel;
1364     private final PsiProximityComparator myProximityComparator;
1365
1366     private PathProximityComparator(final ChooseByNameModel model, final PsiElement context) {
1367       myModel = model;
1368       myProximityComparator = new PsiProximityComparator(context);
1369     }
1370
1371     public int compare(final Object o1, final Object o2) {
1372       int rc = myProximityComparator.compare(o1, o2);
1373       if (rc != 0) return rc;
1374
1375       return Comparing.compare(myModel.getFullName(o1), myModel.getFullName(o2));
1376     }
1377   }
1378
1379   private static class HintLabel extends JLabel {
1380     private HintLabel(String text) {
1381       super(text, RIGHT);
1382       setForeground(Color.darkGray);
1383     }
1384   }
1385 }