Skip to content

Commit f31b811

Browse files
pbodnarmickaelistria
authored andcommitted
Bug 531785 - Auto infix search in Open Resource
A new match rule `RULE_SUBSTRING_MATCH` is introduced in the `SearchPattern`. The constant itself is copied as-is from JDT's variant of `SearchPattern`, yet its usage and purpose differs. When used and prefix search is not enforced, then: * search is done as if "*" was specified at the pattern start * CamelCase search doesn't require the 1st pattern char to be the very 1st char of the matched string Use this in Resource search dialogs for the default and file name pattern, while also list "prefix matches" firstly and extend the matched characters highlighting to still work correctly. Path (container) and extension searches should not be affected by this. Further changes: * users can start a search pattern with ">" to enforce the old prefix-only search Necessary refactoring: * unify how `stringPattern` gets prepared in `SearchPattern.initializePatternAndMatchRule()` * this also implies refactoring of camelCaseMatch() * use `SearchPattern.initialPattern` for creating the extra patterns in the dialog class * this fixes scenarios when the newly introduced ">" is used together with "." (changes in pattern didn't get recognized) * this also means that most of the lines made by 2ff4063 are removed as they are no longer necessary Improve unit tests of `SearchPattern`: * remove duplicated examples from javadocs * newly cover basic edge cases * use better examples for starts-vs-contains-like tests * add sanity check that at least 1 testing resource matches some pattern
1 parent 2dba486 commit f31b811

File tree

12 files changed

+694
-176
lines changed

12 files changed

+694
-176
lines changed

bundles/org.eclipse.ui.ide/META-INF/MANIFEST.MF

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: %Plugin.name
44
Bundle-SymbolicName: org.eclipse.ui.ide; singleton:=true
5-
Bundle-Version: 3.20.0.qualifier
5+
Bundle-Version: 3.20.100.qualifier
66
Bundle-ClassPath: .
77
Bundle-Activator: org.eclipse.ui.internal.ide.IDEWorkbenchPlugin
88
Bundle-ActivationPolicy: lazy
@@ -50,7 +50,7 @@ Require-Bundle: org.eclipse.core.resources;bundle-version="[3.17.0,4.0.0)";resol
5050
org.eclipse.core.runtime;bundle-version="[3.18.0,4.0.0)",
5151
org.eclipse.core.filesystem;bundle-version="[1.0.0,2.0.0)",
5252
org.eclipse.help;bundle-version="[3.2.0,4.0.0)",
53-
org.eclipse.ui;bundle-version="[3.106.0,4.0.0)",
53+
org.eclipse.ui;bundle-version="[3.202.0,4.0.0)",
5454
org.eclipse.ui.views;bundle-version="[3.2.0,4.0.0)";resolution:=optional,
5555
org.eclipse.jface.text;bundle-version="[3.2.0,4.0.0)",
5656
org.eclipse.ui.forms;bundle-version="[3.3.0,4.0.0)";resolution:=optional,

bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/dialogs/FilteredResourcesSelectionDialog.java

Lines changed: 102 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,19 @@ public class FilteredResourcesSelectionDialog extends FilteredItemsSelectionDial
102102
private static final String SHOW_DERIVED = "ShowDerived"; //$NON-NLS-1$
103103
private static final String FILTER_BY_LOCATION = "FilterByLocation"; //$NON-NLS-1$
104104

105+
private static final char START_SYMBOL = '>';
106+
107+
private static final char END_SYMBOL = '<';
108+
109+
private static final char BLANK = ' ';
110+
111+
// this is hard-coded, as a UI option is most probably not necessary
112+
private final boolean autoInfixSearch = true;
113+
114+
private int getDefaultMatchRules() {
115+
return SearchPattern.DEFAULT_MATCH_RULES | (autoInfixSearch ? SearchPattern.RULE_SUBSTRING_MATCH : 0);
116+
}
117+
105118
private ShowDerivedResourcesAction showDerivedResourcesAction;
106119

107120
private ResourceItemLabelProvider resourceItemLabelProvider;
@@ -443,6 +456,7 @@ protected Comparator<IResource> getItemsComparator() {
443456

444457
if (pattern != null) {
445458
int patternDot = pattern.lastIndexOf('.');
459+
// Prioritize names matching the whole pattern
446460
String patternNoExtension = patternDot == -1 ? pattern : pattern.substring(0, patternDot);
447461
boolean m1 = patternNoExtension.equals(n1);
448462
boolean m2 = patternNoExtension.equals(n2);
@@ -454,6 +468,21 @@ protected Comparator<IResource> getItemsComparator() {
454468
return 1;
455469
}
456470
}
471+
// Prioritize names starting with the pattern
472+
char patternFirstChar = getFirstFileNameChar(pattern);
473+
if (patternFirstChar != 0) {
474+
patternFirstChar = Character.toLowerCase(patternFirstChar);
475+
m1 = patternFirstChar == Character.toLowerCase(s1.charAt(0));
476+
m2 = patternFirstChar == Character.toLowerCase(s2.charAt(0));
477+
if (!m1 || !m2) {
478+
if (m1) {
479+
return -1;
480+
}
481+
if (m2) {
482+
return 1;
483+
}
484+
}
485+
}
457486
}
458487

459488
int comparability = collator.compare(n1, n2);
@@ -495,6 +524,22 @@ protected Comparator<IResource> getItemsComparator() {
495524
};
496525
}
497526

