2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInsight.lookup.impl;
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.codeInsight.completion.CodeCompletionFeatures;
21 import com.intellij.codeInsight.completion.CompletionLookupArranger;
22 import com.intellij.codeInsight.completion.PrefixMatcher;
23 import com.intellij.codeInsight.completion.RangeMarkerSpy;
24 import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
25 import com.intellij.codeInsight.hint.HintManager;
26 import com.intellij.codeInsight.hint.HintManagerImpl;
27 import com.intellij.codeInsight.lookup.*;
28 import com.intellij.codeInsight.template.impl.LiveTemplateLookupElement;
29 import com.intellij.featureStatistics.FeatureUsageTracker;
30 import com.intellij.ide.ui.UISettings;
31 import com.intellij.lang.LangBundle;
32 import com.intellij.openapi.Disposable;
33 import com.intellij.openapi.actionSystem.ActionManager;
34 import com.intellij.openapi.actionSystem.IdeActions;
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.command.CommandProcessor;
39 import com.intellij.openapi.command.WriteCommandAction;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.editor.*;
42 import com.intellij.openapi.editor.event.*;
43 import com.intellij.openapi.keymap.KeymapUtil;
44 import com.intellij.openapi.project.Project;
45 import com.intellij.openapi.ui.popup.JBPopup;
46 import com.intellij.openapi.ui.popup.JBPopupFactory;
47 import com.intellij.openapi.util.Condition;
48 import com.intellij.openapi.util.Disposer;
49 import com.intellij.openapi.util.IconLoader;
50 import com.intellij.openapi.util.Trinity;
51 import com.intellij.openapi.util.registry.Registry;
52 import com.intellij.openapi.util.text.StringUtil;
53 import com.intellij.psi.PsiDocumentManager;
54 import com.intellij.psi.PsiElement;
55 import com.intellij.psi.PsiFile;
56 import com.intellij.psi.impl.DebugUtil;
57 import com.intellij.ui.LightweightHint;
58 import com.intellij.ui.ListScrollingUtil;
59 import com.intellij.ui.ScreenUtil;
60 import com.intellij.ui.awt.RelativePoint;
61 import com.intellij.ui.components.JBList;
62 import com.intellij.ui.components.JBScrollPane;
63 import com.intellij.ui.plaf.beg.BegPopupMenuBorder;
64 import com.intellij.ui.popup.AbstractPopup;
65 import com.intellij.util.Alarm;
66 import com.intellij.util.CollectConsumer;
67 import com.intellij.util.ObjectUtils;
68 import com.intellij.util.SmartList;
69 import com.intellij.util.containers.ConcurrentHashMap;
70 import com.intellij.util.containers.ContainerUtil;
71 import com.intellij.util.ui.AbstractLayoutManager;
72 import com.intellij.util.ui.AsyncProcessIcon;
73 import com.intellij.util.ui.ButtonlessScrollBarUI;
74 import com.intellij.util.ui.UIUtil;
75 import gnu.trove.TObjectHashingStrategy;
76 import org.jetbrains.annotations.NotNull;
77 import org.jetbrains.annotations.Nullable;
78 import org.jetbrains.annotations.TestOnly;
81 import javax.swing.border.Border;
82 import javax.swing.border.EmptyBorder;
83 import javax.swing.border.LineBorder;
84 import javax.swing.event.ListSelectionEvent;
85 import javax.swing.event.ListSelectionListener;
87 import java.awt.event.MouseAdapter;
88 import java.awt.event.MouseEvent;
90 import java.util.List;
92 public class LookupImpl extends LightweightHint implements LookupEx, Disposable {
93 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupImpl");
94 private static final int MAX_PREFERRED_COUNT = 5;
96 private static final LookupItem EMPTY_LOOKUP_ITEM = LookupItem.fromString("preselect");
97 private static final Icon relevanceSortIcon = IconLoader.getIcon("/ide/lookupRelevance.png");
98 private static final Icon lexiSortIcon = IconLoader.getIcon("/ide/lookupAlphanumeric.png");
100 private final Project myProject;
101 private final Editor myEditor;
103 private int myPreferredItemsCount;
104 private String myInitialPrefix;
106 private boolean myStableStart;
107 private RangeMarker myLookupStartMarker;
108 private final JList myList = new JBList(new DefaultListModel());
109 private final LookupCellRenderer myCellRenderer;
110 private Boolean myPositionedAbove = null;
112 private final ArrayList<LookupListener> myListeners = new ArrayList<LookupListener>();
114 private long myStampShown = 0;
115 private boolean myShown = false;
116 private boolean myDisposed = false;
117 private boolean myHidden = false;
118 private final List<LookupElement> myFrozenItems = new ArrayList<LookupElement>();
119 private String mySelectionInvariant = null;
120 private boolean mySelectionTouched;
121 private boolean myFocused = true;
122 private String myAdditionalPrefix = "";
123 private final AsyncProcessIcon myProcessIcon = new AsyncProcessIcon("Completion progress");
124 private final JPanel myIconPanel = new JPanel(new BorderLayout());
125 private volatile boolean myCalculating;
126 private final Advertiser myAdComponent;
127 private volatile String myAdText;
128 private volatile int myLookupTextWidth = 50;
129 private boolean myChangeGuard;
130 private volatile LookupModel myModel = new LookupModel(EMPTY_LOOKUP_ITEM);
131 private LookupModel myPresentableModel = myModel;
132 @SuppressWarnings("unchecked") private final Map<LookupElement, PrefixMatcher> myMatchers = new ConcurrentHashMap<LookupElement, PrefixMatcher>(TObjectHashingStrategy.IDENTITY);
133 private LookupHint myElementHint = null;
134 private Alarm myHintAlarm = new Alarm();
135 private JLabel mySortingLabel = new JLabel();
136 private final JScrollPane myScrollPane;
137 private final LookupLayeredPane myLayeredPane = new LookupLayeredPane();
138 private JButton myScrollBarIncreaseButton;
139 private boolean myStartCompletionWhenNothingMatches;
140 private boolean myResizePending;
141 private int myMaximumHeight = Integer.MAX_VALUE;
143 public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger){
144 super(new JPanel(new BorderLayout()));
145 setForceShowAsPopup(true);
146 setCancelOnClickOutside(false);
148 AbstractPopup.suppressMacCornerFor(getComponent());
153 myIconPanel.setVisible(false);
154 myCellRenderer = new LookupCellRenderer(this);
155 myList.setCellRenderer(myCellRenderer);
157 myList.setFocusable(false);
158 myList.setFixedCellWidth(50);
160 myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
161 myList.setBackground(LookupCellRenderer.BACKGROUND_COLOR);
163 myScrollBarIncreaseButton = new JButton();
164 myScrollBarIncreaseButton.setFocusable(false);
165 myScrollBarIncreaseButton.setRequestFocusEnabled(false);
167 myScrollPane = new JBScrollPane(myList);
168 myScrollPane.setViewportBorder(new EmptyBorder(0, 0, 0, 0));
169 myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
170 myScrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(13, -1));
171 myScrollPane.getVerticalScrollBar().setUI(new ButtonlessScrollBarUI() {
173 protected JButton createIncreaseButton(int orientation) {
174 return myScrollBarIncreaseButton;
178 getComponent().add(myLayeredPane, BorderLayout.CENTER);
180 myLayeredPane.mainPanel.add(myScrollPane, BorderLayout.CENTER);
181 myScrollPane.setBorder(null);
183 myAdComponent = new Advertiser();
184 JComponent adComponent = myAdComponent.getAdComponent();
185 adComponent.setBorder(new EmptyBorder(0, 1, 1, 2 + relevanceSortIcon.getIconWidth()));
186 myLayeredPane.mainPanel.add(adComponent, BorderLayout.SOUTH);
187 getComponent().setBorder(new BegPopupMenuBorder());
189 myIconPanel.setBackground(Color.LIGHT_GRAY);
190 myIconPanel.add(myProcessIcon);
192 updateLookupStart(0);
194 final ListModel model = myList.getModel();
195 addEmptyItem((DefaultListModel)model);
196 updateListHeight(model);
198 setArranger(arranger);
202 mySortingLabel.setBorder(new LineBorder(Color.LIGHT_GRAY));
203 mySortingLabel.setOpaque(true);
204 mySortingLabel.addMouseListener(new MouseAdapter() {
206 public void mouseClicked(MouseEvent e) {
207 FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_CHANGE_SORTING);
208 UISettings.getInstance().SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY = !UISettings.getInstance().SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY;
215 private void updateSorting() {
216 final boolean lexi = UISettings.getInstance().SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY;
217 mySortingLabel.setIcon(lexi ? lexiSortIcon : relevanceSortIcon);
218 mySortingLabel.setToolTipText(lexi ? "Click to sort variants by relevance" : "Click to sort variants alphabetically");
223 public void setArranger(LookupArranger arranger) {
224 myModel.setArranger(arranger);
228 public boolean isFocused() {
232 public void setFocused(boolean focused) {
236 public boolean isCalculating() {
237 return myCalculating;
240 public void setCalculating(final boolean calculating) {
241 myCalculating = calculating;
242 Runnable setVisible = new Runnable() {
245 myIconPanel.setVisible(myCalculating);
249 new Alarm().addRequest(setVisible, 100);
255 myProcessIcon.resume();
257 myProcessIcon.suspend();
261 public int getPreferredItemsCount() {
262 return myPreferredItemsCount;
265 public void markSelectionTouched() {
266 if (!ApplicationManager.getApplication().isUnitTestMode()) {
267 ApplicationManager.getApplication().assertIsDispatchThread();
269 mySelectionTouched = true;
270 myPresentableModel.preselectedItem = null;
275 public void setSelectionTouched(boolean selectionTouched) {
276 mySelectionTouched = selectionTouched;
279 public void resort() {
280 myFrozenItems.clear();
282 myPresentableModel.preselectedItem = EMPTY_LOOKUP_ITEM;
283 synchronized (myList) {
284 ((DefaultListModel)myList.getModel()).clear();
287 final List<LookupElement> items = myPresentableModel.getItems();
288 myPresentableModel.clearItems();
289 for (final LookupElement item : items) {
290 addItem(item, itemMatcher(item));
295 public void addItem(LookupElement item, PrefixMatcher matcher) {
296 myMatchers.put(item, matcher);
297 myModel.addItem(item);
299 updateLookupWidth(item);
302 public void updateLookupWidth(LookupElement item) {
303 final LookupElementPresentation presentation = renderItemApproximately(item);
304 int maxWidth = myCellRenderer.updateMaximumWidth(presentation);
305 myLookupTextWidth = Math.max(maxWidth, myLookupTextWidth);
307 myModel.setItemPresentation(item, presentation);
310 public void requestResize() {
311 ApplicationManager.getApplication().assertIsDispatchThread();
312 myResizePending = true;
315 public Collection<LookupElementAction> getActionsFor(LookupElement element) {
316 final CollectConsumer<LookupElementAction> consumer = new CollectConsumer<LookupElementAction>();
317 for (LookupActionProvider provider : LookupActionProvider.EP_NAME.getExtensions()) {
318 provider.fillActions(element, this, consumer);
320 return consumer.getResult();
323 public JList getList() {
327 public List<LookupElement> getItems() {
328 final ArrayList<LookupElement> result = new ArrayList<LookupElement>();
329 final Object[] objects;
330 synchronized (myList) {
331 objects = ((DefaultListModel)myList.getModel()).toArray();
333 for (final Object object : objects) {
334 if (!(object instanceof EmptyLookupItem)) {
335 result.add((LookupElement) object);
341 public void setAdvertisementText(@Nullable String text) {
343 if (StringUtil.isNotEmpty(text)) {
344 addAdvertisement(ObjectUtils.assertNotNull(text));
348 public String getAdvertisementText() {
353 public String getAdditionalPrefix() {
354 return myAdditionalPrefix;
357 void appendPrefix(char c) {
359 myAdditionalPrefix += c;
360 myInitialPrefix = null;
361 myFrozenItems.clear();
364 ensureSelectionVisible();
367 public void setStartCompletionWhenNothingMatches(boolean startCompletionWhenNothingMatches) {
368 myStartCompletionWhenNothingMatches = startCompletionWhenNothingMatches;
371 public boolean isStartCompletionWhenNothingMatches() {
372 return myStartCompletionWhenNothingMatches;
375 public void ensureSelectionVisible() {
376 if (!isSelectionVisible()) {
377 ListScrollingUtil.ensureIndexIsVisible(myList, myList.getSelectedIndex(), 1);
381 boolean truncatePrefix(boolean preserveSelection) {
382 final int len = myAdditionalPrefix.length();
383 if (len == 0) return false;
385 if (preserveSelection) {
386 markSelectionTouched();
389 myAdditionalPrefix = myAdditionalPrefix.substring(0, len - 1);
390 myInitialPrefix = null;
391 myFrozenItems.clear();
393 if (myPresentableModel == myModel) {
395 ensureSelectionVisible();
401 private boolean updateList() {
402 if (!ApplicationManager.getApplication().isUnitTestMode()) {
403 ApplicationManager.getApplication().assertIsDispatchThread();
407 final Trinity<List<LookupElement>, Iterable<List<LookupElement>>, Boolean> snapshot = myPresentableModel.getModelSnapshot();
409 final LinkedHashSet<LookupElement> items = matchingItems(snapshot.first);
411 checkMinPrefixLengthChanges(items);
413 boolean hasPreselected = !mySelectionTouched && items.contains(myPresentableModel.preselectedItem);
414 LookupElement oldSelected = mySelectionTouched ? (LookupElement)myList.getSelectedValue() : null;
415 String oldInvariant = mySelectionInvariant;
417 LinkedHashSet<LookupElement> model = new LinkedHashSet<LookupElement>();
418 model.addAll(getPrefixItems(items, true));
419 model.addAll(getPrefixItems(items, false));
421 myFrozenItems.retainAll(items);
422 model.addAll(myFrozenItems);
424 if (!isAlphaSorted()) {
425 addMostRelevantItems(model, snapshot.second);
426 if (hasPreselected) {
427 model.add(myPresentableModel.preselectedItem);
431 myPreferredItemsCount = model.size();
432 myFrozenItems.clear();
434 myFrozenItems.addAll(model);
437 if (isAlphaSorted()) {
438 final ArrayList<LookupElement> elements = new ArrayList<LookupElement>(items);
439 Collections.sort(elements, new Comparator<LookupElement>() {
441 public int compare(LookupElement o1, LookupElement o2) {
442 return o1.getLookupString().compareToIgnoreCase(o2.getLookupString());
445 model.addAll(elements);
446 } else if (limitRelevance()) {
447 model.addAll(addRemainingItemsLexicographically(model, items));
449 for (List<LookupElement> group : snapshot.second) {
450 for (LookupElement element : group) {
451 if (prefixMatches(element)) {
458 DefaultListModel listModel = (DefaultListModel)myList.getModel();
459 synchronized (myList) {
462 if (!model.isEmpty()) {
463 for (LookupElement element : model) {
464 listModel.addElement(element);
468 addEmptyItem(listModel);
472 updateListHeight(listModel);
474 if (!model.isEmpty()) {
475 LookupElement first = model.iterator().next();
476 if (isFocused() && (!isExactPrefixItem(first, true) || mySelectionTouched || shouldSkip(first))) {
477 restoreSelection(oldSelected, hasPreselected, oldInvariant, snapshot.second);
480 myList.setSelectedIndex(0);
483 return snapshot.third;
486 private static boolean shouldSkip(LookupElement element) {
487 return element instanceof LiveTemplateLookupElement && ((LiveTemplateLookupElement)element).sudden;
490 private boolean isSelectionVisible() {
491 return myList.getFirstVisibleIndex() <= myList.getSelectedIndex() && myList.getSelectedIndex() <= myList.getLastVisibleIndex();
494 private LinkedHashSet<LookupElement> matchingItems(final List<LookupElement> elements) {
495 final LinkedHashSet<LookupElement> items = new LinkedHashSet<LookupElement>();
496 for (LookupElement element : elements) {
497 if (prefixMatches(element)) {
504 private boolean checkReused() {
505 if (myPresentableModel != myModel) {
506 myAdditionalPrefix = "";
507 myFrozenItems.clear();
508 myPresentableModel = myModel;
514 private void checkMinPrefixLengthChanges(Collection<LookupElement> items) {
515 if (myStableStart) return;
516 if (!myCalculating && !items.isEmpty()) {
517 myStableStart = true;
520 int minPrefixLength = items.isEmpty() ? 0 : Integer.MAX_VALUE;
521 for (final LookupElement item : items) {
522 minPrefixLength = Math.min(itemMatcher(item).getPrefix().length(), minPrefixLength);
525 updateLookupStart(minPrefixLength);
528 private void restoreSelection(@Nullable LookupElement oldSelected, boolean choosePreselectedItem, @Nullable String oldInvariant, Iterable<List<LookupElement>> groups) {
529 if (oldSelected != null) {
530 if (oldSelected.isValid()) {
531 myList.setSelectedValue(oldSelected, false);
532 if (myList.getSelectedValue() == oldSelected) {
537 if (oldInvariant != null) {
538 for (LookupElement element : getItems()) {
539 if (oldInvariant.equals(myPresentableModel.getItemPresentationInvariant(element))) {
540 myList.setSelectedValue(element, false);
541 if (myList.getSelectedValue() == element) {
549 LookupElement preselected = myPresentableModel.preselectedItem;
550 if (choosePreselectedItem) {
551 myList.setSelectedValue(preselected, false);
553 myList.setSelectedIndex(doSelectMostPreferableItem(getItems(), groups));
556 if (preselected != null && myShown) {
557 myPresentableModel.preselectedItem = getCurrentItem();
561 private void updateListHeight(ListModel model) {
562 myList.setFixedCellHeight(myCellRenderer.getListCellRendererComponent(myList, model.getElementAt(0), 0, false, false).getPreferredSize().height);
564 myList.setVisibleRowCount(Math.min(model.getSize(), UISettings.getInstance().MAX_LOOKUP_LIST_HEIGHT));
567 private void addEmptyItem(DefaultListModel model) {
568 LookupItem<String> item = new EmptyLookupItem(myCalculating ? " " : LangBundle.message("completion.no.suggestions"));
569 myMatchers.put(item, new CamelHumpMatcher(""));
570 model.addElement(item);
572 updateLookupWidth(item);
576 private static LookupElementPresentation renderItemApproximately(LookupElement item) {
577 final LookupElementPresentation p = new LookupElementPresentation();
578 item.renderElement(p);
582 private static List<LookupElement> addRemainingItemsLexicographically(Set<LookupElement> firstItems, Collection<LookupElement> allItems) {
583 List<LookupElement> model = new ArrayList<LookupElement>();
584 for (LookupElement item : allItems) {
585 if (!firstItems.contains(item)) {
592 private void addMostRelevantItems(final Set<LookupElement> model, final Iterable<List<LookupElement>> sortedItems) {
593 if (model.size() > MAX_PREFERRED_COUNT) return;
595 for (final List<LookupElement> elements : sortedItems) {
596 final List<LookupElement> suitable = new SmartList<LookupElement>();
597 for (final LookupElement item : elements) {
598 if (!model.contains(item) && prefixMatches(item)) {
603 if (model.size() + suitable.size() > MAX_PREFERRED_COUNT) break;
604 model.addAll(suitable);
608 public static boolean limitRelevance() {
609 return Registry.is("limited.relevance.sorting.in.completion");
612 public boolean isFrozen(@NotNull LookupElement element) {
613 return myFrozenItems.contains(element);
616 private List<LookupElement> getPrefixItems(final Collection<LookupElement> elements, final boolean caseSensitive) {
617 List<LookupElement> better = new ArrayList<LookupElement>();
618 for (LookupElement element : elements) {
619 if (isExactPrefixItem(element, caseSensitive)) {
624 final Comparator<LookupElement> itemComparator = myPresentableModel.getArranger().getItemComparator();
625 if (itemComparator != null) {
626 Collections.sort(better, itemComparator);
629 List<LookupElement> classified = myPresentableModel.classifyByRelevance(better);
630 List<LookupElement> result = new ArrayList<LookupElement>(classified.size());
631 for (LookupElement element : classified) {
632 if (element.getLookupString().equals(itemPattern(element))) {
636 for (LookupElement element : classified) {
637 if (!element.getLookupString().equals(itemPattern(element))) {
646 public String itemPattern(LookupElement element) {
647 return itemMatcher(element).getPrefix() + myAdditionalPrefix;
650 private boolean isAlphaSorted() {
651 return isCompletion() && UISettings.getInstance().SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY;
654 private boolean isExactPrefixItem(LookupElement item, final boolean caseSensitive) {
655 final String pattern = itemPattern(item);
656 final Set<String> strings = item.getAllLookupStrings();
657 if (strings.contains(pattern)) {
658 return caseSensitive; //to not add the same elements twice to the model, as sensitive and then as insensitive
665 for (String s : strings) {
666 if (s.equalsIgnoreCase(pattern)) {
673 private boolean prefixMatches(final LookupElement item) {
674 PrefixMatcher matcher = itemMatcher(item);
675 if (myAdditionalPrefix.length() > 0) {
676 matcher = matcher.cloneWithPrefix(itemPattern(item));
678 return matcher.prefixMatches(item);
683 public PrefixMatcher itemMatcher(LookupElement item) {
684 PrefixMatcher matcher = myMatchers.get(item);
685 if (matcher == null) {
686 throw new AssertionError("Item not in lookup: item=" + item + "; lookup items=" + getItems());
691 // in layered pane coordinate system.
692 private Rectangle calculatePosition() {
693 Dimension dim = getComponent().getPreferredSize();
694 int lookupStart = getLookupStart();
695 if (lookupStart < 0 || lookupStart > myEditor.getDocument().getTextLength()) {
696 LOG.error(lookupStart + "; offset=" + myEditor.getCaretModel().getOffset() + "; element=" +
700 LogicalPosition pos = myEditor.offsetToLogicalPosition(lookupStart);
701 Point location = myEditor.logicalPositionToXY(pos);
702 location.y += myEditor.getLineHeight();
703 location.x -= myCellRenderer.getIconIndent() + getComponent().getInsets().left;
705 SwingUtilities.convertPointToScreen(location, myEditor.getContentComponent());
706 final Rectangle screenRectangle = ScreenUtil.getScreenRectangle(location);
708 if (!isPositionedAboveCaret()) {
709 int shiftLow = screenRectangle.height - (location.y + dim.height);
710 myPositionedAbove = shiftLow < 0 && shiftLow < location.y - dim.height && location.y >= dim.height;
712 if (isPositionedAboveCaret()) {
713 location.y -= dim.height + myEditor.getLineHeight();
716 //otherwise the lookup won't intersect with the editor and every editor's resize (e.g. after typing in console) will close the lookup
720 if (!screenRectangle.contains(location)) {
721 location = ScreenUtil.findNearestPointOnBorder(screenRectangle, location);
724 final JRootPane rootPane = myEditor.getComponent().getRootPane();
725 if (rootPane == null) {
726 LOG.error(myEditor.isDisposed() + "; shown=" + myShown + "; disposed=" + myDisposed + "; editorShowing=" + myEditor.getContentComponent().isShowing());
728 Rectangle candidate = new Rectangle(location, dim);
729 ScreenUtil.cropRectangleToFitTheScreen(candidate);
731 SwingUtilities.convertPointFromScreen(location, rootPane.getLayeredPane());
732 return new Rectangle(location.x, location.y, dim.width, candidate.height);
735 public void finishLookup(final char completionChar) {
736 finishLookup(completionChar, (LookupElement)myList.getSelectedValue());
739 public void finishLookup(char completionChar, @Nullable final LookupElement item) {
741 item instanceof EmptyLookupItem ||
742 item.getObject() instanceof DeferredUserLookupValue &&
743 item.as(LookupItem.CLASS_CONDITION_KEY) != null &&
744 !((DeferredUserLookupValue)item.getObject()).handleUserSelection(item.as(LookupItem.CLASS_CONDITION_KEY), myProject)) {
746 fireItemSelected(null, completionChar);
750 final PsiFile file = getPsiFile();
751 if (file != null && !WriteCommandAction.ensureFilesWritable(myProject, Arrays.asList(file))) {
753 fireItemSelected(null, completionChar);
757 final String prefix = itemPattern(item);
758 boolean plainMatch = ContainerUtil.or(item.getAllLookupStrings(), new Condition<String>() {
760 public boolean value(String s) {
761 return StringUtil.startsWithIgnoreCase(s, prefix);
765 FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EDITING_COMPLETION_CAMEL_HUMPS);
768 performGuardedChange(new Runnable() {
770 AccessToken token = WriteAction.start();
772 insertLookupString(item, prefix);
782 fireItemSelected(item, completionChar);
785 private void insertLookupString(LookupElement item, final String prefix) {
786 String lookupString = getCaseCorrectedLookupString(item);
788 if (myEditor.getSelectionModel().hasBlockSelection()) {
789 LogicalPosition blockStart = myEditor.getSelectionModel().getBlockStart();
790 LogicalPosition blockEnd = myEditor.getSelectionModel().getBlockEnd();
791 assert blockStart != null && blockEnd != null;
793 for (int line = blockStart.line; line <= blockEnd.line; line++) {
794 myEditor.getDocument().replaceString(myEditor.logicalPositionToOffset(new LogicalPosition(line, blockStart.column)) - prefix.length(),
795 myEditor.logicalPositionToOffset(new LogicalPosition(line, blockEnd.column)),
798 LogicalPosition start = new LogicalPosition(blockStart.line, blockStart.column - prefix.length());
799 LogicalPosition end = new LogicalPosition(blockEnd.line, start.column + lookupString.length());
800 myEditor.getSelectionModel().setBlockSelection(start, end);
801 myEditor.getCaretModel().moveToLogicalPosition(end);
803 EditorModificationUtil.deleteSelectedText(myEditor);
804 final int caretOffset = myEditor.getCaretModel().getOffset();
805 int lookupStart = caretOffset - prefix.length();
807 int len = myEditor.getDocument().getTextLength();
808 LOG.assertTrue(lookupStart >= 0 && lookupStart <= len, "ls: " + lookupStart + " caret: " + caretOffset + " prefix:" + prefix + " doc: " + len);
809 LOG.assertTrue(caretOffset >= 0 && caretOffset <= len, "co: " + caretOffset + " doc: " + len);
811 myEditor.getDocument().replaceString(lookupStart, caretOffset, lookupString);
813 int offset = lookupStart + lookupString.length();
814 myEditor.getCaretModel().moveToOffset(offset);
815 myEditor.getSelectionModel().removeSelection();
818 myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
821 private String getCaseCorrectedLookupString(LookupElement item) {
822 String lookupString = item.getLookupString();
823 if (item.isCaseSensitive()) {
827 final String prefix = itemPattern(item);
828 final int length = prefix.length();
829 if (length == 0 || !StringUtil.startsWithIgnoreCase(lookupString, prefix)) return lookupString;
830 boolean isAllLower = true;
831 boolean isAllUpper = true;
832 boolean sameCase = true;
833 for (int i = 0; i < length && (isAllLower || isAllUpper || sameCase); i++) {
834 final char c = prefix.charAt(i);
835 boolean isLower = Character.isLowerCase(c);
836 boolean isUpper = Character.isUpperCase(c);
837 // do not take this kind of symbols into account ('_', '@', etc.)
838 if (!isLower && !isUpper) continue;
839 isAllLower = isAllLower && isLower;
840 isAllUpper = isAllUpper && isUpper;
841 sameCase = sameCase && isLower == Character.isLowerCase(lookupString.charAt(i));
843 if (sameCase) return lookupString;
844 if (isAllLower) return lookupString.toLowerCase();
845 if (isAllUpper) return lookupString.toUpperCase();
849 public int getLookupStart() {
850 LOG.assertTrue(myLookupStartMarker.isValid(), disposeTrace);
851 return myLookupStartMarker.getStartOffset();
854 public void performGuardedChange(Runnable change) {
855 performGuardedChange(change, null);
858 public void performGuardedChange(Runnable change, @Nullable final String debug) {
860 assert myLookupStartMarker.isValid();
861 assert !myChangeGuard;
863 myChangeGuard = true;
864 final Document document = myEditor.getDocument();
865 RangeMarkerSpy spy = new RangeMarkerSpy(myLookupStartMarker) {
867 protected void invalidated(DocumentEvent e) {
868 LOG.error("Lookup start marker invalidated, say thanks to the " + e +
869 ", doc=" + document +
873 document.addDocumentListener(spy);
878 document.removeDocumentListener(spy);
879 myChangeGuard = false;
882 LOG.assertTrue(myLookupStartMarker.isValid(), "invalid lookup start");
884 updateLookupLocation();
890 public boolean vetoesHiding() {
891 return myChangeGuard || myDisposed;
894 public boolean isAvailableToUser() {
895 if (ApplicationManager.getApplication().isUnitTestMode()) {
901 public boolean isShown() {
902 if (!ApplicationManager.getApplication().isUnitTestMode()) {
903 ApplicationManager.getApplication().assertIsDispatchThread();
908 public boolean showLookup() {
909 ApplicationManager.getApplication().assertIsDispatchThread();
911 LOG.assertTrue(!myShown);
913 myStampShown = System.currentTimeMillis();
915 if (ApplicationManager.getApplication().isUnitTestMode()) return true;
917 if (!myEditor.getContentComponent().isShowing()) {
922 myAdComponent.showRandomText();
924 getComponent().setBorder(null);
925 updateScrollbarVisibility();
927 Rectangle bounds = calculatePosition();
928 myMaximumHeight = bounds.height;
929 Point p = bounds.getLocation();
930 HintManagerImpl.getInstanceImpl().showEditorHint(this, myEditor, p, HintManager.HIDE_BY_ESCAPE | HintManager.UPDATE_BY_SCROLLING, 0, false,
931 HintManagerImpl.createHintHint(myEditor, p, this, HintManager.UNDER).setAwtTooltip(false));
938 LOG.assertTrue(myList.isShowing(), "!showing, disposed=" + myDisposed);
943 public boolean mayBeNoticed() {
944 return myStampShown > 0 && System.currentTimeMillis() - myStampShown > 300;
947 private void addListeners() {
948 myEditor.getDocument().addDocumentListener(new DocumentAdapter() {
949 public void documentChanged(DocumentEvent e) {
950 if (!myChangeGuard) {
956 final CaretListener caretListener = new CaretListener() {
957 public void caretPositionChanged(CaretEvent e) {
958 if (!myChangeGuard) {
963 final SelectionListener selectionListener = new SelectionListener() {
964 public void selectionChanged(final SelectionEvent e) {
965 if (!myChangeGuard) {
970 final EditorMouseListener mouseListener = new EditorMouseAdapter() {
971 public void mouseClicked(EditorMouseEvent e){
977 myEditor.getCaretModel().addCaretListener(caretListener);
978 myEditor.getSelectionModel().addSelectionListener(selectionListener);
979 myEditor.addEditorMouseListener(mouseListener);
980 Disposer.register(this, new Disposable() {
982 public void dispose() {
983 myEditor.getCaretModel().removeCaretListener(caretListener);
984 myEditor.getSelectionModel().removeSelectionListener(selectionListener);
985 myEditor.removeEditorMouseListener(mouseListener);
989 myList.addListSelectionListener(new ListSelectionListener() {
990 private LookupElement oldItem = null;
992 public void valueChanged(ListSelectionEvent e){
993 myHintAlarm.cancelAllRequests();
995 final LookupElement item = getCurrentItem();
996 if (oldItem != item) {
997 mySelectionInvariant = item == null ? null : myPresentableModel.getItemPresentationInvariant(item);
998 fireCurrentItemChanged(item);
1007 myList.addMouseListener(new MouseAdapter() {
1008 public void mouseClicked(MouseEvent e){
1010 markSelectionTouched();
1012 if (e.getClickCount() == 2){
1013 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
1015 finishLookup(NORMAL_SELECT_CHAR);
1023 private void updateHint(@NotNull final LookupElement item) {
1025 if (myElementHint != null) {
1026 myLayeredPane.remove(myElementHint);
1027 myElementHint = null;
1028 final JRootPane rootPane = getComponent().getRootPane();
1029 if (rootPane != null) {
1030 rootPane.revalidate();
1038 final Collection<LookupElementAction> actions = getActionsFor(item);
1039 if (!actions.isEmpty()) {
1040 myHintAlarm.addRequest(new Runnable() {
1044 myElementHint = new LookupHint();
1045 myLayeredPane.add(myElementHint, 20, 0);
1046 myLayeredPane.layoutHint();
1052 private int updateLookupStart(int myMinPrefixLength) {
1053 int offset = myEditor.getSelectionModel().hasSelection()
1054 ? myEditor.getSelectionModel().getSelectionStart()
1055 : myEditor.getCaretModel().getOffset();
1056 int start = Math.max(offset - myMinPrefixLength - myAdditionalPrefix.length(), 0);
1057 if (myLookupStartMarker != null) {
1058 if (myLookupStartMarker.isValid() && myLookupStartMarker.getStartOffset() == start && myLookupStartMarker.getEndOffset() == start) {
1061 myLookupStartMarker.dispose();
1063 myLookupStartMarker = myEditor.getDocument().createRangeMarker(start, start);
1064 myLookupStartMarker.setGreedyToLeft(true);
1069 public LookupElement getCurrentItem(){
1070 LookupElement item = (LookupElement)myList.getSelectedValue();
1071 return item instanceof EmptyLookupItem ? null : item;
1074 public void setCurrentItem(LookupElement item){
1075 markSelectionTouched();
1076 myList.setSelectedValue(item, false);
1079 public void addLookupListener(LookupListener listener){
1080 myListeners.add(listener);
1083 public void removeLookupListener(LookupListener listener){
1084 myListeners.remove(listener);
1087 public Rectangle getCurrentItemBounds(){
1088 int index = myList.getSelectedIndex();
1090 LOG.error("No selected element, size=" + myList.getModel().getSize() + "; items" + getItems());
1092 Rectangle itmBounds = myList.getCellBounds(index, index);
1093 if (itmBounds == null){
1094 LOG.error("No bounds for " + index + "; size=" + myList.getModel().getSize());
1097 Point layeredPanePoint=SwingUtilities.convertPoint(myList,itmBounds.x,itmBounds.y,getComponent());
1098 itmBounds.x = layeredPanePoint.x;
1099 itmBounds.y = layeredPanePoint.y;
1103 public void fireItemSelected(@Nullable final LookupElement item, char completionChar){
1104 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1106 if (!myListeners.isEmpty()){
1107 LookupEvent event = new LookupEvent(this, item, completionChar);
1108 LookupListener[] listeners = myListeners.toArray(new LookupListener[myListeners.size()]);
1109 for (LookupListener listener : listeners) {
1111 listener.itemSelected(event);
1113 catch (Throwable e) {
1120 private void fireLookupCanceled(final boolean explicitly) {
1121 if (!myListeners.isEmpty()){
1122 LookupEvent event = new LookupEvent(this, explicitly);
1123 LookupListener[] listeners = myListeners.toArray(new LookupListener[myListeners.size()]);
1124 for (LookupListener listener : listeners) {
1126 listener.lookupCanceled(event);
1128 catch (Throwable e) {
1135 private void fireCurrentItemChanged(LookupElement item){
1136 if (!myListeners.isEmpty()){
1137 LookupEvent event = new LookupEvent(this, item, (char)0);
1138 LookupListener[] listeners = myListeners.toArray(new LookupListener[myListeners.size()]);
1139 for (LookupListener listener : listeners) {
1140 listener.currentItemChanged(event);
1145 public boolean fillInCommonPrefix(boolean explicitlyInvoked) {
1146 if (explicitlyInvoked) {
1150 if (explicitlyInvoked && myCalculating) return false;
1151 if (!explicitlyInvoked && mySelectionTouched) return false;
1153 ListModel listModel = myList.getModel();
1154 if (listModel.getSize() <= 1) return false;
1156 if (listModel.getSize() == 0) return false;
1158 final LookupElement firstItem = (LookupElement)listModel.getElementAt(0);
1159 if (listModel.getSize() == 1 && firstItem instanceof EmptyLookupItem) return false;
1161 final PrefixMatcher firstItemMatcher = itemMatcher(firstItem);
1162 final String oldPrefix = firstItemMatcher.getPrefix();
1163 final String presentPrefix = oldPrefix + myAdditionalPrefix;
1164 String commonPrefix = getCaseCorrectedLookupString(firstItem);
1166 for (int i = 1; i < listModel.getSize(); i++) {
1167 LookupElement item = (LookupElement)listModel.getElementAt(i);
1168 if (!oldPrefix.equals(itemMatcher(item).getPrefix())) return false;
1170 final String lookupString = getCaseCorrectedLookupString(item);
1171 final int length = Math.min(commonPrefix.length(), lookupString.length());
1172 if (length < commonPrefix.length()) {
1173 commonPrefix = commonPrefix.substring(0, length);
1176 for (int j = 0; j < length; j++) {
1177 if (commonPrefix.charAt(j) != lookupString.charAt(j)) {
1178 commonPrefix = lookupString.substring(0, j);
1183 if (commonPrefix.length() == 0 || commonPrefix.length() < presentPrefix.length()) {
1188 if (commonPrefix.equals(presentPrefix)) {
1192 for (int i = 0; i < listModel.getSize(); i++) {
1193 LookupElement item = (LookupElement)listModel.getElementAt(i);
1194 if (!itemMatcher(item).cloneWithPrefix(commonPrefix).prefixMatches(item)) {
1199 if (myAdditionalPrefix.length() == 0 && myInitialPrefix == null && !explicitlyInvoked) {
1200 myInitialPrefix = presentPrefix;
1203 myInitialPrefix = null;
1206 replacePrefix(presentPrefix, commonPrefix);
1210 public void replacePrefix(final String presentPrefix, final String newPrefix) {
1211 performGuardedChange(new Runnable() {
1213 EditorModificationUtil.deleteSelectedText(myEditor);
1214 int offset = myEditor.getCaretModel().getOffset();
1215 final int start = offset - presentPrefix.length();
1216 myEditor.getDocument().replaceString(start, offset, newPrefix);
1218 Map<LookupElement, PrefixMatcher> newItems = myPresentableModel.retainMatchingItems(newPrefix, LookupImpl.this);
1220 myMatchers.putAll(newItems);
1222 myAdditionalPrefix = "";
1224 myEditor.getCaretModel().moveToOffset(start + newPrefix.length());
1231 public PsiFile getPsiFile() {
1232 return PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());
1235 public boolean isCompletion() {
1236 return myModel.getArranger() instanceof CompletionLookupArranger;
1239 public PsiElement getPsiElement() {
1240 PsiFile file = getPsiFile();
1241 if (file == null) return null;
1243 int offset = getLookupStart();
1244 if (offset > 0) return file.findElementAt(offset - 1);
1246 return file.findElementAt(0);
1249 public Editor getEditor() {
1254 public void setPositionedAbove(boolean positionedAbove) {
1255 myPositionedAbove = positionedAbove;
1258 public boolean isPositionedAboveCaret(){
1259 return myPositionedAbove != null && myPositionedAbove.booleanValue();
1262 public boolean isSelectionTouched() {
1263 return mySelectionTouched;
1270 public void hideLookup(boolean explicitly) {
1271 ApplicationManager.getApplication().assertIsDispatchThread();
1273 if (myDisposed) return;
1275 doHide(true, explicitly);
1278 private void doHide(final boolean fireCanceled, final boolean explicitly) {
1279 if (myChangeGuard) {
1280 LOG.error("Disposing under a change guard");
1284 LOG.error(disposeTrace);
1292 Disposer.dispose(this);
1296 catch (Throwable e) {
1302 fireLookupCanceled(explicitly);
1306 public void restorePrefix() {
1307 if (myInitialPrefix != null) {
1308 myEditor.getDocument().replaceString(getLookupStart(), myEditor.getCaretModel().getOffset(), myInitialPrefix);
1312 private static String staticDisposeTrace = null;
1313 private String disposeTrace = null;
1315 public static String getLastLookupDisposeTrace() {
1316 return staticDisposeTrace;
1319 public void dispose() {
1320 assert ApplicationManager.getApplication().isDispatchThread();
1323 LOG.error(disposeTrace);
1327 if (myLookupStartMarker != null) {
1328 myLookupStartMarker.dispose();
1329 myLookupStartMarker = null;
1331 Disposer.dispose(myProcessIcon);
1332 Disposer.dispose(myHintAlarm);
1335 disposeTrace = DebugUtil.currentStackTrace() + "\n============";
1336 //noinspection AssignmentToStaticFieldFromInstanceMethod
1337 staticDisposeTrace = disposeTrace;
1340 private int doSelectMostPreferableItem(List<LookupElement> items, Iterable<List<LookupElement>> groups) {
1341 if (items.isEmpty()) {
1345 if (items.size() == 1) {
1349 for (int i = 0; i < items.size(); i++) {
1350 LookupElement item = items.get(i);
1351 if (isExactPrefixItem(item, true) && !shouldSkip(item)) {
1356 final int index = myModel.getArranger().suggestPreselectedItem(items, groups);
1357 assert index >= 0 && index < items.size();
1361 public void refreshUi(boolean mayCheckReused) {
1362 final boolean reused = mayCheckReused && checkReused();
1364 boolean selectionVisible = isSelectionVisible();
1366 boolean itemsChanged = updateList();
1369 LOG.assertTrue(!ApplicationManager.getApplication().isUnitTestMode());
1371 if (myEditor.getComponent().getRootPane() == null) {
1372 LOG.error("Null root pane");
1375 updateScrollbarVisibility();
1377 if (myResizePending || itemsChanged) {
1378 myMaximumHeight = Integer.MAX_VALUE;
1380 Rectangle rectangle = calculatePosition();
1381 myMaximumHeight = rectangle.height;
1383 if (myResizePending || itemsChanged) {
1384 myResizePending = false;
1387 HintManagerImpl.updateLocation(this, myEditor, rectangle.getLocation());
1389 if (reused || selectionVisible) {
1390 ensureSelectionVisible();
1395 private void updateLookupLocation() {
1396 Rectangle rectangle = calculatePosition();
1397 myMaximumHeight = rectangle.height;
1398 HintManagerImpl.updateLocation(this, myEditor, rectangle.getLocation());
1401 private void updateScrollbarVisibility() {
1402 boolean showSorting = isCompletion() && getList().getModel().getSize() >= 3;
1403 mySortingLabel.setVisible(showSorting);
1404 myScrollPane.setVerticalScrollBarPolicy(showSorting ? ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS : ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
1407 public void markReused() {
1408 myAdComponent.clearAdvertisements();
1409 myModel = new LookupModel(null);
1413 public void addAdvertisement(@NotNull final String text) {
1414 UIUtil.invokeLaterIfNeeded(new Runnable() {
1418 myAdComponent.addAdvertisement(text);
1426 public boolean isLookupDisposed() {
1430 public void checkValid() {
1432 throw new AssertionError("Disposed at: " + disposeTrace);
1437 public void showItemPopup(JBPopup hint) {
1438 final Rectangle bounds = getCurrentItemBounds();
1439 hint.show(new RelativePoint(getComponent(), new Point(bounds.x + bounds.width, bounds.y)));
1443 public boolean showElementActions() {
1444 if (!isVisible()) return false;
1446 final LookupElement element = getCurrentItem();
1447 if (element == null) {
1451 final Collection<LookupElementAction> actions = getActionsFor(element);
1452 if (actions.isEmpty()) {
1456 showItemPopup(JBPopupFactory.getInstance().createListPopup(new LookupActionsStep(actions, this, element)));
1460 private class LookupLayeredPane extends JLayeredPane {
1461 final JPanel mainPanel = new JPanel(new BorderLayout());
1463 private LookupLayeredPane() {
1464 add(mainPanel, 0, 0);
1465 add(myIconPanel, 42, 0);
1466 add(mySortingLabel, 10, 0);
1468 setLayout(new AbstractLayoutManager() {
1470 public Dimension preferredLayoutSize(@Nullable Container parent) {
1471 int maxCellWidth = myLookupTextWidth + myCellRenderer.getIconIndent();
1472 int scrollBarWidth = myScrollPane.getPreferredSize().width - myScrollPane.getViewport().getPreferredSize().width;
1473 int listWidth = Math.min(scrollBarWidth + maxCellWidth, UISettings.getInstance().MAX_LOOKUP_WIDTH);
1474 int adWidth = myAdComponent.getAdComponent().getPreferredSize().width;
1475 return new Dimension(Math.max(listWidth, adWidth),
1476 Math.min(mainPanel.getPreferredSize().height, myMaximumHeight));
1480 public void layoutContainer(Container parent) {
1481 Dimension size = getSize();
1482 mainPanel.setSize(size);
1483 mainPanel.validate();
1485 if (!myResizePending) {
1486 Dimension preferredSize = preferredLayoutSize(null);
1487 if (preferredSize.width != size.width) {
1488 UISettings.getInstance().MAX_LOOKUP_WIDTH = Math.max(300, size.width);
1491 int listHeight = myList.getLastVisibleIndex() - myList.getFirstVisibleIndex() + 1;
1492 if (listHeight != myList.getModel().getSize() && listHeight != myList.getVisibleRowCount() && preferredSize.height != size.height) {
1493 UISettings.getInstance().MAX_LOOKUP_LIST_HEIGHT = Math.max(5, listHeight);
1497 myList.setFixedCellWidth(myScrollPane.getViewport().getWidth());
1498 layoutStatusIcons();
1504 private void layoutStatusIcons() {
1505 int adHeight = myAdComponent.getAdComponent().getPreferredSize().height;
1506 Dimension buttonSize = adHeight > 0 || !mySortingLabel.isVisible() ? new Dimension(0, 0) : new Dimension(relevanceSortIcon.getIconWidth(), relevanceSortIcon.getIconHeight());
1507 myScrollBarIncreaseButton.setPreferredSize(buttonSize);
1508 myScrollBarIncreaseButton.setMinimumSize(buttonSize);
1509 myScrollBarIncreaseButton.setMaximumSize(buttonSize);
1510 myScrollPane.getVerticalScrollBar().revalidate();
1511 myScrollPane.getVerticalScrollBar().repaint();
1513 final Dimension iconSize = myProcessIcon.getPreferredSize();
1514 myIconPanel.setBounds(getWidth() - iconSize.width, 0, iconSize.width, iconSize.height);
1516 final Dimension sortSize = mySortingLabel.getPreferredSize();
1517 final Point sbLocation = SwingUtilities.convertPoint(myScrollPane.getVerticalScrollBar(), 0, 0, myLayeredPane);
1519 final int sortHeight = Math.max(adHeight, mySortingLabel.getPreferredSize().height);
1520 mySortingLabel.setBounds(sbLocation.x, getHeight() - sortHeight, sortSize.width, sortHeight);
1524 if (myElementHint != null && getCurrentItem() != null) {
1525 final Rectangle bounds = getCurrentItemBounds();
1526 myElementHint.setSize(myElementHint.getPreferredSize());
1527 myElementHint.setLocation(new Point(bounds.x + bounds.width - myElementHint.getWidth(), bounds.y));
1533 private class LookupHint extends JLabel {
1534 private final Border INACTIVE_BORDER = BorderFactory.createEmptyBorder(4, 4, 4, 4);
1535 private final Border ACTIVE_BORDER = BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.BLACK, 1), BorderFactory.createEmptyBorder(3, 3, 3, 3));
1536 private LookupHint() {
1538 setBorder(INACTIVE_BORDER);
1539 setIcon(IconLoader.findIcon("/actions/intentionBulb.png"));
1540 String acceleratorsText = KeymapUtil.getFirstKeyboardShortcutText(
1541 ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS));
1542 if (acceleratorsText.length() > 0) {
1543 setToolTipText(CodeInsightBundle.message("lightbulb.tooltip", acceleratorsText));
1546 addMouseListener(new MouseAdapter() {
1548 public void mouseEntered(MouseEvent e) {
1549 setBorder(ACTIVE_BORDER);
1553 public void mouseExited(MouseEvent e) {
1554 setBorder(INACTIVE_BORDER);
1557 public void mousePressed(MouseEvent e) {
1558 if (!e.isPopupTrigger() && e.getButton() == MouseEvent.BUTTON1) {
1559 showElementActions();
1566 public LinkedHashMap<LookupElement,StringBuilder> getRelevanceStrings() {
1567 return myPresentableModel.getRelevanceStrings();