From 5311b6cb0d67fcd0c5cc683e23e4830c38cb1790 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 1 Feb 2011 14:05:31 +0100 Subject: [PATCH] an ability to sort lookup items alphabetically (IDEA-60529) --- .../completion/CompletionSortingTestCase.java | 1 + platform/icons/src/ide/lookupAlphanumeric.png | Bin 0 -> 316 bytes platform/icons/src/ide/lookupRelevance.png | Bin 0 -> 462 bytes .../codeInsight/lookup/LookupArranger.java | 16 +++ .../codeInsight/lookup/impl/LookupImpl.java | 112 ++++++++++++++---- .../codeInsight/lookup/impl/LookupModel.java | 6 +- .../src/com/intellij/ide/ui/UISettings.java | 1 + 7 files changed, 107 insertions(+), 29 deletions(-) create mode 100644 platform/icons/src/ide/lookupAlphanumeric.png create mode 100644 platform/icons/src/ide/lookupRelevance.png diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/CompletionSortingTestCase.java b/java/java-tests/testSrc/com/intellij/codeInsight/completion/CompletionSortingTestCase.java index bbe00f31a89d..fcfb6e2e31b9 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/CompletionSortingTestCase.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/CompletionSortingTestCase.java @@ -90,6 +90,7 @@ public abstract class CompletionSortingTestCase extends LightCompletionTestCase item.putUserData(CompletionLookupArranger.WEIGHT, null); item.putUserData(CompletionLookupArranger.RELEVANCE_KEY, null); } + lookup.setSelectionTouched(false); lookup.resort(); } diff --git a/platform/icons/src/ide/lookupAlphanumeric.png b/platform/icons/src/ide/lookupAlphanumeric.png new file mode 100644 index 0000000000000000000000000000000000000000..55222644f54a352fb99281933e9bc678eb0db17c GIT binary patch literal 316 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R}Y)RhkE)4%c zaKYZ?lYt_f1s;*b3=G`DAk4@xYmNj^kiEpy*OmP?6CI7NC%1iEBiObAE1a zYF-J0b5UwyNotBhd1gt5g1e`0KzJjcI8f1aPZ!4!jq^t@*zz$ZO0Yag&)u}t#Yt5| zM5&Nzt<%~I8Y)tIH7q)E78kHAFJM@=;L>UnlX%7Xz4K3nOg?Env$DNj@!$vX!xHXO z_LxLk-Z044$rjF6*2UngCO{ BYKQ;; literal 0 HcmV?d00001 diff --git a/platform/icons/src/ide/lookupRelevance.png b/platform/icons/src/ide/lookupRelevance.png new file mode 100644 index 0000000000000000000000000000000000000000..f04f37a0c9e9d2ee1e0d6b6d46470e3a3b81c052 GIT binary patch literal 462 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R}Y)RhkE)4%c zaKYZ?lYt_f1s;*b3=G`DAk4@xYmNj^kiEpy*OmP?6Ck|8l#l;J=m#?dA`l<_bFSWo-$mM^U&%J*L?-_dClyBy3 zH(!<+RVWiL`us}P)@GZ_FQ=64t~JoM`*Qsk%k=7Glb0eZ^?@PF;OXk;vd$@?2>^aB BxCQ_K literal 0 HcmV?d00001 diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java index 03578fca1dec..07982b3148d4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/LookupArranger.java @@ -32,6 +32,22 @@ public abstract class LookupArranger { } }; + public static final LookupArranger LEXICOGRAPHIC = new LookupArranger() { + @Override + public Comparable getRelevance(LookupElement element) { + return 0; + } + + @Override + public Comparator getItemComparator() { + return new Comparator() { + @Override + public int compare(LookupElement o1, LookupElement o2) { + return o1.getLookupString().compareTo(o2.getLookupString()); + } + }; + } + }; public abstract Comparable getRelevance(LookupElement element); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java index 67e324b94961..8f63cc336fb4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java @@ -24,6 +24,7 @@ import com.intellij.codeInsight.hint.HintManagerImpl; import com.intellij.codeInsight.hint.HintUtil; import com.intellij.codeInsight.lookup.*; import com.intellij.featureStatistics.FeatureUsageTracker; +import com.intellij.ide.ui.UISettings; import com.intellij.lang.LangBundle; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionManager; @@ -66,6 +67,7 @@ import org.jetbrains.annotations.TestOnly; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; @@ -79,6 +81,9 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { private static final int MAX_PREFERRED_COUNT = 5; private static final LookupItem EMPTY_LOOKUP_ITEM = LookupItem.fromString("preselect"); + private static final int LOOKUP_HEIGHT = Integer.getInteger("idea.lookup.height", 11).intValue(); + private static final Icon relevanceSortIcon = IconLoader.getIcon("/ide/lookupRelevance.png"); + private static final Icon lexiSortIcon = IconLoader.getIcon("/ide/lookupAlphanumeric.png"); private final Project myProject; private final Editor myEditor; @@ -86,7 +91,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { private int myMinPrefixLength; private int myPreferredItemsCount; private String myInitialPrefix; - private LookupArranger myArranger; + private LookupArranger myCustomArranger; private RangeMarker myLookupStartMarker; private final JList myList = new JBList(new DefaultListModel()); @@ -111,12 +116,13 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { private final JLabel myAdComponent; private volatile String myAdText; private volatile int myLookupTextWidth = 50; - private static final int LOOKUP_HEIGHT = Integer.getInteger("idea.lookup.height", 11).intValue(); private boolean myReused; private boolean myChangeGuard; private LookupModel myModel = new LookupModel(); private LookupHint myElementHint = null; private Alarm myHintAlarm = new Alarm(); + private JLabel mySortingLabel; + private final JScrollPane myScrollPane; public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger){ super(new JPanel(new BorderLayout())); @@ -133,11 +139,12 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); myList.setBackground(LookupCellRenderer.BACKGROUND_COLOR); - JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myList); - scrollPane.setViewportBorder(new EmptyBorder(0, 0, 0, 0)); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - getComponent().add(scrollPane, BorderLayout.NORTH); - scrollPane.setBorder(null); + myScrollPane = ScrollPaneFactory.createScrollPane(myList); + myScrollPane.setViewportBorder(new EmptyBorder(0, 0, 0, 0)); + myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + myScrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(13, -1)); + getComponent().add(myScrollPane, BorderLayout.NORTH); + myScrollPane.setBorder(null); myAdComponent = HintUtil.createAdComponent(null); getComponent().add(myAdComponent, BorderLayout.SOUTH); @@ -153,11 +160,29 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { setArranger(arranger); addListeners(); + + mySortingLabel = new JLabel(); + mySortingLabel.setBorder(new LineBorder(Color.DARK_GRAY)); + mySortingLabel.setOpaque(true); + mySortingLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + UISettings.getInstance().SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY = !UISettings.getInstance().SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY; + updateSorting(); + } + }); + updateSorting(); + } + + private void updateSorting() { + mySortingLabel.setIcon(UISettings.getInstance().SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY ? lexiSortIcon : relevanceSortIcon); + myModel.setArranger(getActualArranger()); + resort(); } public void setArranger(LookupArranger arranger) { - myArranger = arranger; - myModel.setArranger(arranger); + myCustomArranger = arranger; + myModel.setArranger(getActualArranger()); } @Override @@ -196,8 +221,11 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } @TestOnly + public void setSelectionTouched(boolean selectionTouched) { + mySelectionTouched = selectionTouched; + } + public void resort() { - mySelectionTouched = false; myFrozenItems.clear(); myPreselectedItem = EMPTY_LOOKUP_ITEM; final List items = myModel.getItems(); @@ -206,6 +234,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { addItem(item); } updateList(); + ListScrollingUtil.ensureIndexIsVisible(myList, myList.getSelectedIndex(), 0); } public void addItem(LookupElement item) { @@ -472,10 +501,11 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } private boolean addExactPrefixItems(DefaultListModel model, Set firstItems, final Collection elements) { + final LookupArranger arranger = getActualArranger(); List sorted = new SortedList(new Comparator() { public int compare(LookupElement o1, LookupElement o2) { //noinspection unchecked - return myArranger.getRelevance(o1).compareTo(myArranger.getRelevance(o2)); + return arranger.getRelevance(o1).compareTo(arranger.getRelevance(o2)); } }); for (final LookupElement item : elements) { @@ -492,6 +522,13 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { return !firstItems.isEmpty(); } + private LookupArranger getActualArranger() { + if (UISettings.getInstance().SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY) { + return LookupArranger.LEXICOGRAPHIC; + } + return myCustomArranger; + } + private boolean isExactPrefixItem(LookupElement item) { return item.getAllLookupStrings().contains(item.getPrefixMatcher().getPrefix() + myAdditionalPrefix); } @@ -520,7 +557,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { JComponent internalComponent = myEditor.getContentComponent(); final JRootPane rootPane = editorComponent.getRootPane(); if (rootPane == null) { - LOG.error(myArranger + "; " + myEditor.isDisposed()); + LOG.error(myEditor.isDisposed()); } JLayeredPane layeredPane = rootPane.getLayeredPane(); Point layeredPanePoint=SwingUtilities.convertPoint(internalComponent,location, layeredPane); @@ -620,12 +657,16 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { if (ApplicationManager.getApplication().isUnitTestMode()) return; getComponent().setBorder(null); + updateScrollbarVisibility(); Point p = calculatePosition(); - HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl(); - hintManager.showEditorHint(this, myEditor, p, HintManagerImpl.HIDE_BY_ESCAPE | HintManagerImpl.UPDATE_BY_SCROLLING, 0, false); + HintManagerImpl.getInstanceImpl().showEditorHint(this, myEditor, p, HintManagerImpl.HIDE_BY_ESCAPE | HintManagerImpl.UPDATE_BY_SCROLLING, 0, false); - getComponent().getRootPane().getLayeredPane().add(myIconPanel, 42, 0); + final JLayeredPane layeredPane = getComponent().getRootPane().getLayeredPane(); + layeredPane.add(myIconPanel, 42, 0); + layeredPane.add(mySortingLabel, 10, 0); + + layoutStatusIcons(); } public boolean mayBeNoticed() { @@ -800,7 +841,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); if (item != null) { - myArranger.itemSelected(item, this); + getActualArranger().itemSelected(item, this); } if (!myListeners.isEmpty()){ @@ -949,7 +990,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } public boolean isCompletion() { - return myArranger instanceof CompletionLookupArranger; + return myCustomArranger instanceof CompletionLookupArranger; } public PsiElement getPsiElement() { @@ -1047,7 +1088,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { } } - final int index = myArranger.suggestPreselectedItem(items); + final int index = getActualArranger().suggestPreselectedItem(items); assert index >= 0 && index < items.size(); return index; } @@ -1055,22 +1096,41 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { public void refreshUi() { updateList(); - if (isVisible() && !ApplicationManager.getApplication().isUnitTestMode()) { + if (isVisible()) { + LOG.assertTrue(!ApplicationManager.getApplication().isUnitTestMode()); + if (myEditor.getComponent().getRootPane() == null) { LOG.error("Null root pane"); } - Point point = calculatePosition(); - - final Dimension size = myProcessIcon.getPreferredSize(); - myIconPanel.setBounds(getComponent().getRootPane().getLayeredPane().getWidth() - size.width, 0, size.width, size.height); - - HintManagerImpl.adjustEditorHintPosition(this, myEditor, point); + updateScrollbarVisibility(); + HintManagerImpl.adjustEditorHintPosition(this, myEditor, calculatePosition()); + layoutStatusIcons(); } } + private void layoutStatusIcons() { + final JLayeredPane layeredPane = getComponent().getRootPane().getLayeredPane(); + final int width = layeredPane.getWidth(); + final int height = layeredPane.getHeight(); + + final Dimension iconSize = myProcessIcon.getPreferredSize(); + myIconPanel.setBounds(width - iconSize.width, 0, iconSize.width, iconSize.height); + + final Dimension sortSize = mySortingLabel.getPreferredSize(); + mySortingLabel.setBounds(width - sortSize.width, height - sortSize.height, sortSize.width, sortSize.height); + + } + + private void updateScrollbarVisibility() { + boolean showSorting = getList().getModel().getSize() >= 3; + mySortingLabel.setVisible(showSorting); + myScrollPane.setVerticalScrollBarPolicy(showSorting ? ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS : ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + } + + @TestOnly public LookupArranger getArranger() { - return myArranger; + return getActualArranger(); } public void markReused() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupModel.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupModel.java index a2ce364a8d9a..94f3098ee5a4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupModel.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupModel.java @@ -27,7 +27,6 @@ import com.intellij.util.containers.SortedList; import gnu.trove.THashMap; import gnu.trove.THashSet; import gnu.trove.TObjectHashingStrategy; -import org.jetbrains.annotations.TestOnly; import java.util.*; @@ -50,9 +49,10 @@ public class LookupModel { private TreeMap> myRelevanceGroups; private LookupArranger myArranger; - @TestOnly public List getItems() { - return new ArrayList(myItems); + synchronized (lock) { + return new ArrayList(myItems); + } } public void clearItems() { diff --git a/platform/platform-api/src/com/intellij/ide/ui/UISettings.java b/platform/platform-api/src/com/intellij/ide/ui/UISettings.java index 55588be2125a..c2ac1ad91276 100644 --- a/platform/platform-api/src/com/intellij/ide/ui/UISettings.java +++ b/platform/platform-api/src/com/intellij/ide/ui/UISettings.java @@ -84,6 +84,7 @@ public class UISettings implements PersistentStateComponent, Exporta public boolean OVERRIDE_NONIDEA_LAF_FONTS = false; public boolean SHOW_ICONS_IN_MENUS = true; // Only makes sense on MacOS public boolean DISABLE_MNEMONICS = SystemInfo.isMac; // IDEADEV-33409, should be disabled by default on MacOS + public boolean SORT_LOOKUP_ELEMENTS_LEXICOGRAPHICALLY = false; /** * Defines whether asterisk is shown on modified editor tab or not