527+
/**
528+
* @param pattern
529+
* @return the first character from the given string which <em>could</em> be
530+
* considered a part of a file name. Returns <code>0</code> if there is
531+
* no such character found.
532+
*/
533+
private char getFirstFileNameChar(String pattern) {
534+
for (int i = 0; i < pattern.length(); i++) {
535+
char ch = pattern.charAt(i);
536+
if (ch != '*') {
537+
return ch;
538+
}
539+
}
540+
return 0;
541+
}
542+
498543
/**
499544
* Return the "distance" of the item from the root of the relative search
500545
* container. Distances can be compared (smaller numbers are better). <br>
@@ -722,6 +767,9 @@ private List<Position> getMatchPositions(String string, String matching) {
722767
}
723768

724769
// Pre-process the matching pattern
770+
if (matching.charAt(0) == '>') {
771+
matching = matching.substring(1);
772+
}
725773
char lastChar = matching.charAt(matching.length() - 1);
726774
if (lastChar == ' ' || lastChar == '<') {
727775
matching = matching.substring(0, matching.length() - 1);
@@ -756,8 +804,11 @@ private List<Position> getMatchPositions(String string, String matching) {
756804
if (regionsDontMatch) {
757805
// We should get here only when CamelCase nor wildcard matching succeeded
758806
// A simple comparison of the whole strings should succeed instead
759-
if (string.toLowerCase().startsWith(matching.toLowerCase())) {
760-
positions.add(new Position(0, matching.length()));
807+
int matchingIndex = autoInfixSearch
808+
? string.toLowerCase().indexOf(matching.toLowerCase())
809+
: (string.toLowerCase().startsWith(matching.toLowerCase()) ? 0 : -1);
810+
if (matchingIndex > -1) {
811+
positions.add(new Position(matchingIndex, matching.length()));
761812
}
762813
}
763814
return positions;
@@ -959,7 +1010,7 @@ protected class ResourceFilter extends ItemsFilter {
9591010
* @param typeMask filter type mask. See {@link IResource#getType()} types.
9601011
*/
9611012
public ResourceFilter(IContainer container, boolean showDerived, int typeMask) {
962-
super();
1013+
super(new SearchPattern(getDefaultMatchRules()));
9631014
this.filterContainer = container;
9641015
this.showDerived = showDerived;
9651016
this.filterTypeMask = typeMask;
@@ -977,21 +1028,22 @@ public ResourceFilter(IContainer container, boolean showDerived, int typeMask) {
9771028
private ResourceFilter(IContainer container, IContainer searchContainer, boolean showDerived, int typeMask) {
9781029
this(container, showDerived, typeMask);
9791030

980-
String stringPattern = getPattern();
981-
int matchRule = getMatchRule();
1031+
final String stringPattern = patternMatcher.getInitialPattern();
9821032
String filenamePattern;
9831033

9841034
int sep = stringPattern.lastIndexOf(IPath.SEPARATOR);
9851035
if (sep != -1) {
1036+
// This means that we primarily check (via `patternMatcher`) just the resource
1037+
// _name_ part and when there is some actual _container_ part (`sep > 0`), we
1038+
// also do checks for that part (via `containerPattern` and optional
1039+
// `relativeContainerPattern`).
9861040
filenamePattern = stringPattern.substring(sep + 1, stringPattern.length());
987-
if ("*".equals(filenamePattern)) //$NON-NLS-1$
988-
filenamePattern = "**"; //$NON-NLS-1$
9891041

9901042
if (sep > 0) {
9911043
if (filenamePattern.isEmpty()) // relative patterns don't need a file name
9921044
filenamePattern = "**"; //$NON-NLS-1$
9931045

994-
String containerPattern = stringPattern.substring(0, sep);
1046+
String containerPattern = stringPattern.substring(isMatchPrefix(stringPattern) ? 1 : 0, sep);
9951047

9961048
if (searchContainer != null) {
9971049
relativeContainerPattern = new SearchPattern(
@@ -1001,6 +1053,8 @@ private ResourceFilter(IContainer container, IContainer searchContainer, boolean
10011053
}
10021054

10031055
if (!containerPattern.startsWith(Character.toString('*'))) {
1056+
// bug 552418 - make the search always "root less", so that users don't need to
1057+
// type the initial "*/"
10041058
if (!containerPattern.startsWith(Character.toString(IPath.SEPARATOR))) {
10051059
containerPattern = IPath.SEPARATOR + containerPattern;
10061060
}
@@ -1010,35 +1064,29 @@ private ResourceFilter(IContainer container, IContainer searchContainer, boolean
10101064
| SearchPattern.RULE_PREFIX_MATCH | SearchPattern.RULE_PATTERN_MATCH);
10111065
this.containerPattern.setPattern(containerPattern);
10121066
}
1013-
boolean isPrefixPattern = matchRule == SearchPattern.RULE_PREFIX_MATCH
1014-
|| (matchRule == SearchPattern.RULE_PATTERN_MATCH && filenamePattern.endsWith("*")); //$NON-NLS-1$
1015-
if (!isPrefixPattern)
1016-
// Add '<' again as it was removed by SearchPattern
1017-
filenamePattern += '<';
1018-
else if (filenamePattern.endsWith("*") && !filenamePattern.equals("**")) //$NON-NLS-1$ //$NON-NLS-2$
1019-
// Remove added '*' as the filename pattern might be a camel case pattern
1020-
filenamePattern = filenamePattern.substring(0, filenamePattern.length() - 1);
1067+
if (isMatchPrefix(stringPattern)) {
1068+
filenamePattern = '>' + filenamePattern;
1069+
}
10211070
patternMatcher.setPattern(filenamePattern);
1022-
// Update filenamePattern and matchRule as they might have changed
1023-
filenamePattern = getPattern();
1024-
matchRule = getMatchRule();
10251071
} else {
10261072
filenamePattern = stringPattern;
10271073
}
10281074

10291075
int lastPatternDot = filenamePattern.lastIndexOf('.');
10301076
if (lastPatternDot != -1) {
1031-
if (matchRule != SearchPattern.RULE_EXACT_MATCH) {
1032-
namePattern = new SearchPattern();
1033-
namePattern.setPattern(filenamePattern.substring(0, lastPatternDot));
1034-
String extensionPatternStr = filenamePattern.substring(lastPatternDot + 1);
1035-
// Add a '<' except this is a camel case pattern or a prefix pattern
1036-
if (matchRule != SearchPattern.RULE_CAMELCASE_MATCH && matchRule != SearchPattern.RULE_PREFIX_MATCH
1037-
&& !extensionPatternStr.endsWith("*")) //$NON-NLS-1$
1038-
extensionPatternStr += '<';
1039-
extensionPattern = new SearchPattern();
1040-
extensionPattern.setPattern(extensionPatternStr);
1077+
// This means we primarily check resource name as _name_ and _extension_ part
1078+
// and only when we don't succeed, we try the default whole-name check (via
1079+
// `patternMatcher`).
1080+
namePattern = new SearchPattern(getDefaultMatchRules());
1081+
String namePatternStr = filenamePattern.substring(0, lastPatternDot);
1082+
if (isMatchSuffix(stringPattern) && !namePatternStr.endsWith("*")) { //$NON-NLS-1$
1083+
// This means extension part will end with '<' (or ' ')
1084+
// and we should apply the same for the name part.
1085+
namePatternStr += "<"; //$NON-NLS-1$
10411086
}
1087+
namePattern.setPattern(namePatternStr);
1088+
extensionPattern = new SearchPattern();
1089+
extensionPattern.setPattern(filenamePattern.substring(lastPatternDot + 1));
10421090
}
10431091

10441092
}
@@ -1218,4 +1266,29 @@ protected void storeItemToMemento(Object item, IMemento element) {
12181266

12191267
}
12201268

1269+
/**
1270+
* Returns whether prefix matching is enforced in the given search pattern.
1271+
*/
1272+
private static boolean isMatchPrefix(String pattern) {
1273+
if (pattern.length() == 0) {
1274+
return false;
1275+
}
1276+
1277+
char first = pattern.charAt(0);
1278+
return pattern.length() > 1 && first == START_SYMBOL;
1279+
}
1280+
1281+
/**
1282+
* Returns whether suffix matching is enforced in the given search pattern.
1283+
*/
1284+
private static boolean isMatchSuffix(String pattern) {
1285+
if (pattern.length() <= 1) {
1286+
return false;
1287+
}
1288+
1289+
char last = pattern.charAt(pattern.length() - 1);
1290+
boolean matchPrefix = isMatchPrefix(pattern);
1291+
return pattern.length() > (matchPrefix ? 2 : 1) && (last == END_SYMBOL || last == BLANK);
1292+
}
1293+
12211294
}

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,7 @@ ResourceSelectionDialog_folders = In &folders:
865865
ResourceSelectionDialog_showDerived=Show &derived resources
866866

867867
OpenResourceDialog_title = Open Resource
868-
OpenResourceDialog_message = &Enter resource name prefix, path prefix or pattern (?, * or camel case):
868+
OpenResourceDialog_message = &Enter resource name, path or pattern (?, * or camel case):
869869
OpenResourceDialog_openButton_text = &Open
870870
OpenResourceDialog_openWithButton_text=Open Wit&h
871871
OpenResourceDialog_openWithMenu_label=Open Wit&h
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<component id="org.eclipse.ui.workbench" version="2">
3+
<resource path="Eclipse UI/org/eclipse/ui/dialogs/SearchPattern.java" type="org.eclipse.ui.dialogs.SearchPattern">
4+
<filter id="336658481">
5+
<message_arguments>
6+
<message_argument value="org.eclipse.ui.dialogs.SearchPattern"/>
7+
<message_argument value="DEFAULT_MATCH_RULES"/>
8+
</message_arguments>
9+
</filter>
10+
<filter id="336658481">
11+
<message_arguments>
12+
<message_argument value="org.eclipse.ui.dialogs.SearchPattern"/>
13+
<message_argument value="RULE_SUBSTRING_MATCH"/>
14+
</message_arguments>
15+
</filter>
16+
</resource>
17+
</component>

0 commit comments

Comments
 (0)