git clone http://danamlund/git/eclipse-quick-launch/.git
Log | Files | Refs | LICENSE

commit 46bc1a9aa5c722acc9e91ee22e4df9b895bab129
parent afbb28668b721ae50dc2f12861f5de6d89ed2783
Author: Dan Amlund <dan@danamlund.dk>
Date:   Sat, 11 Nov 2017 21:17:54 +0100

1.4 extend from same dialog as ctrl-shift-t, put elements that match better higher in the list. Better matching means neigboring letters from pattern, and same casing as in pattern.

Diffstat:
Mdk.danamlund.quicklaunch/.classpath | 1+
Mdk.danamlund.quicklaunch/META-INF/MANIFEST.MF | 5+++--
Mdk.danamlund.quicklaunch/build.properties | 2+-
Mdk.danamlund.quicklaunch/src/dk/danamlund/quicklaunch/QuickLaunchConfigurationDialog.java | 218+++++++++++++++++++++++++++++++++++++------------------------------------------
Adk.danamlund.quicklaunch/src/dk/danamlund/quicklaunch/StringFuzzyMatcher.java | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adk.danamlund.quicklaunch/unittest/dk/danamlund/quicklaunch/FuzzyMatchToolsTest.java | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mreadme.txt | 2+-
7 files changed, 239 insertions(+), 121 deletions(-)

