未验证 提交 1b8b377f 编写于 作者: G green9317 提交者: GitHub

feat(gui): allow use regex in the search dialog (PR #1069)

* Implements the option to use Regex on the Search Dialog

* Updated the way search works to pass a search settings class with options set rather than method arguments

* Fixing style issues

* Updating Style Fix

* Cleaning code

* Updating code to combine SearchSettings and Search Impl as well as efficiency improvements.

* Fixing bug caused from moving code in the searchImpl class

* Fixing a minor bug

* adding style fixes
上级 7227f1ac
...@@ -58,6 +58,7 @@ public abstract class CommonSearchDialog extends JDialog { ...@@ -58,6 +58,7 @@ public abstract class CommonSearchDialog extends JDialog {
protected String highlightText; protected String highlightText;
protected boolean highlightTextCaseInsensitive = false; protected boolean highlightTextCaseInsensitive = false;
protected boolean highlightTextUseRegex = false;
public CommonSearchDialog(MainWindow mainWindow) { public CommonSearchDialog(MainWindow mainWindow) {
super(mainWindow); super(mainWindow);
...@@ -466,6 +467,7 @@ public abstract class CommonSearchDialog extends JDialog { ...@@ -466,6 +467,7 @@ public abstract class CommonSearchDialog extends JDialog {
SearchContext searchContext = new SearchContext(highlightText); SearchContext searchContext = new SearchContext(highlightText);
searchContext.setMatchCase(!highlightTextCaseInsensitive); searchContext.setMatchCase(!highlightTextCaseInsensitive);
searchContext.setMarkAll(true); searchContext.setMarkAll(true);
searchContext.setRegularExpression(highlightTextUseRegex);
SearchEngine.markAll(textArea, searchContext); SearchEngine.markAll(textArea, searchContext);
} }
return textArea; return textArea;
......
...@@ -37,7 +37,8 @@ public class SearchDialog extends CommonSearchDialog { ...@@ -37,7 +37,8 @@ public class SearchDialog extends CommonSearchDialog {
METHOD, METHOD,
FIELD, FIELD,
CODE, CODE,
IGNORE_CASE IGNORE_CASE,
USE_REGEX
} }
private transient Set<SearchOptions> options; private transient Set<SearchOptions> options;
...@@ -86,6 +87,7 @@ public class SearchDialog extends CommonSearchDialog { ...@@ -86,6 +87,7 @@ public class SearchDialog extends CommonSearchDialog {
searchFieldSubscribe(); searchFieldSubscribe();
JCheckBox caseChBox = makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE); JCheckBox caseChBox = makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE);
JCheckBox regexChBox = makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX);
JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS); JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS);
JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD); JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD);
...@@ -102,6 +104,7 @@ public class SearchDialog extends CommonSearchDialog { ...@@ -102,6 +104,7 @@ public class SearchDialog extends CommonSearchDialog {
JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.options"))); searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.options")));
searchOptions.add(caseChBox); searchOptions.add(caseChBox);
searchOptions.add(regexChBox);
Box box = Box.createHorizontalBox(); Box box = Box.createHorizontalBox();
box.setAlignmentX(LEFT_ALIGNMENT); box.setAlignmentX(LEFT_ALIGNMENT);
...@@ -203,6 +206,7 @@ public class SearchDialog extends CommonSearchDialog { ...@@ -203,6 +206,7 @@ public class SearchDialog extends CommonSearchDialog {
String text = searchField.getText(); String text = searchField.getText();
highlightText = text; highlightText = text;
highlightTextCaseInsensitive = options.contains(SearchOptions.IGNORE_CASE); highlightTextCaseInsensitive = options.contains(SearchOptions.IGNORE_CASE);
highlightTextUseRegex = options.contains(SearchOptions.USE_REGEX);
cache.setLastSearch(text); cache.setLastSearch(text);
if (textSearch) { if (textSearch) {
......
...@@ -27,23 +27,23 @@ public class CodeIndex { ...@@ -27,23 +27,23 @@ public class CodeIndex {
values.removeIf(v -> v.getJavaNode().getTopParentClass().equals(cls)); values.removeIf(v -> v.getJavaNode().getTopParentClass().equals(cls));
} }
private boolean isMatched(StringRef key, String str, boolean caseInsensitive) { private boolean isMatched(StringRef key, SearchSettings searchSettings) {
return key.indexOf(str, caseInsensitive) != -1; return searchSettings.isMatch(key);
} }
public Flowable<CodeNode> search(final String searchStr, final boolean caseInsensitive) { public Flowable<CodeNode> search(final SearchSettings searchSettings) {
return Flowable.create(emitter -> { return Flowable.create(emitter -> {
LOG.debug("Code search started: {} ...", searchStr); LOG.debug("Code search started: {} ...", searchSettings.getSearchString());
for (CodeNode node : values) { for (CodeNode node : values) {
if (isMatched(node.getLineStr(), searchStr, caseInsensitive)) { if (isMatched(node.getLineStr(), searchSettings)) {
emitter.onNext(node); emitter.onNext(node);
} }
if (emitter.isCancelled()) { if (emitter.isCancelled()) {
LOG.debug("Code search canceled: {}", searchStr); LOG.debug("Code search canceled: {}", searchSettings.getSearchString());
return; return;
} }
} }
LOG.debug("Code search complete: {}, memory usage: {}", searchStr, UiUtils.memoryInfo()); LOG.debug("Code search complete: {}, memory usage: {}", searchSettings.getSearchString(), UiUtils.memoryInfo());
emitter.onComplete(); emitter.onComplete();
}, BackpressureStrategy.LATEST); }, BackpressureStrategy.LATEST);
} }
......
package jadx.gui.utils.search;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SearchSettings {
private static final Logger LOG = LoggerFactory.getLogger(SearchSettings.class);
private final String searchString;
private final boolean useRegex;
private final boolean ignoreCase;
private Pattern regexPattern;
private int startPos = 0;
public SearchSettings(String searchString, boolean ignoreCase, boolean useRegex) {
this.searchString = searchString;
this.useRegex = useRegex;
this.ignoreCase = ignoreCase;
}
/*
* Return whether Regex search should be done
*/
public boolean isUseRegex() {
return this.useRegex;
}
/*
* Return whether case will be ignored
*/
public boolean isIgnoreCase() {
return this.ignoreCase;
}
/*
* Return search string
*/
public String getSearchString() {
return this.searchString;
}
/*
* Return the starting index
*/
public int getStartPos() {
return this.startPos;
}
/*
* Set Starting Index
*/
public void setStartPos(int startPos) {
this.startPos = startPos;
}
/*
* get Regex Pattern
*/
public Pattern getPattern() {
return this.regexPattern;
}
/*
* Runs Pattern.compile if using Regex. If not using Regex return true
* return false is invalid Regex
*/
public boolean preCompile() {
try {
if (this.useRegex && this.ignoreCase) {
this.regexPattern = Pattern.compile(this.searchString, Pattern.CASE_INSENSITIVE);
} else if (this.useRegex) {
this.regexPattern = Pattern.compile(this.searchString);
}
} catch (Exception e) {
LOG.warn("Invalid Regex: {}", this.searchString);
return false;
}
return true;
}
/*
* Checks if searchArea matches the searched string found in searchSettings
*/
public boolean isMatch(StringRef searchArea) {
return isMatch(searchArea.toString());
}
/*
* Checks if searchArea matches the searched string found in searchSettings
*/
public boolean isMatch(String searchArea) {
return find(searchArea) != -1;
}
/*
* Returns the position within searchArea that the searched string found in searchSettings was
* identified.
* returns -1 if a match is not found
*/
public int find(StringRef searchArea) {
return find(searchArea.toString());
}
/*
* Returns the position within searchArea that the searched string found in searchSettings was
* identified.
* returns -1 if a match is not found
*/
public int find(String searchArea) {
int pos;
if (this.useRegex) {
Matcher matcher = this.regexPattern.matcher(searchArea);
if (matcher.find(this.startPos)) {
pos = matcher.start();
} else {
pos = -1;
}
} else if (this.ignoreCase) {
pos = StringUtils.indexOfIgnoreCase(searchArea, this.searchString, this.startPos);
} else {
pos = searchArea.indexOf(this.searchString, this.startPos);
}
return pos;
}
}
...@@ -3,8 +3,6 @@ package jadx.gui.utils.search; ...@@ -3,8 +3,6 @@ package jadx.gui.utils.search;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import io.reactivex.BackpressureStrategy; import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable; import io.reactivex.Flowable;
...@@ -22,19 +20,14 @@ public class SimpleIndex { ...@@ -22,19 +20,14 @@ public class SimpleIndex {
data.entrySet().removeIf(e -> e.getKey().getJavaNode().getTopParentClass().equals(cls)); data.entrySet().removeIf(e -> e.getKey().getJavaNode().getTopParentClass().equals(cls));
} }
private boolean isMatched(String str, String searchStr, boolean caseInsensitive) { private boolean isMatched(String str, SearchSettings searchSettings) {
if (caseInsensitive) { return searchSettings.isMatch(str);
return StringUtils.containsIgnoreCase(str, searchStr);
} else {
return str.contains(searchStr);
}
} }
public Flowable<JNode> search(final String searchStr, final boolean caseInsensitive) { public Flowable<JNode> search(final SearchSettings searchSettings) {
return Flowable.create(emitter -> { return Flowable.create(emitter -> {
for (Map.Entry<JNode, String> entry : data.entrySet()) { for (Map.Entry<JNode, String> entry : data.entrySet()) {
if (isMatched(entry.getValue(), searchSettings)) {
if (isMatched(entry.getValue(), searchStr, caseInsensitive)) {
emitter.onNext(entry.getKey()); emitter.onNext(entry.getKey());
} }
if (emitter.isCancelled()) { if (emitter.isCancelled()) {
......
...@@ -4,7 +4,6 @@ import java.util.ArrayList; ...@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -29,6 +28,7 @@ import static jadx.gui.ui.SearchDialog.SearchOptions.CODE; ...@@ -29,6 +28,7 @@ import static jadx.gui.ui.SearchDialog.SearchOptions.CODE;
import static jadx.gui.ui.SearchDialog.SearchOptions.FIELD; import static jadx.gui.ui.SearchDialog.SearchOptions.FIELD;
import static jadx.gui.ui.SearchDialog.SearchOptions.IGNORE_CASE; import static jadx.gui.ui.SearchDialog.SearchOptions.IGNORE_CASE;
import static jadx.gui.ui.SearchDialog.SearchOptions.METHOD; import static jadx.gui.ui.SearchDialog.SearchOptions.METHOD;
import static jadx.gui.ui.SearchDialog.SearchOptions.USE_REGEX;
public class TextSearchIndex { public class TextSearchIndex {
...@@ -94,39 +94,47 @@ public class TextSearchIndex { ...@@ -94,39 +94,47 @@ public class TextSearchIndex {
public Flowable<JNode> buildSearch(String text, Set<SearchDialog.SearchOptions> options) { public Flowable<JNode> buildSearch(String text, Set<SearchDialog.SearchOptions> options) {
boolean ignoreCase = options.contains(IGNORE_CASE); boolean ignoreCase = options.contains(IGNORE_CASE);
LOG.debug("Building search, ignoreCase: {}", ignoreCase); boolean useRegex = options.contains(USE_REGEX);
LOG.debug("Building search, ignoreCase: {}, useRegex: {}", ignoreCase, useRegex);
Flowable<JNode> result = Flowable.empty(); Flowable<JNode> result = Flowable.empty();
SearchSettings searchSettings = new SearchSettings(text, options.contains(IGNORE_CASE), options.contains(USE_REGEX));
if (!searchSettings.preCompile()) {
return result;
}
if (options.contains(CLASS)) { if (options.contains(CLASS)) {
result = Flowable.concat(result, clsNamesIndex.search(text, ignoreCase)); result = Flowable.concat(result, clsNamesIndex.search(searchSettings));
} }
if (options.contains(METHOD)) { if (options.contains(METHOD)) {
result = Flowable.concat(result, mthSignaturesIndex.search(text, ignoreCase)); result = Flowable.concat(result, mthSignaturesIndex.search(searchSettings));
} }
if (options.contains(FIELD)) { if (options.contains(FIELD)) {
result = Flowable.concat(result, fldSignaturesIndex.search(text, ignoreCase)); result = Flowable.concat(result, fldSignaturesIndex.search(searchSettings));
} }
if (options.contains(CODE)) { if (options.contains(CODE)) {
if (codeIndex.size() > 0) { if (codeIndex.size() > 0) {
result = Flowable.concat(result, codeIndex.search(text, ignoreCase)); result = Flowable.concat(result, codeIndex.search(searchSettings));
} }
if (!skippedClasses.isEmpty()) { if (!skippedClasses.isEmpty()) {
result = Flowable.concat(result, searchInSkippedClasses(text, ignoreCase)); result = Flowable.concat(result, searchInSkippedClasses(searchSettings));
} }
} }
return result; return result;
} }
public Flowable<CodeNode> searchInSkippedClasses(final String searchStr, final boolean caseInsensitive) { public Flowable<CodeNode> searchInSkippedClasses(final SearchSettings searchSettings) {
return Flowable.create(emitter -> { return Flowable.create(emitter -> {
LOG.debug("Skipped code search started: {} ...", searchStr); LOG.debug("Skipped code search started: {} ...", searchSettings.getSearchString());
for (JavaClass javaClass : skippedClasses) { for (JavaClass javaClass : skippedClasses) {
String code = javaClass.getCode(); String code = javaClass.getCode();
int pos = 0; int pos = 0;
while (pos != -1) { while (pos != -1) {
pos = searchNext(emitter, searchStr, javaClass, code, pos, caseInsensitive); searchSettings.setStartPos(pos);
pos = searchNext(emitter, javaClass, code, searchSettings);
if (emitter.isCancelled()) { if (emitter.isCancelled()) {
LOG.debug("Skipped Code search canceled: {}", searchStr); LOG.debug("Skipped Code search canceled: {}", searchSettings.getSearchString());
return; return;
} }
} }
...@@ -136,24 +144,19 @@ public class TextSearchIndex { ...@@ -136,24 +144,19 @@ public class TextSearchIndex {
return; return;
} }
} }
LOG.debug("Skipped code search complete: {}, memory usage: {}", searchStr, UiUtils.memoryInfo()); LOG.debug("Skipped code search complete: {}, memory usage: {}", searchSettings.getSearchString(), UiUtils.memoryInfo());
emitter.onComplete(); emitter.onComplete();
}, BackpressureStrategy.LATEST); }, BackpressureStrategy.LATEST);
} }
private int searchNext(FlowableEmitter<CodeNode> emitter, String text, JavaNode javaClass, String code, private int searchNext(FlowableEmitter<CodeNode> emitter, JavaNode javaClass, String code, final SearchSettings searchSettings) {
int startPos, boolean ignoreCase) {
int pos; int pos;
if (ignoreCase) { pos = searchSettings.find(code);
pos = StringUtils.indexOfIgnoreCase(code, text, startPos);
} else {
pos = code.indexOf(text, startPos);
}
if (pos == -1) { if (pos == -1) {
return -1; return -1;
} }
int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos); int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos);
int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length()); int lineEnd = code.indexOf(CodeWriter.NL, pos + searchSettings.getSearchString().length());
StringRef line = StringRef.subString(code, lineStart, lineEnd == -1 ? code.length() : lineEnd); StringRef line = StringRef.subString(code, lineStart, lineEnd == -1 ? code.length() : lineEnd);
emitter.onNext(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim())); emitter.onNext(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim()));
return lineEnd; return lineEnd;
......
...@@ -76,6 +76,7 @@ search_dialog.prev_page=Vorherige Seite anzeigen ...@@ -76,6 +76,7 @@ search_dialog.prev_page=Vorherige Seite anzeigen
search_dialog.info_label=Zeige Ergebnisse %1$d bis %2$d von %3$d search_dialog.info_label=Zeige Ergebnisse %1$d bis %2$d von %3$d
search_dialog.col_node=Knoten search_dialog.col_node=Knoten
search_dialog.col_code=Code search_dialog.col_code=Code
search_dialog.regex=Regex
usage_dialog.title=Verwendungssuche usage_dialog.title=Verwendungssuche
usage_dialog.label=Verwendung für: usage_dialog.label=Verwendung für:
......
...@@ -76,6 +76,7 @@ search_dialog.prev_page=Show previous page ...@@ -76,6 +76,7 @@ search_dialog.prev_page=Show previous page
search_dialog.info_label=Showing results %1$d to %2$d of %3$d search_dialog.info_label=Showing results %1$d to %2$d of %3$d
search_dialog.col_node=Node search_dialog.col_node=Node
search_dialog.col_code=Code search_dialog.col_code=Code
search_dialog.regex=Regex
usage_dialog.title=Usage search usage_dialog.title=Usage search
usage_dialog.label=Usage for: usage_dialog.label=Usage for:
......
...@@ -76,6 +76,7 @@ search_dialog.prev_page=Mostrar página anterior ...@@ -76,6 +76,7 @@ search_dialog.prev_page=Mostrar página anterior
search_dialog.info_label=Mostrando resultados %1$d a %2$d de %3$d search_dialog.info_label=Mostrando resultados %1$d a %2$d de %3$d
search_dialog.col_node=Nodo search_dialog.col_node=Nodo
search_dialog.col_code=Código search_dialog.col_code=Código
search_dialog.regex=Regex
usage_dialog.title=Usage search usage_dialog.title=Usage search
usage_dialog.label=Usage for: usage_dialog.label=Usage for:
......
...@@ -76,6 +76,7 @@ search_dialog.prev_page=上一页 ...@@ -76,6 +76,7 @@ search_dialog.prev_page=上一页
search_dialog.info_label=显示了 %3$d 个结果中的第 %1$d 至第 %2$d 个 search_dialog.info_label=显示了 %3$d 个结果中的第 %1$d 至第 %2$d 个
search_dialog.col_node=节点 search_dialog.col_node=节点
search_dialog.col_code=代码 search_dialog.col_code=代码
search_dialog.regex=正则表达式
usage_dialog.title=查找 usage_dialog.title=查找
usage_dialog.label=查找用例: usage_dialog.label=查找用例:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册