未验证 提交 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 {
protected String highlightText;
protected boolean highlightTextCaseInsensitive = false;
protected boolean highlightTextUseRegex = false;
public CommonSearchDialog(MainWindow mainWindow) {
super(mainWindow);
......@@ -466,6 +467,7 @@ public abstract class CommonSearchDialog extends JDialog {
SearchContext searchContext = new SearchContext(highlightText);
searchContext.setMatchCase(!highlightTextCaseInsensitive);
searchContext.setMarkAll(true);
searchContext.setRegularExpression(highlightTextUseRegex);
SearchEngine.markAll(textArea, searchContext);
}
return textArea;
......
......@@ -37,7 +37,8 @@ public class SearchDialog extends CommonSearchDialog {
METHOD,
FIELD,
CODE,
IGNORE_CASE
IGNORE_CASE,
USE_REGEX
}
private transient Set<SearchOptions> options;
......@@ -86,6 +87,7 @@ public class SearchDialog extends CommonSearchDialog {
searchFieldSubscribe();
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 mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD);
......@@ -102,6 +104,7 @@ public class SearchDialog extends CommonSearchDialog {
JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.options")));
searchOptions.add(caseChBox);
searchOptions.add(regexChBox);
Box box = Box.createHorizontalBox();
box.setAlignmentX(LEFT_ALIGNMENT);
......@@ -203,6 +206,7 @@ public class SearchDialog extends CommonSearchDialog {
String text = searchField.getText();
highlightText = text;
highlightTextCaseInsensitive = options.contains(SearchOptions.IGNORE_CASE);
highlightTextUseRegex = options.contains(SearchOptions.USE_REGEX);
cache.setLastSearch(text);
if (textSearch) {
......
......@@ -27,23 +27,23 @@ public class CodeIndex {
values.removeIf(v -> v.getJavaNode().getTopParentClass().equals(cls));
}
private boolean isMatched(StringRef key, String str, boolean caseInsensitive) {
return key.indexOf(str, caseInsensitive) != -1;
private boolean isMatched(StringRef key, SearchSettings searchSettings) {
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 -> {
LOG.debug("Code search started: {} ...", searchStr);
LOG.debug("Code search started: {} ...", searchSettings.getSearchString());
for (CodeNode node : values) {
if (isMatched(node.getLineStr(), searchStr, caseInsensitive)) {
if (isMatched(node.getLineStr(), searchSettings)) {
emitter.onNext(node);
}
if (emitter.isCancelled()) {
LOG.debug("Code search canceled: {}", searchStr);
LOG.debug("Code search canceled: {}", searchSettings.getSearchString());
return;
}
}
LOG.debug("Code search complete: {}, memory usage: {}", searchStr, UiUtils.memoryInfo());
LOG.debug("Code search complete: {}, memory usage: {}", searchSettings.getSearchString(), UiUtils.memoryInfo());
emitter.onComplete();
}, 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;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
......@@ -22,19 +20,14 @@ public class SimpleIndex {
data.entrySet().removeIf(e -> e.getKey().getJavaNode().getTopParentClass().equals(cls));
}
private boolean isMatched(String str, String searchStr, boolean caseInsensitive) {
if (caseInsensitive) {
return StringUtils.containsIgnoreCase(str, searchStr);
} else {
return str.contains(searchStr);
}
private boolean isMatched(String str, SearchSettings searchSettings) {
return searchSettings.isMatch(str);
}
public Flowable<JNode> search(final String searchStr, final boolean caseInsensitive) {
public Flowable<JNode> search(final SearchSettings searchSettings) {
return Flowable.create(emitter -> {
for (Map.Entry<JNode, String> entry : data.entrySet()) {
if (isMatched(entry.getValue(), searchStr, caseInsensitive)) {
if (isMatched(entry.getValue(), searchSettings)) {
emitter.onNext(entry.getKey());
}
if (emitter.isCancelled()) {
......
......@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -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.IGNORE_CASE;
import static jadx.gui.ui.SearchDialog.SearchOptions.METHOD;
import static jadx.gui.ui.SearchDialog.SearchOptions.USE_REGEX;
public class TextSearchIndex {
......@@ -94,39 +94,47 @@ public class TextSearchIndex {
public Flowable<JNode> buildSearch(String text, Set<SearchDialog.SearchOptions> options) {
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();
SearchSettings searchSettings = new SearchSettings(text, options.contains(IGNORE_CASE), options.contains(USE_REGEX));
if (!searchSettings.preCompile()) {
return result;
}
if (options.contains(CLASS)) {
result = Flowable.concat(result, clsNamesIndex.search(text, ignoreCase));
result = Flowable.concat(result, clsNamesIndex.search(searchSettings));
}
if (options.contains(METHOD)) {
result = Flowable.concat(result, mthSignaturesIndex.search(text, ignoreCase));
result = Flowable.concat(result, mthSignaturesIndex.search(searchSettings));
}
if (options.contains(FIELD)) {
result = Flowable.concat(result, fldSignaturesIndex.search(text, ignoreCase));
result = Flowable.concat(result, fldSignaturesIndex.search(searchSettings));
}
if (options.contains(CODE)) {
if (codeIndex.size() > 0) {
result = Flowable.concat(result, codeIndex.search(text, ignoreCase));
result = Flowable.concat(result, codeIndex.search(searchSettings));
}
if (!skippedClasses.isEmpty()) {
result = Flowable.concat(result, searchInSkippedClasses(text, ignoreCase));
result = Flowable.concat(result, searchInSkippedClasses(searchSettings));
}
}
return result;
}
public Flowable<CodeNode> searchInSkippedClasses(final String searchStr, final boolean caseInsensitive) {
public Flowable<CodeNode> searchInSkippedClasses(final SearchSettings searchSettings) {
return Flowable.create(emitter -> {
LOG.debug("Skipped code search started: {} ...", searchStr);
LOG.debug("Skipped code search started: {} ...", searchSettings.getSearchString());
for (JavaClass javaClass : skippedClasses) {
String code = javaClass.getCode();
int pos = 0;
while (pos != -1) {
pos = searchNext(emitter, searchStr, javaClass, code, pos, caseInsensitive);
searchSettings.setStartPos(pos);
pos = searchNext(emitter, javaClass, code, searchSettings);
if (emitter.isCancelled()) {
LOG.debug("Skipped Code search canceled: {}", searchStr);
LOG.debug("Skipped Code search canceled: {}", searchSettings.getSearchString());
return;
}
}
......@@ -136,24 +144,19 @@ public class TextSearchIndex {
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();
}, BackpressureStrategy.LATEST);
}
private int searchNext(FlowableEmitter<CodeNode> emitter, String text, JavaNode javaClass, String code,
int startPos, boolean ignoreCase) {
private int searchNext(FlowableEmitter<CodeNode> emitter, JavaNode javaClass, String code, final SearchSettings searchSettings) {
int pos;
if (ignoreCase) {
pos = StringUtils.indexOfIgnoreCase(code, text, startPos);
} else {
pos = code.indexOf(text, startPos);
}
pos = searchSettings.find(code);
if (pos == -1) {
return -1;
}
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);
emitter.onNext(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim()));
return lineEnd;
......
......@@ -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.col_node=Knoten
search_dialog.col_code=Code
search_dialog.regex=Regex
usage_dialog.title=Verwendungssuche
usage_dialog.label=Verwendung für:
......
......@@ -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.col_node=Node
search_dialog.col_code=Code
search_dialog.regex=Regex
usage_dialog.title=Usage search
usage_dialog.label=Usage for:
......
......@@ -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.col_node=Nodo
search_dialog.col_code=Código
search_dialog.regex=Regex
usage_dialog.title=Usage search
usage_dialog.label=Usage for:
......
......@@ -76,6 +76,7 @@ search_dialog.prev_page=上一页
search_dialog.info_label=显示了 %3$d 个结果中的第 %1$d 至第 %2$d 个
search_dialog.col_node=节点
search_dialog.col_code=代码
search_dialog.regex=正则表达式
usage_dialog.title=查找
usage_dialog.label=查找用例:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册