mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-25 21:51:47 +00:00
Merge remote-tracking branch
'origin/GP-3491-dragonmacher-decompiler-find-window--SQUASHED' (Closes #5317, #538)
This commit is contained in:
commit
b68fa6c745
@ -67,8 +67,7 @@ public class FindPossibleReferencesPlugin extends Plugin {
|
||||
final static String RESTORE_SELECTION_ACTION_NAME = "Restore Direct Refs Search Selection";
|
||||
final static String SEARCH_DIRECT_REFS_ACTION_NAME = "Search for Direct References";
|
||||
final static String SEARCH_DIRECT_REFS_ACTION_HELP = "Direct_Refs_Search_Alignment";
|
||||
private DockingAction action;
|
||||
private ArrayList<TableComponentProvider<ReferenceAddressPair>> providerList;
|
||||
private List<TableComponentProvider<ReferenceAddressPair>> providerList;
|
||||
|
||||
public FindPossibleReferencesPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
@ -98,22 +97,21 @@ public class FindPossibleReferencesPlugin extends Plugin {
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
action = new ActionBuilder(SEARCH_DIRECT_REFS_ACTION_NAME, getName())
|
||||
.menuPath(ToolConstants.MENU_SEARCH, "For Direct References")
|
||||
.menuGroup("search for", "DirectReferences")
|
||||
.helpLocation(new HelpLocation(HelpTopics.SEARCH, SEARCH_DIRECT_REFS_ACTION_NAME))
|
||||
.description(getPluginDescription().getDescription())
|
||||
.withContext(ListingActionContext.class, true)
|
||||
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
|
||||
.onAction(this::findReferences)
|
||||
.enabledWhen(this::hasCorrectAddressSize)
|
||||
.buildAndInstall(tool);
|
||||
new ActionBuilder(SEARCH_DIRECT_REFS_ACTION_NAME, getName())
|
||||
.menuPath(ToolConstants.MENU_SEARCH, "For Direct References")
|
||||
.menuGroup("search for", "DirectReferences")
|
||||
.helpLocation(new HelpLocation(HelpTopics.SEARCH, SEARCH_DIRECT_REFS_ACTION_NAME))
|
||||
.description(getPluginDescription().getDescription())
|
||||
.withContext(ListingActionContext.class, true)
|
||||
.inWindow(ActionBuilder.When.CONTEXT_MATCHES)
|
||||
.onAction(this::findReferences)
|
||||
.enabledWhen(this::hasCorrectAddressSize)
|
||||
.buildAndInstall(tool);
|
||||
|
||||
}
|
||||
|
||||
private boolean hasCorrectAddressSize(NavigatableActionContext context) {
|
||||
int size =
|
||||
context.getProgram().getAddressFactory().getDefaultAddressSpace().getSize();
|
||||
int size = context.getProgram().getAddressFactory().getDefaultAddressSpace().getSize();
|
||||
if ((size == 64) || (size == 32) || (size == 24) || (size == 16) || (size == 20) ||
|
||||
(size == 21)) {
|
||||
return true;
|
||||
@ -142,10 +140,10 @@ public class FindPossibleReferencesPlugin extends Plugin {
|
||||
localAction.setHelpLocation(
|
||||
new HelpLocation(HelpTopics.SEARCH, RESTORE_SELECTION_ACTION_NAME));
|
||||
String group = "selection";
|
||||
localAction.setMenuBarData(
|
||||
new MenuData(new String[] { "Restore Search Selection" }, group));
|
||||
localAction.setPopupMenuData(
|
||||
new MenuData(new String[] { "Restore Search Selection" }, group));
|
||||
localAction
|
||||
.setMenuBarData(new MenuData(new String[] { "Restore Search Selection" }, group));
|
||||
localAction
|
||||
.setPopupMenuData(new MenuData(new String[] { "Restore Search Selection" }, group));
|
||||
|
||||
localAction.setDescription(
|
||||
"Sets the program selection back to the selection this search was based upon.");
|
||||
@ -194,9 +192,8 @@ public class FindPossibleReferencesPlugin extends Plugin {
|
||||
return;
|
||||
}
|
||||
if (currentProgram.getMemory()
|
||||
.getBlock(
|
||||
fromAddr)
|
||||
.getType() == MemoryBlockType.BIT_MAPPED) {
|
||||
.getBlock(fromAddr)
|
||||
.getType() == MemoryBlockType.BIT_MAPPED) {
|
||||
Msg.showWarn(getClass(), null, "Search For Direct References",
|
||||
"Cannot search for direct references on bit memory!");
|
||||
return;
|
||||
|
@ -45,6 +45,8 @@ import ghidra.util.HelpLocation;
|
||||
import ghidra.util.table.*;
|
||||
import ghidra.util.table.actions.DeleteTableRowAction;
|
||||
import ghidra.util.table.actions.MakeProgramSelectionAction;
|
||||
import utility.function.Callback;
|
||||
import utility.function.Dummy;
|
||||
|
||||
public class TableComponentProvider<T> extends ComponentProviderAdapter
|
||||
implements TableModelListener, NavigatableRemovalListener {
|
||||
@ -60,11 +62,13 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
||||
private String programName;
|
||||
private String windowSubMenu;
|
||||
private List<ComponentProviderActivationListener> activationListenerList = new ArrayList<>();
|
||||
private Callback closedCallback = Dummy.callback();
|
||||
|
||||
private Navigatable navigatable;
|
||||
private SelectionNavigationAction selectionNavigationAction;
|
||||
private DockingAction selectAction;
|
||||
private DockingAction removeItemsAction;
|
||||
private DockingAction externalGotoAction;
|
||||
|
||||
private Function<MouseEvent, ActionContext> contextProvider = null;
|
||||
|
||||
@ -170,7 +174,7 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
||||
selectionNavigationAction
|
||||
.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation"));
|
||||
|
||||
DockingAction externalGotoAction = new DockingAction("Go to External Location", getName()) {
|
||||
externalGotoAction = new DockingAction("Go to External Location", getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
gotoExternalAddress(getSelectedExternalAddress());
|
||||
@ -203,9 +207,21 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
||||
new MenuData(new String[] { "GoTo External Location" }, icon, null));
|
||||
externalGotoAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Navigation"));
|
||||
|
||||
plugin.getTool().addLocalAction(this, selectAction);
|
||||
plugin.getTool().addLocalAction(this, selectionNavigationAction);
|
||||
plugin.getTool().addLocalAction(this, externalGotoAction);
|
||||
tool.addLocalAction(this, selectAction);
|
||||
tool.addLocalAction(this, selectionNavigationAction);
|
||||
tool.addLocalAction(this, externalGotoAction);
|
||||
}
|
||||
|
||||
public void removeAllActions() {
|
||||
tool.removeLocalAction(this, externalGotoAction);
|
||||
tool.removeLocalAction(this, selectAction);
|
||||
tool.removeLocalAction(this, selectionNavigationAction);
|
||||
|
||||
// this action is conditionally added
|
||||
if (removeItemsAction != null) {
|
||||
tool.removeAction(removeItemsAction);
|
||||
removeItemsAction = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void installRemoveItemsAction() {
|
||||
@ -295,6 +311,8 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
||||
|
||||
@Override
|
||||
public void closeComponent() {
|
||||
this.closedCallback.call();
|
||||
|
||||
if (navigatable != null) {
|
||||
navigatable.removeNavigatableListener(this);
|
||||
}
|
||||
@ -419,4 +437,12 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
||||
public void setActionContextProvider(Function<MouseEvent, ActionContext> contextProvider) {
|
||||
this.contextProvider = contextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener to know when this provider is closed.
|
||||
* @param c the callback
|
||||
*/
|
||||
public void setClosedCallback(Callback c) {
|
||||
this.closedCallback = Dummy.ifNull(c);
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,7 @@ import java.awt.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.widgets.SearchLocation;
|
||||
import docking.widgets.fieldpanel.Layout;
|
||||
import docking.widgets.fieldpanel.LayoutModel;
|
||||
import docking.widgets.fieldpanel.field.*;
|
||||
@ -31,12 +27,10 @@ import docking.widgets.fieldpanel.listener.IndexMapper;
|
||||
import docking.widgets.fieldpanel.listener.LayoutModelListener;
|
||||
import docking.widgets.fieldpanel.support.*;
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
|
||||
import ghidra.app.util.viewer.field.CommentUtils;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Control the GUI layout for displaying tokenized C code
|
||||
@ -335,230 +329,11 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
|
||||
return null;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Search Related Methods
|
||||
//==================================================================================================
|
||||
|
||||
private SearchLocation findNextTokenGoingForward(
|
||||
java.util.function.Function<String, SearchMatch> matcher, String searchString,
|
||||
FieldLocation currentLocation) {
|
||||
|
||||
int startRow = currentLocation.getIndex().intValue();
|
||||
for (int row = startRow; row < fieldList.length; row++) {
|
||||
ClangTextField field = (ClangTextField) fieldList[row];
|
||||
FieldLocation location = (row == startRow) ? currentLocation : null;
|
||||
String lineText = getLineTextFromOffset(location, field, true);
|
||||
SearchMatch match = matcher.apply(lineText);
|
||||
if (match == SearchMatch.NO_MATCH) {
|
||||
continue;
|
||||
}
|
||||
if (row == startRow) { // cursor is on this line
|
||||
//
|
||||
// The match start for all lines without the cursor will be relative to the start
|
||||
// of the line, which is 0. However, when searching on the row with the cursor,
|
||||
// the match start is relative to the cursor position. Update the start to
|
||||
// compensate for the difference between the start of the line and the cursor.
|
||||
//
|
||||
String fullLine = field.getText();
|
||||
int cursorOffset = fullLine.length() - lineText.length();
|
||||
match.start += cursorOffset;
|
||||
match.end += cursorOffset;
|
||||
}
|
||||
|
||||
// we use 0 here because currently there is only one field, which is the entire line
|
||||
int fieldNum = 0;
|
||||
int column = getScreenColumnFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation = new FieldLocation(row, fieldNum, 0, column);
|
||||
|
||||
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||
searchString, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SearchLocation findNextTokenGoingBackward(
|
||||
java.util.function.Function<String, SearchMatch> matcher, String searchString,
|
||||
FieldLocation currentLocation) {
|
||||
|
||||
int startRow = currentLocation.getIndex().intValue();
|
||||
for (int row = startRow; row >= 0; row--) {
|
||||
ClangTextField field = (ClangTextField) fieldList[row];
|
||||
FieldLocation location = (row == startRow) ? currentLocation : null;
|
||||
String lineText = getLineTextFromOffset(location, field, false);
|
||||
|
||||
SearchMatch match = matcher.apply(lineText);
|
||||
if (match != SearchMatch.NO_MATCH) {
|
||||
|
||||
// we use 0 here because currently there is only one field, which is the entire line
|
||||
int fieldNum = 0;
|
||||
int column = getScreenColumnFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation = new FieldLocation(row, fieldNum, 0, column);
|
||||
|
||||
return new FieldBasedSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||
searchString, false);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public SearchLocation findNextTokenForSearchRegex(String searchString,
|
||||
FieldLocation currentLocation, boolean forwardSearch) {
|
||||
|
||||
Pattern pattern = null;
|
||||
try {
|
||||
pattern = Pattern.compile(searchString, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
|
||||
}
|
||||
catch (PatternSyntaxException e) {
|
||||
Msg.showError(this, decompilerPanel, "Regular Expression Syntax Error", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
Pattern finalPattern = pattern;
|
||||
if (forwardSearch) {
|
||||
|
||||
java.util.function.Function<String, SearchMatch> function = textLine -> {
|
||||
|
||||
Matcher matcher = finalPattern.matcher(textLine);
|
||||
if (matcher.find()) {
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
return new SearchMatch(start, end, textLine);
|
||||
}
|
||||
|
||||
return SearchMatch.NO_MATCH;
|
||||
};
|
||||
|
||||
return findNextTokenGoingForward(function, searchString, currentLocation);
|
||||
}
|
||||
|
||||
java.util.function.Function<String, SearchMatch> reverse = textLine -> {
|
||||
|
||||
Matcher matcher = finalPattern.matcher(textLine);
|
||||
|
||||
if (!matcher.find()) {
|
||||
return SearchMatch.NO_MATCH;
|
||||
}
|
||||
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
|
||||
// Since the matcher can only match from the start to end of line, we need
|
||||
// to find all matches and then take the last match
|
||||
while (matcher.find()) {
|
||||
start = matcher.start();
|
||||
end = matcher.end();
|
||||
}
|
||||
|
||||
return new SearchMatch(start, end, textLine);
|
||||
};
|
||||
|
||||
return findNextTokenGoingBackward(reverse, searchString, currentLocation);
|
||||
}
|
||||
|
||||
public SearchLocation findNextTokenForSearch(String searchString, FieldLocation currentLocation,
|
||||
boolean forwardSearch) {
|
||||
|
||||
if (forwardSearch) {
|
||||
|
||||
java.util.function.Function<String, SearchMatch> function = textLine -> {
|
||||
|
||||
int index = StringUtils.indexOfIgnoreCase(textLine, searchString);
|
||||
if (index == -1) {
|
||||
return SearchMatch.NO_MATCH;
|
||||
}
|
||||
|
||||
return new SearchMatch(index, index + searchString.length(), textLine);
|
||||
};
|
||||
|
||||
return findNextTokenGoingForward(function, searchString, currentLocation);
|
||||
}
|
||||
|
||||
java.util.function.Function<String, SearchMatch> function = textLine -> {
|
||||
|
||||
int index = StringUtils.lastIndexOfIgnoreCase(textLine, searchString);
|
||||
if (index == -1) {
|
||||
return SearchMatch.NO_MATCH;
|
||||
}
|
||||
return new SearchMatch(index, index + searchString.length(), textLine);
|
||||
};
|
||||
|
||||
return findNextTokenGoingBackward(function, searchString, currentLocation);
|
||||
}
|
||||
|
||||
private String getLineTextFromOffset(FieldLocation location, ClangTextField textField,
|
||||
boolean forwardSearch) {
|
||||
|
||||
if (location == null) { // the cursor location is not on this line; use all of the text
|
||||
return textField.getText();
|
||||
}
|
||||
|
||||
if (textField.getText().isEmpty()) { // the cursor is on blank line
|
||||
return "";
|
||||
}
|
||||
|
||||
String lineText = textField.getText();
|
||||
if (forwardSearch) {
|
||||
|
||||
int nextCol = location.getCol();
|
||||
|
||||
// protects against the location column being out of range (this can happen if we're
|
||||
// searching forward and the cursor is past the last token)
|
||||
if (nextCol >= lineText.length()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// skip a character to start the next search; this prevents matching the previous match
|
||||
return lineText.substring(nextCol);
|
||||
}
|
||||
|
||||
// backwards search
|
||||
return lineText.substring(0, location.getCol());
|
||||
}
|
||||
|
||||
private int getScreenColumnFromOffset(int textOffset, ClangTextField textField) {
|
||||
RowColLocation rowColLocation = textField.textOffsetToScreenLocation(textOffset);
|
||||
return rowColLocation.col();
|
||||
}
|
||||
|
||||
private static class SearchMatch {
|
||||
private static SearchMatch NO_MATCH = new SearchMatch(-1, -1, null);
|
||||
private int start;
|
||||
private int end;
|
||||
private String textLine;
|
||||
|
||||
SearchMatch(int start, int end, String textLine) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.textLine = textLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == NO_MATCH) {
|
||||
return "NO MATCH";
|
||||
}
|
||||
return "[start=" + start + ",end=" + end + "]: " + textLine;
|
||||
}
|
||||
}
|
||||
//==================================================================================================
|
||||
// End Search Related Methods
|
||||
//==================================================================================================
|
||||
|
||||
ClangToken getTokenForLocation(FieldLocation fieldLocation) {
|
||||
int row = fieldLocation.getIndex().intValue();
|
||||
ClangTextField field = (ClangTextField) fieldList[row];
|
||||
return field.getToken(fieldLocation);
|
||||
}
|
||||
|
||||
public void locationChanged(FieldLocation loc, Field field, Color locationColor,
|
||||
Color parenColor) {
|
||||
// Highlighting is now handled through the decompiler panel's highlight controller.
|
||||
}
|
||||
|
||||
public boolean changePending() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushChanges() {
|
||||
// nothing to do
|
||||
|
@ -0,0 +1,219 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.decompiler.component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.ListSelectionModel;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.Tool;
|
||||
import docking.widgets.FindDialog;
|
||||
import docking.widgets.SearchLocation;
|
||||
import docking.widgets.button.GButton;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import docking.widgets.table.AbstractDynamicTableColumnStub;
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
|
||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearcher;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.app.util.query.TableService;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.table.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DecompilerFindDialog extends FindDialog {
|
||||
|
||||
private DecompilerPanel decompilerPanel;
|
||||
|
||||
public DecompilerFindDialog(DecompilerPanel decompilerPanel) {
|
||||
super("Decompiler Find Text", new DecompilerSearcher(decompilerPanel));
|
||||
this.decompilerPanel = decompilerPanel;
|
||||
|
||||
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
||||
|
||||
GButton showAllButton = new GButton("Search All");
|
||||
showAllButton.addActionListener(e -> showAll());
|
||||
|
||||
// move this button to the end
|
||||
removeButton(dismissButton);
|
||||
|
||||
addButton(showAllButton);
|
||||
addButton(dismissButton);
|
||||
}
|
||||
|
||||
private void showAll() {
|
||||
close();
|
||||
|
||||
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
|
||||
Tool tool = dwm.getTool();
|
||||
TableService tableService = tool.getService(TableService.class);
|
||||
if (tableService == null) {
|
||||
Msg.error(this,
|
||||
"Cannot use the Decompiler Search All action without having a TableService " +
|
||||
"installed");
|
||||
return;
|
||||
}
|
||||
|
||||
List<SearchLocation> results = searcher.searchAll(getSearchText(), useRegex());
|
||||
if (!results.isEmpty()) {
|
||||
// save off searches that find results so users can reuse them later
|
||||
storeSearchText(getSearchText());
|
||||
}
|
||||
|
||||
Program program = decompilerPanel.getProgram();
|
||||
DecompilerFindResultsModel model = new DecompilerFindResultsModel(tool, program, results);
|
||||
|
||||
String title = "Decompiler Search '%s'".formatted(getSearchText());
|
||||
String type = "Decompiler Search ";
|
||||
String subMenuName = "Search";
|
||||
TableComponentProvider<DecompilerSearchLocation> provider =
|
||||
tableService.showTable(title, type, model, subMenuName, null);
|
||||
|
||||
// The Decompiler does not support some of the table's basic actions, such as making
|
||||
// selections for a given row, so remove them.
|
||||
provider.removeAllActions();
|
||||
provider.installRemoveItemsAction();
|
||||
|
||||
GhidraThreadedTablePanel<DecompilerSearchLocation> panel = provider.getThreadedTablePanel();
|
||||
GhidraTable table = panel.getTable();
|
||||
|
||||
// add row listener to go to the field for that row
|
||||
ListSelectionModel selectionModel = table.getSelectionModel();
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
selectionModel.addListSelectionListener(lse -> {
|
||||
if (lse.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int row = table.getSelectedRow();
|
||||
if (row == -1) {
|
||||
searcher.highlightSearchResults(null);
|
||||
return;
|
||||
}
|
||||
|
||||
DecompilerSearchLocation location = model.getRowObject(row);
|
||||
|
||||
notifySearchHit(location);
|
||||
});
|
||||
|
||||
// add listener to table closed to clear highlights
|
||||
provider.setClosedCallback(() -> decompilerPanel.setSearchResults(null));
|
||||
|
||||
// set the tab text to the short and descriptive search term
|
||||
provider.setTabText("'%s'".formatted(getSearchText()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dialogClosed() {
|
||||
// clear the search results when the dialog is closed
|
||||
decompilerPanel.setSearchResults(null);
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Inner Classes
|
||||
//=================================================================================================
|
||||
|
||||
private class DecompilerFindResultsModel
|
||||
extends GhidraProgramTableModel<DecompilerSearchLocation> {
|
||||
|
||||
private List<DecompilerSearchLocation> searchLocations;
|
||||
|
||||
DecompilerFindResultsModel(ServiceProvider sp, Program program,
|
||||
List<SearchLocation> searchLocations) {
|
||||
super("Decompiler Search All Results", sp, program, null);
|
||||
this.searchLocations = searchLocations.stream()
|
||||
.map(l -> (DecompilerSearchLocation) l)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<DecompilerSearchLocation> createTableColumnDescriptor() {
|
||||
|
||||
TableColumnDescriptor<DecompilerSearchLocation> descriptor =
|
||||
new TableColumnDescriptor<>();
|
||||
descriptor.addVisibleColumn(new LineNumberColumn(), 1, true);
|
||||
descriptor.addVisibleColumn(new ContextColumn());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<DecompilerSearchLocation> accumulator,
|
||||
TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
for (DecompilerSearchLocation location : searchLocations) {
|
||||
accumulator.add(location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramLocation getProgramLocation(int modelRow, int modelColumn) {
|
||||
return null; // This doesn't really make sense for this model
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramSelection getProgramSelection(int[] modelRows) {
|
||||
return new ProgramSelection(); // This doesn't really make sense for this model
|
||||
}
|
||||
|
||||
private class LineNumberColumn
|
||||
extends AbstractDynamicTableColumnStub<DecompilerSearchLocation, Integer> {
|
||||
|
||||
@Override
|
||||
public Integer getValue(DecompilerSearchLocation rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
FieldLocation fieldLocation = rowObject.getFieldLocation();
|
||||
return fieldLocation.getIndex().intValue() + 1; // +1 for 1-based lines
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Line";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 75;
|
||||
}
|
||||
}
|
||||
|
||||
private class ContextColumn
|
||||
extends AbstractDynamicTableColumnStub<DecompilerSearchLocation, String> {
|
||||
|
||||
@Override
|
||||
public String getValue(DecompilerSearchLocation rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getTextLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Context";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -42,7 +42,7 @@ import ghidra.app.decompiler.*;
|
||||
import ghidra.app.decompiler.component.hover.DecompilerHoverService;
|
||||
import ghidra.app.decompiler.component.margin.*;
|
||||
import ghidra.app.plugin.core.decompile.DecompilerClipboardProvider;
|
||||
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
|
||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
|
||||
import ghidra.app.util.viewer.util.ScrollpaneAlignedHorizontalLayout;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Function;
|
||||
@ -988,33 +988,15 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
||||
location.getIndex().intValue(), location.col);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Search Methods
|
||||
//==================================================================================================
|
||||
|
||||
public SearchLocation searchText(String text, FieldLocation startLocation,
|
||||
boolean forwardDirection) {
|
||||
return layoutController.findNextTokenForSearch(text, startLocation, forwardDirection);
|
||||
}
|
||||
|
||||
public SearchLocation searchTextRegex(String text, FieldLocation startLocation,
|
||||
boolean forwardDirection) {
|
||||
return layoutController.findNextTokenForSearchRegex(text, startLocation, forwardDirection);
|
||||
}
|
||||
|
||||
public void setSearchResults(SearchLocation searchLocation) {
|
||||
currentSearchLocation = searchLocation;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public FieldBasedSearchLocation getSearchResults() {
|
||||
return (FieldBasedSearchLocation) currentSearchLocation;
|
||||
public DecompilerSearchLocation getSearchResults() {
|
||||
return (DecompilerSearchLocation) currentSearchLocation;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// End Search Methods
|
||||
//==================================================================================================
|
||||
|
||||
public Color getCurrentVariableHighlightColor() {
|
||||
return currentVariableHighlightColor;
|
||||
}
|
||||
@ -1279,7 +1261,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
|
||||
int highlightLine = cField.getLineNumber();
|
||||
|
||||
FieldLocation searchCursorLocation =
|
||||
((FieldBasedSearchLocation) currentSearchLocation).getFieldLocation();
|
||||
((DecompilerSearchLocation) currentSearchLocation).getFieldLocation();
|
||||
int searchLineNumber = searchCursorLocation.getIndex().intValue() + 1;
|
||||
if (highlightLine != searchLineNumber) {
|
||||
// only highlight the match on the actual line
|
||||
|
@ -19,21 +19,27 @@ import docking.widgets.CursorPosition;
|
||||
import docking.widgets.SearchLocation;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
|
||||
public class FieldBasedSearchLocation extends SearchLocation {
|
||||
public class DecompilerSearchLocation extends SearchLocation {
|
||||
|
||||
private final FieldLocation fieldLocation;
|
||||
private String textLine;
|
||||
|
||||
public FieldBasedSearchLocation(FieldLocation fieldLocation, int startIndexInclusive,
|
||||
int endIndexInclusive, String searchText, boolean forwardDirection) {
|
||||
public DecompilerSearchLocation(FieldLocation fieldLocation, int startIndexInclusive,
|
||||
int endIndexInclusive, String searchText, boolean forwardDirection, String textLine) {
|
||||
|
||||
super(startIndexInclusive, endIndexInclusive, searchText, forwardDirection);
|
||||
this.fieldLocation = fieldLocation;
|
||||
this.textLine = textLine;
|
||||
}
|
||||
|
||||
public FieldLocation getFieldLocation() {
|
||||
return fieldLocation;
|
||||
}
|
||||
|
||||
public String getTextLine() {
|
||||
return textLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorPosition getCursorPosition() {
|
||||
return new DecompilerCursorPosition(fieldLocation);
|
@ -15,14 +15,18 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.decompile.actions;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.*;
|
||||
|
||||
import docking.widgets.*;
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import docking.widgets.fieldpanel.support.RowColLocation;
|
||||
import ghidra.app.decompiler.component.ClangTextField;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.UserSearchUtils;
|
||||
|
||||
/**
|
||||
* A {@link FindDialogSearcher} for searching the text of the decompiler window.
|
||||
@ -86,15 +90,14 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||
DecompilerCursorPosition decompilerCursorPosition = (DecompilerCursorPosition) position;
|
||||
FieldLocation startLocation =
|
||||
getNextSearchStartLocation(decompilerCursorPosition, searchForward);
|
||||
return useRegex ? decompilerPanel.searchTextRegex(text, startLocation, searchForward)
|
||||
: decompilerPanel.searchText(text, startLocation, searchForward);
|
||||
return doFind(text, startLocation, searchForward, useRegex);
|
||||
}
|
||||
|
||||
private FieldLocation getNextSearchStartLocation(
|
||||
DecompilerCursorPosition decompilerCursorPosition, boolean searchForward) {
|
||||
|
||||
FieldLocation startLocation = decompilerCursorPosition.getFieldLocation();
|
||||
FieldBasedSearchLocation currentSearchLocation = decompilerPanel.getSearchResults();
|
||||
DecompilerSearchLocation currentSearchLocation = decompilerPanel.getSearchResults();
|
||||
if (currentSearchLocation == null) {
|
||||
return startLocation; // nothing to do; no prior search hit
|
||||
}
|
||||
@ -139,4 +142,229 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||
|
||||
return startLocation;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Search Methods
|
||||
//=================================================================================================
|
||||
|
||||
@Override
|
||||
public List<SearchLocation> searchAll(String searchString, boolean isRegex) {
|
||||
|
||||
Pattern pattern = createPattern(searchString, isRegex);
|
||||
Function<String, SearchMatch> function = createForwardMatchFunction(pattern);
|
||||
FieldLocation start = new FieldLocation();
|
||||
|
||||
List<SearchLocation> results = new ArrayList<>();
|
||||
DecompilerSearchLocation searchLocation = findNext(function, searchString, start);
|
||||
while (searchLocation != null) {
|
||||
results.add(searchLocation);
|
||||
|
||||
FieldLocation last = searchLocation.getFieldLocation();
|
||||
|
||||
int line = last.getIndex().intValue();
|
||||
int field = 0; // there is only 1 field
|
||||
int row = 0; // there is only 1 row
|
||||
int col = last.getCol() + 1; // move over one char to handle sub-matches
|
||||
start = new FieldLocation(line, field, row, col);
|
||||
searchLocation = findNext(function, searchString, start);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private DecompilerSearchLocation doFind(String searchString, FieldLocation currentLocation,
|
||||
boolean forwardSearch, boolean isRegex) {
|
||||
|
||||
Pattern pattern = createPattern(searchString, isRegex);
|
||||
|
||||
if (forwardSearch) {
|
||||
Function<String, SearchMatch> function = createForwardMatchFunction(pattern);
|
||||
return findNext(function, searchString, currentLocation);
|
||||
}
|
||||
|
||||
Function<String, SearchMatch> reverse = createReverseMatchFunction(pattern);
|
||||
return findPrevious(reverse, searchString, currentLocation);
|
||||
}
|
||||
|
||||
private Pattern createPattern(String searchString, boolean isRegex) {
|
||||
|
||||
int options = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
|
||||
if (isRegex) {
|
||||
try {
|
||||
return Pattern.compile(searchString, options);
|
||||
}
|
||||
catch (PatternSyntaxException e) {
|
||||
Msg.showError(this, decompilerPanel, "Regular Expression Syntax Error",
|
||||
e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return UserSearchUtils.createPattern(searchString, false, options);
|
||||
}
|
||||
|
||||
private Function<String, SearchMatch> createForwardMatchFunction(Pattern pattern) {
|
||||
|
||||
return textLine -> {
|
||||
|
||||
Matcher matcher = pattern.matcher(textLine);
|
||||
if (matcher.find()) {
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
return new SearchMatch(start, end, textLine);
|
||||
}
|
||||
|
||||
return SearchMatch.NO_MATCH;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private Function<String, SearchMatch> createReverseMatchFunction(Pattern pattern) {
|
||||
|
||||
return textLine -> {
|
||||
|
||||
Matcher matcher = pattern.matcher(textLine);
|
||||
if (!matcher.find()) {
|
||||
return SearchMatch.NO_MATCH;
|
||||
}
|
||||
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
|
||||
// Since the matcher can only match from the start to end of line, we need to find all
|
||||
// matches and then take the last match
|
||||
|
||||
// Setting the region to one character past the previous match allows repeated matches
|
||||
// within a match. The default behavior of the matcher is to start the match after
|
||||
// the previous match found by find().
|
||||
matcher.region(start + 1, textLine.length());
|
||||
while (matcher.find()) {
|
||||
start = matcher.start();
|
||||
end = matcher.end();
|
||||
matcher.region(start + 1, textLine.length());
|
||||
}
|
||||
|
||||
return new SearchMatch(start, end, textLine);
|
||||
};
|
||||
}
|
||||
|
||||
private DecompilerSearchLocation findNext(Function<String, SearchMatch> matcher,
|
||||
String searchString, FieldLocation currentLocation) {
|
||||
|
||||
List<Field> fields = decompilerPanel.getFields();
|
||||
int line = currentLocation.getIndex().intValue();
|
||||
for (int i = line; i < fields.size(); i++) {
|
||||
ClangTextField field = (ClangTextField) fields.get(i);
|
||||
String partialLine = substring(field, (i == line) ? currentLocation : null, true);
|
||||
SearchMatch match = matcher.apply(partialLine);
|
||||
if (match == SearchMatch.NO_MATCH) {
|
||||
continue;
|
||||
}
|
||||
if (i == line) { // cursor is on this line
|
||||
//
|
||||
// The match start for all lines without the cursor will be relative to the start
|
||||
// of the line, which is 0. However, when searching on the row with the cursor,
|
||||
// the match start is relative to the cursor position. Update the start to
|
||||
// compensate for the difference between the start of the line and the cursor.
|
||||
//
|
||||
String fullLine = field.getText();
|
||||
int cursorOffset = fullLine.length() - partialLine.length();
|
||||
match.start += cursorOffset;
|
||||
match.end += cursorOffset;
|
||||
}
|
||||
|
||||
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation =
|
||||
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
|
||||
|
||||
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||
searchString, true, field.getText());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private DecompilerSearchLocation findPrevious(Function<String, SearchMatch> matcher,
|
||||
String searchString, FieldLocation currentLocation) {
|
||||
|
||||
List<Field> fields = decompilerPanel.getFields();
|
||||
int line = currentLocation.getIndex().intValue();
|
||||
for (int i = line; i >= 0; i--) {
|
||||
ClangTextField field = (ClangTextField) fields.get(i);
|
||||
String textLine = substring(field, (i == line) ? currentLocation : null, false);
|
||||
|
||||
SearchMatch match = matcher.apply(textLine);
|
||||
if (match != SearchMatch.NO_MATCH) {
|
||||
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
|
||||
FieldLocation fieldLocation =
|
||||
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
|
||||
|
||||
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||
searchString, false, field.getText());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String substring(ClangTextField textField, FieldLocation location,
|
||||
boolean forwardSearch) {
|
||||
|
||||
if (location == null) { // the cursor location is not on this line; use all of the text
|
||||
return textField.getText();
|
||||
}
|
||||
|
||||
if (textField.getText().isEmpty()) { // the cursor is on blank line
|
||||
return "";
|
||||
}
|
||||
|
||||
String partialText = textField.getText();
|
||||
|
||||
if (forwardSearch) {
|
||||
|
||||
int nextCol = location.getCol();
|
||||
|
||||
// protects against the location column being out of range (this can happen if we're
|
||||
// searching forward and the cursor is past the last token)
|
||||
if (nextCol >= partialText.length()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// skip a character to start the next search; this prevents matching the previous match
|
||||
return partialText.substring(nextCol);
|
||||
}
|
||||
|
||||
// backwards search
|
||||
return partialText.substring(0, location.getCol());
|
||||
}
|
||||
|
||||
private FieldLineLocation getFieldIndexFromOffset(int screenOffset, ClangTextField textField) {
|
||||
|
||||
RowColLocation rowColLocation = textField.textOffsetToScreenLocation(screenOffset);
|
||||
|
||||
// we use 0 here because currently there is only one field, which is the entire line
|
||||
return new FieldLineLocation(0, rowColLocation.col());
|
||||
}
|
||||
|
||||
private static class SearchMatch {
|
||||
private static SearchMatch NO_MATCH = new SearchMatch(-1, -1, null);
|
||||
private int start;
|
||||
private int end;
|
||||
private String textLine;
|
||||
|
||||
SearchMatch(int start, int end, String textLine) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.textLine = textLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == NO_MATCH) {
|
||||
return "NO MATCH";
|
||||
}
|
||||
return "[start=" + start + ",end=" + end + "]: " + textLine;
|
||||
}
|
||||
}
|
||||
|
||||
private record FieldLineLocation(int fieldNumber, int column) {
|
||||
}
|
||||
}
|
||||
|
@ -23,13 +23,14 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import docking.action.KeyBindingData;
|
||||
import docking.action.MenuData;
|
||||
import docking.widgets.FindDialog;
|
||||
import ghidra.app.decompiler.component.DecompilerFindDialog;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
public class FindAction extends AbstractDecompilerAction {
|
||||
private FindDialog findDialog;
|
||||
private DecompilerFindDialog findDialog;
|
||||
|
||||
public FindAction() {
|
||||
super("Find");
|
||||
@ -49,15 +50,7 @@ public class FindAction extends AbstractDecompilerAction {
|
||||
|
||||
protected FindDialog getFindDialog(DecompilerPanel decompilerPanel) {
|
||||
if (findDialog == null) {
|
||||
findDialog =
|
||||
new FindDialog("Decompiler Find Text", new DecompilerSearcher(decompilerPanel)) {
|
||||
@Override
|
||||
protected void dialogClosed() {
|
||||
// clear the search results when the dialog is closed
|
||||
decompilerPanel.setSearchResults(null);
|
||||
}
|
||||
};
|
||||
findDialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
||||
findDialog = new DecompilerFindDialog(decompilerPanel);
|
||||
}
|
||||
return findDialog;
|
||||
}
|
||||
|
@ -17,21 +17,28 @@ package ghidra.app.plugin.core.decompile;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.widgets.FindDialog;
|
||||
import docking.widgets.dialogs.InputDialog;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import docking.widgets.table.GTable;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.component.DecompilerFindDialog;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.app.plugin.core.decompile.actions.FieldBasedSearchLocation;
|
||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.ClassicSampleX86ProgramBuilder;
|
||||
import ghidra.util.table.GhidraProgramTableModel;
|
||||
import ghidra.util.table.GhidraThreadedTablePanel;
|
||||
|
||||
public class DecompilerFindDialogTest extends AbstractDecompilerTest {
|
||||
|
||||
private FindDialog findDialog;
|
||||
private DecompilerFindDialog findDialog;
|
||||
|
||||
@Override
|
||||
@After
|
||||
@ -243,10 +250,78 @@ public class DecompilerFindDialogTest extends AbstractDecompilerTest {
|
||||
assertSearchHit(line, column, length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchAll() {
|
||||
|
||||
/*
|
||||
|
||||
bool FUN_01002239(int param_1)
|
||||
|
||||
{
|
||||
undefined4 uVar1;
|
||||
int iVar2;
|
||||
undefined4 *puVar3;
|
||||
bool bVar4;
|
||||
undefined *puVar5;
|
||||
undefined2 local_210;
|
||||
undefined4 local_20e [129];
|
||||
int local_8;
|
||||
|
||||
local_210 = 0;
|
||||
puVar3 = local_20e;
|
||||
...
|
||||
...
|
||||
...
|
||||
*/
|
||||
|
||||
decompile("1002239");
|
||||
|
||||
String text = "puVar";
|
||||
showFind(text);
|
||||
|
||||
searchAll();
|
||||
|
||||
GTable table = getResultsTable();
|
||||
List<DecompilerSearchLocation> results = getResults(table);
|
||||
assertEquals(10, results.size());
|
||||
|
||||
// click some rows and verify the cursor location
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
clickAndVerify(i, table, results);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private void clickAndVerify(int row, GTable table, List<DecompilerSearchLocation> results) {
|
||||
|
||||
runSwing(() -> table.selectRow(row));
|
||||
DecompilerSearchLocation searchLocation = results.get(row);
|
||||
FieldLocation fieldLocation = searchLocation.getFieldLocation();
|
||||
ClangToken expectedToken = getToken(fieldLocation);
|
||||
ClangToken cursorToken = getToken();
|
||||
assertEquals(expectedToken, cursorToken);
|
||||
}
|
||||
|
||||
private GTable getResultsTable() {
|
||||
@SuppressWarnings("unchecked")
|
||||
TableComponentProvider<DecompilerSearchLocation> tableProvider =
|
||||
waitForComponentProvider(TableComponentProvider.class);
|
||||
GhidraThreadedTablePanel<DecompilerSearchLocation> panel =
|
||||
tableProvider.getThreadedTablePanel();
|
||||
return panel.getTable();
|
||||
}
|
||||
|
||||
private List<DecompilerSearchLocation> getResults(GTable table) {
|
||||
@SuppressWarnings("unchecked")
|
||||
GhidraProgramTableModel<DecompilerSearchLocation> model =
|
||||
(GhidraProgramTableModel<DecompilerSearchLocation>) table.getModel();
|
||||
waitForTableModel(model);
|
||||
return model.getModelData();
|
||||
}
|
||||
|
||||
private void next() {
|
||||
runSwing(() -> findDialog.next());
|
||||
}
|
||||
@ -255,13 +330,17 @@ public class DecompilerFindDialogTest extends AbstractDecompilerTest {
|
||||
runSwing(() -> findDialog.previous());
|
||||
}
|
||||
|
||||
private void searchAll() {
|
||||
pressButtonByText(findDialog, "Search All");
|
||||
}
|
||||
|
||||
private void assertSearchHit(int line, int column, int length) {
|
||||
|
||||
waitForSwing();
|
||||
assertCurrentLocation(line, column);
|
||||
|
||||
DecompilerPanel panel = getDecompilerPanel();
|
||||
FieldBasedSearchLocation searchResults = panel.getSearchResults();
|
||||
DecompilerSearchLocation searchResults = panel.getSearchResults();
|
||||
FieldLocation searchCursorLocation = searchResults.getFieldLocation();
|
||||
int searchLineNumber = searchCursorLocation.getIndex().intValue() + 1;
|
||||
assertEquals("Search result is on the wrong line", line, searchLineNumber);
|
||||
@ -285,7 +364,7 @@ public class DecompilerFindDialogTest extends AbstractDecompilerTest {
|
||||
private void showFind(String text) {
|
||||
DockingActionIf findAction = getAction(decompiler, "Find");
|
||||
performAction(findAction, provider, true);
|
||||
findDialog = waitForDialogComponent(FindDialog.class);
|
||||
findDialog = waitForDialogComponent(DecompilerFindDialog.class);
|
||||
runSwing(() -> findDialog.setSearchText(text));
|
||||
}
|
||||
|
||||
|
@ -216,7 +216,7 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel<IconValue, Obj
|
||||
private class ThemeIconRenderer extends AbstractGColumnRenderer<ResolvedIcon> {
|
||||
|
||||
public ThemeIconRenderer() {
|
||||
setFont(Gui.getFont("font.monospaced"));
|
||||
setBaseFontId("font.monospaced");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -32,7 +32,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||
|
||||
private GhidraComboBox<String> comboBox;
|
||||
|
||||
private FindDialogSearcher searcher;
|
||||
protected FindDialogSearcher searcher;
|
||||
private JButton nextButton;
|
||||
private JButton previousButton;
|
||||
private JRadioButton stringRadioButton;
|
||||
@ -140,6 +140,10 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||
doSearch(false);
|
||||
}
|
||||
|
||||
protected boolean useRegex() {
|
||||
return regexRadioButton.isSelected();
|
||||
}
|
||||
|
||||
private void doSearch(boolean forward) {
|
||||
|
||||
if (!nextButton.isEnabled()) {
|
||||
@ -190,7 +194,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||
notifyUser("Not found");
|
||||
}
|
||||
|
||||
private void notifySearchHit(SearchLocation location) {
|
||||
protected void notifySearchHit(SearchLocation location) {
|
||||
searcher.setCursorPosition(location.getCursorPosition());
|
||||
storeSearchText(location.getSearchText());
|
||||
searcher.highlightSearchResults(location);
|
||||
@ -234,7 +238,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||
history.forEach(comboBox::addToModel);
|
||||
}
|
||||
|
||||
private void storeSearchText(String text) {
|
||||
protected void storeSearchText(String text) {
|
||||
|
||||
MutableComboBoxModel<String> model = (MutableComboBoxModel<String>) comboBox.getModel();
|
||||
model.insertElementAt(text, 0);
|
||||
|
@ -15,21 +15,72 @@
|
||||
*/
|
||||
package docking.widgets;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
/**
|
||||
* A simple interface for the {@link FindDialog} so that it can work for different search clients.
|
||||
* <p>
|
||||
* The {@link CursorPosition} object used by this interface is one that implementations can extend
|
||||
* to add extra context to use when searching. The implementation is responsible for creating the
|
||||
* locations and these locations will later be handed back to the searcher.
|
||||
*/
|
||||
public interface FindDialogSearcher {
|
||||
|
||||
/**
|
||||
* The current cursor position. Used to search for the next item.
|
||||
* @return the cursor position.
|
||||
*/
|
||||
public CursorPosition getCursorPosition();
|
||||
|
||||
/**
|
||||
* Sets the cursor position after a successful search.
|
||||
* @param position the cursor position.
|
||||
*/
|
||||
public void setCursorPosition(CursorPosition position);
|
||||
|
||||
/**
|
||||
* Returns the start cursor position. This is used when a search is wrapped to start at the
|
||||
* beginning of the search range.
|
||||
* @return the start position.
|
||||
*/
|
||||
public CursorPosition getStart();
|
||||
|
||||
/**
|
||||
* The end cursor position. This is used when a search is wrapped while searching backwards to
|
||||
* start at the end position.
|
||||
* @return the end position.
|
||||
*/
|
||||
public CursorPosition getEnd();
|
||||
|
||||
/**
|
||||
* Called to signal the implementor should highlight the given search location.
|
||||
* @param location the search result location.
|
||||
*/
|
||||
public void highlightSearchResults(SearchLocation location);
|
||||
|
||||
/**
|
||||
* Perform a search for the next item in the given direction starting at the given cursor
|
||||
* position.
|
||||
* @param text the search text.
|
||||
* @param cursorPosition the current cursor position.
|
||||
* @param searchForward true if searching forward.
|
||||
* @param useRegex useRegex true if the search text is a regular expression; false if the texts is
|
||||
* literal text.
|
||||
* @return the search result or null if no match was found.
|
||||
*/
|
||||
public SearchLocation search(String text, CursorPosition cursorPosition, boolean searchForward,
|
||||
boolean useRegex);
|
||||
|
||||
/**
|
||||
* Search for all matches.
|
||||
* @param text the search text.
|
||||
* @param useRegex true if the search text is a regular expression; false if the texts is
|
||||
* literal text.
|
||||
* @return all search results or an empty list.
|
||||
*/
|
||||
public default List<SearchLocation> searchAll(String text, boolean useRegex) {
|
||||
throw new UnsupportedOperationException("Search All is not defined for this searcher");
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import javax.swing.JButton;
|
||||
import resources.ResourceManager;
|
||||
|
||||
/**
|
||||
* A drop-in replacement for {@link JButton} that correctly installs a disable icon.
|
||||
* A drop-in replacement for {@link JButton} that correctly installs a disabled icon.
|
||||
*/
|
||||
public class GButton extends JButton {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user