diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredList.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredList.java index 56e69c48c5f..1180b1324e6 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredList.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/FilteredList.java @@ -23,6 +23,7 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; +import org.eclipse.core.text.StringMatcher; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; @@ -36,7 +37,6 @@ import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import org.eclipse.ui.internal.WorkbenchMessages; -import org.eclipse.ui.internal.misc.TextMatcher; import org.eclipse.ui.internal.util.Util; import org.eclipse.ui.progress.WorkbenchJob; @@ -73,16 +73,16 @@ public interface FilterMatcher { } private class DefaultFilterMatcher implements FilterMatcher { - private TextMatcher fMatcher; + private StringMatcher fMatcher; @Override public void setFilter(String pattern, boolean ignoreCase, boolean ignoreWildCards) { - fMatcher = new TextMatcher(pattern + '*', ignoreCase, ignoreWildCards); + fMatcher = new StringMatcher(pattern.trim() + '*', ignoreCase, ignoreWildCards); } @Override public boolean match(Object element) { - return fMatcher.match(fLabelProvider.getText(element)); + return fMatcher.matchWords(fLabelProvider.getText(element)); } } diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/PatternFilter.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/PatternFilter.java index b27c7d89b43..87746c48cb8 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/PatternFilter.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/PatternFilter.java @@ -17,13 +17,13 @@ import java.util.HashMap; import java.util.Map; +import org.eclipse.core.text.StringMatcher; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ContentViewer; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; -import org.eclipse.ui.internal.misc.TextMatcher; /** * A filter used in conjunction with FilteredTree. In order to @@ -57,7 +57,7 @@ public class PatternFilter extends ViewerFilter { /** * The string pattern matcher used for this pattern filter. */ - private TextMatcher matcher; + private StringMatcher matcher; private boolean useEarlyReturnIfMatcherIsNull = true; @@ -173,7 +173,7 @@ public void setPattern(String patternString) { if (includeLeadingWildcard) { pattern = "*" + pattern; //$NON-NLS-1$ } - matcher = new TextMatcher(pattern, true, false); + matcher = new StringMatcher(pattern.trim(), true, false); } } @@ -197,7 +197,7 @@ private boolean match(String string) { if (matcher == null) { return true; } - return matcher.match(string); + return matcher.matchWords(string); } /** @@ -289,7 +289,7 @@ protected boolean wordMatches(String text) { } // Otherwise check if any of the words of the text matches - String[] words = TextMatcher.getWords(text); + String[] words = StringMatcher.getWords(text); for (String word : words) { if (!match(word)) { return false; diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/SearchPattern.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/SearchPattern.java index 0b219cab6f1..b30e9fdf835 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/SearchPattern.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/dialogs/SearchPattern.java @@ -13,8 +13,8 @@ *******************************************************************************/ package org.eclipse.ui.dialogs; +import org.eclipse.core.text.StringMatcher; import org.eclipse.jface.util.Util; -import org.eclipse.ui.internal.misc.TextMatcher; /** * A search pattern defines how search results are found. @@ -120,7 +120,7 @@ public class SearchPattern { private String initialPattern; - private TextMatcher stringMatcher; + private StringMatcher stringMatcher; private static final char START_SYMBOL = '>'; @@ -200,7 +200,7 @@ public void setPattern(String stringPattern) { initializePatternAndMatchRule(stringPattern); matchRule = matchRule & this.allowedRules; if (matchRule == RULE_PATTERN_MATCH) { - stringMatcher = new TextMatcher(this.stringPattern, true, false); + stringMatcher = new StringMatcher(this.stringPattern.trim(), true, false); } } @@ -219,7 +219,7 @@ public boolean matches(String text) { case RULE_BLANK_MATCH: return true; case RULE_PATTERN_MATCH: - return stringMatcher.match(text); + return stringMatcher.matchWords(text); case RULE_EXACT_MATCH: return stringPattern.equalsIgnoreCase(text); case RULE_CAMELCASE_MATCH: diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/about/AboutPluginsPage.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/about/AboutPluginsPage.java index f39c445e9b1..d733ee2473a 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/about/AboutPluginsPage.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/about/AboutPluginsPage.java @@ -39,6 +39,7 @@ import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.text.StringMatcher; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.util.ConfigureColumns; @@ -73,7 +74,6 @@ import org.eclipse.ui.internal.WorkbenchMessages; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.misc.StatusUtil; -import org.eclipse.ui.internal.misc.TextMatcher; import org.eclipse.ui.internal.util.BundleUtility; import org.eclipse.ui.progress.WorkbenchJob; import org.eclipse.ui.statushandlers.StatusManager; @@ -689,14 +689,14 @@ public void setAscending(boolean ascending) { } class BundlePatternFilter extends ViewerFilter { - private TextMatcher matcher; + private StringMatcher matcher; public void setPattern(String searchPattern) { if (searchPattern == null || searchPattern.isEmpty()) { this.matcher = null; } else { String pattern = "*" + searchPattern + "*"; //$NON-NLS-1$//$NON-NLS-2$ - this.matcher = new TextMatcher(pattern, true, false); + this.matcher = new StringMatcher(pattern, true, false); } } @@ -708,12 +708,12 @@ public boolean select(Viewer viewer, Object parentElement, Object element) { if (element instanceof AboutBundleData) { AboutBundleData data = (AboutBundleData) element; - return matcher.match(data.getName()) || matcher.match(data.getProviderName()) - || matcher.match(data.getId()); + return matcher.matchWords(data.getName()) || matcher.matchWords(data.getProviderName()) + || matcher.matchWords(data.getId()); } else if (element instanceof AboutBundleGroupData data) { - return matcher.match(data.getName()) || matcher.match(data.getProviderName()) - || matcher.match(data.getId()); + return matcher.matchWords(data.getName()) || matcher.matchWords(data.getProviderName()) + || matcher.matchWords(data.getId()); } return true; } diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/misc/TextMatcher.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/misc/TextMatcher.java deleted file mode 100644 index 6b07140a2f1..00000000000 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/misc/TextMatcher.java +++ /dev/null @@ -1,169 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2020 Thomas Wolf and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.ui.internal.misc; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; -import org.eclipse.core.text.StringMatcher; - -/** - * Similar to {@link StringMatcher}, this {@code TextMatcher} matches a pattern - * that may contain the wildcards '?' or '*' against a text. However, the - * matching is not only done on the full text, but also on individual words from - * the text, and if the pattern contains whitespace, the pattern is split into - * sub-patterns and those are matched, too. - *

- * The precise rules are: - *

- * - *

- * An empty pattern matches only the empty text. - *

- */ -public final class TextMatcher { - - private static final Pattern NON_WORD = Pattern.compile("\\W+", Pattern.UNICODE_CHARACTER_CLASS); //$NON-NLS-1$ - - private final StringMatcher full; - - private final List parts; - - /** - * Creates a new {@link TextMatcher}. - * - * @param pattern to match - * @param ignoreCase whether to do case-insensitive matching - * @param ignoreWildCards whether to treat '?' and '*' as normal characters, not - * as wildcards - * @throws IllegalArgumentException if {@code pattern == null} - */ - public TextMatcher(String pattern, boolean ignoreCase, boolean ignoreWildCards) { - full = new StringMatcher(pattern.trim(), ignoreCase, ignoreWildCards); - parts = splitPattern(pattern, ignoreCase, ignoreWildCards); - } - - private List splitPattern(String pattern, - boolean ignoreCase, boolean ignoreWildCards) { - String pat = pattern.trim(); - if (pat.isEmpty()) { - return Collections.emptyList(); - } - String[] subPatterns = pat.split("\\s+"); //$NON-NLS-1$ - if (subPatterns.length <= 1) { - return Collections.emptyList(); - } - List matchers = new ArrayList<>(); - for (String s : subPatterns) { - if (s == null || s.isEmpty()) { - continue; - } - StringMatcher m = new StringMatcher(s, ignoreCase, ignoreWildCards); - m.usePrefixMatch(); - matchers.add(m); - } - return matchers; - } - - /** - * Determines whether the given {@code text} matches the pattern. - * - * @param text String to match; must not be {@code null} - * @return {@code true} if the whole {@code text} matches the pattern; - * {@code false} otherwise - * @throws IllegalArgumentException if {@code text == null} - */ - public boolean match(String text) { - if (text == null) { - throw new IllegalArgumentException(); - } - return match(text, 0, text.length()); - } - - /** - * Determines whether the given sub-string of {@code text} from {@code start} - * (inclusive) to {@code end} (exclusive) matches the pattern. - * - * @param text String to match in; must not be {@code null} - * @param start start index (inclusive) within {@code text} of the sub-string to - * match - * @param end end index (exclusive) within {@code text} of the sub-string to - * match - * @return {@code true} if the given slice of {@code text} matches the pattern; - * {@code false} otherwise - * @throws IllegalArgumentException if {@code text == null} - */ - public boolean match(String text, int start, int end) { - if (text == null) { - throw new IllegalArgumentException(); - } - if (start > end) { - return false; - } - int tlen = text.length(); - start = Math.max(0, start); - end = Math.min(end, tlen); - if (full.match(text, start, end)) { - return true; - } - String[] words = getWords(text.substring(start, end)); - if (match(full, words)) { - return true; - } - if (parts.isEmpty()) { - return false; - } - for (StringMatcher subMatcher : parts) { - if (!subMatcher.match(text, start, end) && !match(subMatcher, words)) { - return false; - } - } - return true; - } - - private boolean match(StringMatcher matcher, String[] words) { - return Arrays.stream(words).filter(Objects::nonNull).anyMatch(matcher::match); - } - - /** - * Splits a given text into words. - * - * @param text to split - * @return the words of the text - */ - public static String[] getWords(String text) { - // Previous implementations (in the removed StringMatcher) used the ICU - // BreakIterator to split the text. That worked well, but in 2020 it was decided - // to drop the dependency to the ICU library due to its size. The JDK - // BreakIterator splits differently, causing e.g. - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=563121 . The NON_WORD regexp - // appears to work well for programming language text, but may give sub-optimal - // results for natural languages. See also - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=90579 . - return NON_WORD.split(text); - } - - @Override - public String toString() { - return '[' + full.toString() + ',' + parts + ']'; - } -} diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java index 8664529313e..008255bf985 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/UiTestSuite.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -37,7 +37,6 @@ import org.eclipse.ui.tests.fieldassist.FieldAssistTestSuite; import org.eclipse.ui.tests.filteredtree.FilteredTreeTests; import org.eclipse.ui.tests.filteredtree.PatternFilterTest; -import org.eclipse.ui.tests.filteredtree.TextMatcherTest; import org.eclipse.ui.tests.internal.InternalTestSuite; import org.eclipse.ui.tests.intro.IntroTestSuite; import org.eclipse.ui.tests.keys.KeysTestSuite; @@ -91,7 +90,6 @@ ConcurrencyTestSuite.class, FilteredTreeTests.class, PatternFilterTest.class, - TextMatcherTest.class, StatusHandlingTestSuite.class, MenusTestSuite.class, QuickAccessTestSuite.class, diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/FilteredTreeTests.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/FilteredTreeTests.java index 190d8d6367e..c8975a4d898 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/FilteredTreeTests.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/FilteredTreeTests.java @@ -117,6 +117,12 @@ public void testAddAndRemovePattern() { applyPattern("0-0-0-0 name-*"); assertNumberOfTopLevelItems(1); + applyPattern(" 0-0-0-0 name-*"); + assertNumberOfTopLevelItems(1); + + applyPattern("0-0-0-0 name-* "); + assertNumberOfTopLevelItems(1); + applyPattern("0-0-0-0 name unknownWord"); assertNumberOfTopLevelItems(0); diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/TextMatcherTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/TextMatcherTest.java deleted file mode 100644 index 7f817e79951..00000000000 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/filteredtree/TextMatcherTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2020 Thomas Wolf and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.ui.tests.filteredtree; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import org.eclipse.ui.internal.misc.TextMatcher; -import org.junit.Test; - -/** - * Tests for {@link TextMatcher}. - */ -public class TextMatcherTest { - - @Test - public void testEmpty() { - assertTrue(new TextMatcher("", false, false).match("")); - assertFalse(new TextMatcher("", false, false).match("foo")); - assertFalse(new TextMatcher("", false, false).match("foo bar baz")); - assertTrue(new TextMatcher("", false, true).match("")); - assertFalse(new TextMatcher("", false, true).match("foo")); - assertFalse(new TextMatcher("", false, true).match("foo bar baz")); - } - - @Test - public void testSuffixes() { - assertFalse(new TextMatcher("fo*ar", false, false).match("foobar_123")); - assertFalse(new TextMatcher("fo*ar", false, false).match("foobar_baz")); - } - - @Test - public void testChinese() { - assertTrue(new TextMatcher("喜欢", false, false).match("我 喜欢 吃 苹果。")); - // This test would work only if word-splitting used the ICU BreakIterator. - // "Words" are as shown above. - // assertTrue(new TextMatcher("喜欢", false, false).match("我喜欢吃苹果。")); - } - - @Test - public void testSingleWords() { - assertTrue(new TextMatcher("huhn", false, false).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("h?hner", false, false).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("h*hner", false, false).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("hühner", false, false).match("hahn henne hühner küken huhn")); - // Full pattern must match word fully - assertFalse(new TextMatcher("h?hner", false, false).match("hahn henne hühnerhof küken huhn")); - assertFalse(new TextMatcher("h*hner", false, false).match("hahn henne hühnerhof küken huhn")); - assertFalse(new TextMatcher("hühner", false, false).match("hahn henne hühnerhof küken huhn")); - - assertTrue(new TextMatcher("huhn", false, true).match("hahn henne hühner küken huhn")); - assertFalse(new TextMatcher("h?hner", false, true).match("hahn henne hühner küken huhn")); - assertFalse(new TextMatcher("h*hner", false, true).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("hühner", false, true).match("hahn henne hühner küken huhn")); - // Full pattern must match word fully - assertFalse(new TextMatcher("h?hner", false, true).match("hahn henne hühnerhof küken huhn")); - assertFalse(new TextMatcher("h*hner", false, true).match("hahn henne hühnerhof küken huhn")); - assertFalse(new TextMatcher("hühner", false, true).match("hahn henne hühnerhof küken huhn")); - - // Bug 570390: Pattern starting/ending with whitespace should still match - assertTrue(new TextMatcher("hahn ", false, false).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher("huhn ", false, false).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher(" hahn", false, false).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher(" huhn", false, false).match("hahn henne hühnerhof küken huhn")); - } - - @Test - public void testMultipleWords() { - assertTrue(new TextMatcher("huhn h?hner", false, false).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("huhn h?hner", false, false).match("hahn henne hühnerhof küken huhn")); - assertFalse(new TextMatcher("huhn h?hner", false, true).match("hahn henne hühner küken huhn")); - assertFalse(new TextMatcher("huhn h?hner", false, true).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher("huhn h*hner", false, false).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("huhn h*hner", false, false).match("hahn henne hühnerhof küken huhn")); - assertFalse(new TextMatcher("huhn h*hner", false, true).match("hahn henne hühner küken huhn")); - assertFalse(new TextMatcher("huhn h*hner", false, true).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher("huhn hühner", false, false).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("huhn hühner", false, false).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher("huhn hühner", false, true).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("huhn hühner", false, true).match("hahn henne hühnerhof küken huhn")); - - // Bug 570390: Pattern starting/ending with whitespace should still match - assertTrue(new TextMatcher("huhn hahn ", false, false).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher("hahn huhn ", false, false).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher(" huhn hahn", false, false).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher(" hahn huhn", false, false).match("hahn henne hühnerhof küken huhn")); - } - - @Test - public void testCaseInsensitivity() { - assertTrue(new TextMatcher("Huhn HÜHNER", true, false).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("Huhn HÜHNER", true, false).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher("Huhn HÜHNER", true, true).match("hahn henne hühner küken huhn")); - assertTrue(new TextMatcher("Huhn HÜHNER", true, true).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher("HüHnEr", true, false).match("hahn henne hühner küken huhn")); - assertFalse(new TextMatcher("HüHnEr", true, false).match("hahn henne hühnerhof küken huhn")); - assertTrue(new TextMatcher("HüHnEr", true, true).match("hahn henne hühner küken huhn")); - assertFalse(new TextMatcher("HüHnEr", true, true).match("hahn henne hühnerhof küken huhn")); - } -}