diff --git a/dk.danamlund.quicklaunch/.classpath b/dk.danamlund.quicklaunch/.classpath @@ -3,5 +3,6 @@ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="unittest"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/dk.danamlund.quicklaunch/META-INF/MANIFEST.MF b/dk.danamlund.quicklaunch/META-INF/MANIFEST.MF @@ -2,9 +2,10 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Quick Launch Configuration Bundle-SymbolicName: dk.danamlund.quicklaunch;singleton:=true -Bundle-Version: 1.3 +Bundle-Version: 1.4 Require-Bundle: org.eclipse.ui, org.eclipse.debug.core;bundle-version="3.10.100", org.eclipse.equinox.common;bundle-version="3.8.0", - org.eclipse.debug.ui + org.eclipse.debug.ui, + org.junit Bundle-RequiredExecutionEnvironment: JavaSE-1.8 diff --git a/dk.danamlund.quicklaunch/build.properties b/dk.danamlund.quicklaunch/build.properties @@ -1,4 +1,4 @@ -source.. = src/ +source.. = src/, unittest/ output.. = bin/ bin.includes = plugin.xml,\ META-INF/,\ diff --git a/dk.danamlund.quicklaunch/src/dk/danamlund/quicklaunch/QuickLaunchConfigurationDialog.java b/dk.danamlund.quicklaunch/src/dk/danamlund/quicklaunch/QuickLaunchConfigurationDialog.java @@ -1,39 +1,47 @@ package dk.danamlund.quicklaunch; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.ui.DebugUITools; import org.eclipse.debug.ui.IDebugModelPresentation; +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.viewers.LabelProvider; -import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.dialogs.ElementListSelectionDialog; -import org.eclipse.ui.dialogs.FilteredList; -import org.eclipse.ui.dialogs.FilteredList.FilterMatcher; - -public class QuickLaunchConfigurationDialog extends ElementListSelectionDialog { +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IMemento; +import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog; + +public class QuickLaunchConfigurationDialog extends FilteredItemsSelectionDialog { private final String runMode; - private Object previousSelection = null; + private final FuzzyComparator fuzzyComparator; + private final StringFuzzyMatcher stringFuzzyMatcher; private List<ILaunchConfiguration> launchConfigurations; public QuickLaunchConfigurationDialog(String runMode) { - super(null, new QuickLaunchConfigurationLabelProvider()); + this(runMode, new StringFuzzyMatcher()); + } + + public QuickLaunchConfigurationDialog(String runMode, StringFuzzyMatcher stringFuzzyMatcher) { + super(null, false); this.runMode = runMode; + this.fuzzyComparator = new FuzzyComparator(stringFuzzyMatcher); + this.stringFuzzyMatcher = stringFuzzyMatcher; - setBlockOnOpen(false); + setListLabelProvider(new IconLabelProvider()); setTitle(capitalize(runMode) + " Launch Configuration"); setMessage("Enter fuzzy pattern:"); + + setSelectionHistory(new ResourceSelectionHistory()); } private static String capitalize(String s) { @@ -43,71 +51,53 @@ public class QuickLaunchConfigurationDialog extends ElementListSelectionDialog { return s.substring(0, 1).toUpperCase() + s.substring(1); } - public void setLaunchConfigurations(List<ILaunchConfiguration> launchConfigurations) { - this.launchConfigurations = launchConfigurations; - setElements(this.launchConfigurations.toArray()); + @Override + protected Control createExtendedContentArea(Composite parent) { + return null; } @Override - public int open() { - int output = super.open(); - - // Running initially selected element caused ok button to be disabled - // on next run until you search for something. - // This fixes that, without me having to understand why it happens. - Button okButton = getOkButton(); - if (okButton != null) { - okButton.setEnabled(true); - } - - if (previousSelection != null) { - setSelection(new Object[] { previousSelection }); - } + protected IDialogSettings getDialogSettings() { + return new DialogSettings("QuickLaunchConfigurationDialog"); + } - return output; + @Override + protected IStatus validateItem(Object item) { + return Status.OK_STATUS; } @Override - protected FilteredList createFilteredList(Composite parent) { - FilteredList list = super.createFilteredList(parent); - // Set dummy filters and comparators, do everything in doFilter - list.setFilterMatcher(new TrueFilterMatcher()); - list.setComparator(new AllEqualComparator()); - return list; + protected ItemsFilter createFilter() { + return new FuzzyMatchItemsFilter(); } @Override - protected Text createFilterText(Composite parent) { - Text text = super.createFilterText(parent); + protected Comparator<?> getItemsComparator() { + return fuzzyComparator; + } - // Dont trigger FilterList filter - for (Listener listener : text.getListeners(SWT.Modify)) { - text.removeListener(SWT.Modify, listener); + @Override + protected void fillContentProvider(AbstractContentProvider contentProvider, ItemsFilter itemsFilter, + IProgressMonitor progressMonitor) throws CoreException { + FuzzyMatchItemsFilter fuzzyFilter = (FuzzyMatchItemsFilter) itemsFilter; + fuzzyComparator.setPattern(fuzzyFilter.getRealPattern()); + progressMonitor.beginTask("Searching", launchConfigurations.size()); + for (ILaunchConfiguration launchConfiguration : launchConfigurations) { + contentProvider.add(launchConfiguration, itemsFilter); + progressMonitor.worked(1); } - - text.addListener(SWT.Modify, new Listener() { - @Override - public void handleEvent(Event e) { - doFilter(text.getText()); - } - }); - - return text; + progressMonitor.done(); } - private void doFilter(String pattern) { - pattern = pattern.toLowerCase(); - - List<ILaunchConfiguration> filtered = new ArrayList<>(); - for (ILaunchConfiguration launchConfig : launchConfigurations) { - if (fuzzyMatch(launchConfig.toString(), pattern)) { - filtered.add(launchConfig); - } - } - filtered.sort(new FuzzyScoreComparator(pattern)); + @Override + public int open() { + setInitialPattern(""); + return super.open(); + } - // Naively update list with filtered elements. - fFilteredList.setElements(filtered.toArray()); + @Override + public String getElementName(Object item) { + return item.toString(); } @Override @@ -121,88 +111,82 @@ public class QuickLaunchConfigurationDialog extends ElementListSelectionDialog { } catch (CoreException e) { throw new IllegalStateException(e); } - previousSelection = launchConfiguration; } } - private static class QuickLaunchConfigurationLabelProvider extends LabelProvider { - final IDebugModelPresentation debugModelPresentation = DebugUITools.newDebugModelPresentation(); + public void setLaunchConfigurations(List<ILaunchConfiguration> launchConfigurations) { + this.launchConfigurations = launchConfigurations; + } + private class FuzzyMatchItemsFilter extends ItemsFilter { @Override - public Image getImage(Object element) { - return debugModelPresentation.getImage(element); + public boolean matchItem(Object item) { + return stringFuzzyMatcher.fuzzyMatch(item.toString(), getRealPattern()); } - } - /** - * fuzzy 'foo' is same as regular '*f*o*o*'. - */ - private static boolean fuzzyMatch(String element, String pattern) { - if (pattern.isEmpty()) { + @Override + public boolean isConsistentItem(Object item) { return true; } - String s = element.toLowerCase().trim(); - int sIndex = 0; - for (int i = 0; i < pattern.length(); i++) { - if (sIndex >= s.length()) { - return false; - } - sIndex = s.indexOf(pattern.charAt(i), sIndex); - if (sIndex < 0) { - return false; - } - sIndex++; + + @Override + public boolean isSubFilter(ItemsFilter filter) { + // false to trigger fillContentProvider every time + return false; } - return true; - } - private static int getFuzzyScore(String name, String filter) { - int score = 0; - int nameI = 0; - for (int filterI = 0; filterI < filter.length(); filterI++) { - int newNameI = name.indexOf(filter.charAt(filterI), nameI); - if (newNameI == nameI) { - score++; - } - nameI = newNameI + 1; + @Override + public String getPattern() { + // Make pattern always be non-empty so we also show results for the + // empty pattern + return super.getPattern() + "_"; + } + + public String getRealPattern() { + return super.getPattern(); } - return score; } - private static class TrueFilterMatcher implements FilterMatcher { + private static class ResourceSelectionHistory extends SelectionHistory { @Override - public boolean match(Object element) { - return true; + protected Object restoreItemFromMemento(IMemento element) { + return null; } @Override - public void setFilter(String pattern, boolean ignoreCase, boolean ignoreWildCards) { + protected void storeItemToMemento(Object item, IMemento element) { } - } - private static class AllEqualComparator implements Comparator<Object> { @Override - public int compare(Object o1, Object o2) { - return 0; + public synchronized void accessed(Object object) { + // never save history } } - private static class FuzzyScoreComparator implements Comparator<Object> { - private final String pattern; + private static class FuzzyComparator implements Comparator<Object> { + private final StringFuzzyMatcher stringFuzzyMatcher; + private String pattern = ""; + + public FuzzyComparator(StringFuzzyMatcher stringFuzzyMatcher) { + this.stringFuzzyMatcher = stringFuzzyMatcher; + } + + @Override + public int compare(Object a, Object b) { + return stringFuzzyMatcher.getFuzzyScoreComparator(pattern).compare(a, b); + } - public FuzzyScoreComparator(String pattern) { + public void setPattern(String pattern) { this.pattern = pattern; } + } + + private static class IconLabelProvider extends LabelProvider { + final IDebugModelPresentation debugModelPresentation = DebugUITools.newDebugModelPresentation(); @Override - public int compare(Object aObject, Object bObject) { - String a = aObject.toString().toLowerCase(); - String b = bObject.toString().toLowerCase(); - int comparison = Integer.compare(getFuzzyScore(a, pattern), getFuzzyScore(b, pattern)); - if (comparison != 0) { - return comparison; - } - return a.compareTo(b); + public Image getImage(Object element) { + return debugModelPresentation.getImage(element); } } } diff --git a/dk.danamlund.quicklaunch/src/dk/danamlund/quicklaunch/StringFuzzyMatcher.java b/dk.danamlund.quicklaunch/src/dk/danamlund/quicklaunch/StringFuzzyMatcher.java @@ -0,0 +1,78 @@ +package dk.danamlund.quicklaunch; + +import java.util.Comparator; + +public class StringFuzzyMatcher { + + /** + * fuzzy 'foo' is same as regular '*f*o*o*'. + */ + public boolean fuzzyMatch(String element, String pattern) { + if (pattern.isEmpty()) { + return true; + } + pattern = pattern.toLowerCase(); + String s = element.toLowerCase().trim(); + int sIndex = 0; + for (int i = 0; i < pattern.length(); i++) { + if (sIndex >= s.length()) { + return false; + } + sIndex = s.indexOf(pattern.charAt(i), sIndex); + if (sIndex < 0) { + return false; + } + sIndex++; + } + return true; + } + + public int getFuzzyScore(String name, String filter) { + String nameLower = name.toLowerCase(); + String filterLower = filter.toLowerCase(); + + int score = 0; + int nameI = 0; + for (int filterI = 0; filterI < filter.length(); filterI++) { + int newNameI = nameLower.indexOf(filterLower.charAt(filterI), nameI); + if (newNameI == nameI) { + score += 2; + if (name.charAt(nameI) == filter.charAt(filterI)) { + score++; + } + } + nameI = newNameI + 1; + } + // Give score if final char of filter is also final char of name + if (nameI == name.length()) { + score += 2; + } + return score; + } + + public Comparator<Object> getFuzzyScoreComparator(String pattern) { + return new FuzzyScoreComparator(this, pattern); + } + + private static class FuzzyScoreComparator implements Comparator<Object> { + private final StringFuzzyMatcher stringFuzzyMatcher; + private final String pattern; + + public FuzzyScoreComparator(StringFuzzyMatcher stringFuzzyMatcher, String pattern) { + this.stringFuzzyMatcher = stringFuzzyMatcher; + this.pattern = pattern; + } + + @Override + public int compare(Object aObject, Object bObject) { + String a = aObject.toString(); + String b = bObject.toString(); + int comparison = Integer.compare(stringFuzzyMatcher.getFuzzyScore(b, pattern), + stringFuzzyMatcher.getFuzzyScore(a, pattern)); + if (comparison != 0) { + return comparison; + } + return a.compareTo(b); + } + } +} diff --git a/dk.danamlund.quicklaunch/unittest/dk/danamlund/quicklaunch/FuzzyMatchToolsTest.java b/dk.danamlund.quicklaunch/unittest/dk/danamlund/quicklaunch/FuzzyMatchToolsTest.java @@ -0,0 +1,54 @@ +package dk.danamlund.quicklaunch; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class FuzzyMatchToolsTest { + + @Test + public void testFuzzyMatch() { + assertTrue(fuzzyMatch("foo", "foo")); + assertTrue(fuzzyMatch("?f?o?o?", "foo")); + assertFalse(fuzzyMatch("oof", "foo")); + assertFalse(fuzzyMatch("bar", "foo")); + + assertTrue(fuzzyMatch("FOO", "foo")); + assertTrue(fuzzyMatch("foo", "FOO")); + } + + @Test + public void fuzzyScore() { + checkScoreHigherThan("foo", "?f?o?o?", "foo"); + } + + @Test + public void fuzzyScoreBetterToMatchFirstChar() { + checkScoreHigherThan("foo", "?foo", "foo"); + checkScoreHigherThan("foo", "foobar", "foo"); + } + + @Test + public void fuzzyScoreBetterToMatchLastChar() { + checkScoreHigherThan("foo", "foo?", "foo"); + } + + @Test + public void fuzzyScoreBetterToMatchCase() { + checkScoreHigherThan("Foo", "foo", "Foo"); + } + + private void checkScoreHigherThan(String a, String b, String pattern) { + assertTrue(a + "(" + getFuzzyScore(a, pattern) + ") > " + b + "(" + getFuzzyScore(b, pattern) + ")", + getFuzzyScore(a, pattern) > getFuzzyScore(b, pattern)); + } + + private int getFuzzyScore(String element, String pattern) { + return new StringFuzzyMatcher().getFuzzyScore(element, pattern); + } + + private boolean fuzzyMatch(String element, String pattern) { + return new StringFuzzyMatcher().fuzzyMatch(element, pattern); + } +} diff --git a/readme.txt b/readme.txt @@ -1,6 +1,6 @@ Eclipse quick launch -Run launch configurations like Ctrl-Shift-T +Run launch configurations like Ctrl-Shift-D Launch with Ctrl-Shift-Y or (debug) with Ctrl-Shift-D