Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support regular expressions in Editor search #12716

Merged
66 changes: 57 additions & 9 deletions vassal-app/src/main/java/VASSAL/configure/ConfigureTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import VASSAL.launch.EditorWindow;
import VASSAL.preferences.Prefs;
import VASSAL.search.SearchTarget;
import VASSAL.tools.BrowserSupport;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.NamedKeyStroke;
import VASSAL.tools.ReadErrorDialog;
Expand Down Expand Up @@ -126,6 +127,7 @@
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -1811,6 +1813,7 @@ public Class<? extends Buildable> getChild() {
private static class SearchParameters {
public static final String SEARCH_STRING = "searchString"; //$NON-NLS-1$//
public static final String MATCH_CASE = "matchCase"; //$NON-NLS-1$//
public static final String MATCH_REGEX = "matchRegex"; //$NON-NLS-1$//
public static final String MATCH_NAMES = "matchNames"; //$NON-NLS-1$//
public static final String MATCH_TYPES = "matchTypes"; //$NON-NLS-1$//
public static final String MATCH_ADVANCED = "matchAdvanced"; //$NON-NLS-1$//
Expand All @@ -1827,6 +1830,9 @@ private static class SearchParameters {
/** True if case-sensitive */
private boolean matchCase;

/** True if matching on a Regular Expression */
private boolean matchRegex;

/** True if match configurable names */
private boolean matchNames;

Expand Down Expand Up @@ -1866,6 +1872,7 @@ public SearchParameters() {

prefs.addOption(null, new StringConfigurer(SearchParameters.SEARCH_STRING, null, ""));
prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_CASE, null, false));
prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_REGEX, null, false));
prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_NAMES, null, true));
prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_TYPES, null, true));
prefs.addOption(null, new BooleanConfigurer(SearchParameters.MATCH_ADVANCED, null, false));
Expand All @@ -1878,6 +1885,7 @@ public SearchParameters() {

searchString = (String) prefs.getValue(SearchParameters.SEARCH_STRING);
matchCase = (Boolean)prefs.getValue(SearchParameters.MATCH_CASE);
matchRegex = (Boolean)prefs.getValue(SearchParameters.MATCH_REGEX);
matchNames = (Boolean)prefs.getValue(SearchParameters.MATCH_NAMES);
matchTypes = (Boolean)prefs.getValue(SearchParameters.MATCH_TYPES);
matchAdvanced = (Boolean)prefs.getValue(SearchParameters.MATCH_ADVANCED);
Expand All @@ -1892,9 +1900,10 @@ public SearchParameters() {
/**
* Constructs a new search parameters object
*/
public SearchParameters(String searchString, boolean matchCase, boolean matchNames, boolean matchTypes, boolean matchAdvanced, boolean matchTraits, boolean matchExpressions, boolean matchProperties, boolean matchKeys, boolean matchMenus, boolean matchMessages) {
public SearchParameters(String searchString, boolean matchCase, boolean matchRegex, boolean matchNames, boolean matchTypes, boolean matchAdvanced, boolean matchTraits, boolean matchExpressions, boolean matchProperties, boolean matchKeys, boolean matchMenus, boolean matchMessages) {
this.searchString = searchString;
this.matchCase = matchCase;
this.matchRegex = matchRegex;
this.matchNames = matchNames;
this.matchTypes = matchTypes;
this.matchAdvanced = matchAdvanced;
Expand All @@ -1919,6 +1928,10 @@ public boolean isMatchCase() {
return matchCase;
}

public boolean isMatchRegex() {
return matchRegex;
}

public void setMatchCase(boolean matchCase) {
this.matchCase = matchCase;
writePrefs();
Expand Down Expand Up @@ -2010,6 +2023,7 @@ public void setMatchMessages(boolean matchMessages) {
public void setFrom(final SearchParameters searchParameters) {
searchString = searchParameters.getSearchString();
matchCase = searchParameters.isMatchCase();
matchRegex = searchParameters.isMatchRegex();
matchNames = searchParameters.isMatchNames();
matchTypes = searchParameters.isMatchTypes();
matchAdvanced = searchParameters.isMatchAdvanced();
Expand All @@ -2026,6 +2040,7 @@ public void writePrefs() {
if (prefs != null) {
prefs.setValue(SEARCH_STRING, searchString);
prefs.setValue(MATCH_CASE, matchCase);
prefs.setValue(MATCH_REGEX, matchRegex);
prefs.setValue(MATCH_NAMES, matchNames);
prefs.setValue(MATCH_TYPES, matchTypes);
prefs.setValue(MATCH_ADVANCED, matchAdvanced);
Expand All @@ -2048,6 +2063,7 @@ public boolean equals(Object o) {
}
final SearchParameters that = (SearchParameters) o;
return isMatchCase() == that.isMatchCase() &&
isMatchRegex() == that.isMatchRegex() &&
isMatchNames() == that.isMatchNames() &&
isMatchTypes() == that.isMatchTypes() &&
isMatchTraits() == that.isMatchTraits() &&
Expand All @@ -2062,7 +2078,7 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(getSearchString(), isMatchCase(), isMatchNames(), isMatchTypes(), isMatchAdvanced(),
return Objects.hash(getSearchString(), isMatchCase(), isMatchRegex(), isMatchNames(), isMatchTypes(), isMatchAdvanced(),
isMatchTraits(), isMatchExpressions(), isMatchProperties(), isMatchKeys(),
isMatchMenus(), isMatchMessages());
}
Expand Down Expand Up @@ -2108,6 +2124,7 @@ public void actionPerformed(ActionEvent e) {
search.selectAll();

final JCheckBox sensitive = new JCheckBox(Resources.getString("Editor.search_case"), searchParameters.isMatchCase());
final JCheckBox regex = new JCheckBox(Resources.getString("Editor.search_regex"), searchParameters.isMatchRegex());
final JCheckBox advanced = new JCheckBox(Resources.getString("Editor.search_advanced"), searchParameters.isMatchAdvanced());

final JCheckBox names = new JCheckBox(Resources.getString("Editor.search_names"), searchParameters.isMatchNames());
Expand Down Expand Up @@ -2144,7 +2161,7 @@ public void actionPerformed(ActionEvent e) {
final JButton find = new JButton(Resources.getString("Editor.search_next"));
find.addActionListener(e12 -> {
final SearchParameters parametersSetInDialog =
new SearchParameters(search.getText(), sensitive.isSelected(), names.isSelected(), types.isSelected(), true, traits.isSelected(), expressions.isSelected(), properties.isSelected(), keys.isSelected(), menus.isSelected(), messages.isSelected());
new SearchParameters(search.getText(), sensitive.isSelected(), regex.isSelected(), names.isSelected(), types.isSelected(), true, traits.isSelected(), expressions.isSelected(), properties.isSelected(), keys.isSelected(), menus.isSelected(), messages.isSelected());

final boolean anyChanges = !searchParameters.equals(parametersSetInDialog);

Expand All @@ -2159,7 +2176,7 @@ public void actionPerformed(ActionEvent e) {
ConfigureTree.chat(Resources.getString("Editor.search_all_off"));
}

if (!searchParameters.getSearchString().isEmpty()) {
if (!searchParameters.getSearchString().isEmpty() && (!searchParameters.isMatchRegex() || isValidRegex(searchParameters.getSearchString()))) {
if (anyChanges) {
// Unless we're just continuing to the next match in an existing search, compute & display hit count
final int matches = getNumMatches(searchParameters.getSearchString());
Expand Down Expand Up @@ -2199,6 +2216,7 @@ public void actionPerformed(ActionEvent e) {

// options row
panel.add(sensitive);
panel.add(regex);
panel.add(advanced);

// Advanced 1
Expand Down Expand Up @@ -2241,7 +2259,17 @@ public void actionPerformed(ActionEvent e) {
}

private void showSearchHelp() {
// FIXME - Add Help ref
File dir = VASSAL.build.module.Documentation.getDocumentationBaseDir();
dir = new File(dir, "ReferenceManual"); //$NON-NLS-1$
final File theFile = new File(dir, "Search.html"); //$NON-NLS-1$
HelpFile h = null;
try {
h = new HelpFile(null, theFile, "#top"); //$NON-NLS-1$
}
catch (MalformedURLException e) {
ErrorDialog.bug(e);
}
BrowserSupport.openURL(h.getContents().toString());
}

/**
Expand Down Expand Up @@ -2626,18 +2654,38 @@ else if (c instanceof PrototypeDefinition) {
}
}

private boolean isValidRegex(String searchString) { // avoid exceptions by checking the Regex before use
try {
return "".matches(searchString) || true;
}
catch (java.util.regex.PatternSyntaxException e) {
chat("Search string is not a valid Regular Expression: " + e.getMessage()); //NON-NLS
return false;
}
}

/**
* Checks a single string against our search parameters
* @param target - string to check
* @param searchString - our search string
* @return true if this is a match based on our "matchCase" checkbox
* @return true if this is a match based on our "matchCase" & "matchRegex"checkboxes.
*/
private boolean checkString(String target, String searchString) {
if (searchParameters.isMatchCase()) {
return target.contains(searchString);
if (searchParameters.isMatchRegex()) {
if (searchParameters.isMatchCase()) {
return target.matches(searchString);
}
else {
return target.toLowerCase().matches(searchString.toLowerCase());
}
}
else {
return target.toLowerCase().contains(searchString.toLowerCase());
if (searchParameters.isMatchCase()) {
return target.contains(searchString);
}
else {
return target.toLowerCase().contains(searchString.toLowerCase());
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Editor.search=Search...
Editor.search_string=String to find
Editor.search_next=Find next
Editor.search_case=Exact case
Editor.search_regex=Regular Expression
Editor.search_advanced=Advanced search
Editor.search_names=Match names
Editor.search_types=Match [Class names]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ a|
===== Exact Case
Force an Exact case search (i.e. abc is different to ABC).

===== Regular Expression
The specified search string is a https://en.wikipedia.org/wiki/Regular_expression[Regular Expression]. In the simplest example, a search for ABC will find matches that equate wholly to "ABC", and lower or mixed case variants (unless _Exact Case_ is checked as well).

===== Advanced Search
Show the advanced search options. All search options are on in a simple search. The advanced options allow you to turn off options to narrow your search.

Expand Down