mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
Merge remote-tracking branch
'origin/GP-3858-dragonmacher-console-find--SQUASHED' (Closes #2567, #7136)
This commit is contained in:
commit
4db0ccc8ec
24 changed files with 1525 additions and 505 deletions
|
@ -24,7 +24,7 @@ color.fg.dialog.equates.equate = color.palette.blue
|
||||||
color.fg.dialog.equates.suggestion = color.palette.hint
|
color.fg.dialog.equates.suggestion = color.palette.hint
|
||||||
|
|
||||||
color.fg.consoletextpane = color.fg
|
color.fg.consoletextpane = color.fg
|
||||||
color.fg.error.consoletextpane = color.fg.error
|
color.fg.consoletextpane.error = color.fg.error
|
||||||
|
|
||||||
color.fg.infopanel.version = color.fg
|
color.fg.infopanel.version = color.fg
|
||||||
|
|
||||||
|
@ -34,8 +34,9 @@ color.fg.interpreterconsole.error = color.fg.error
|
||||||
|
|
||||||
color.bg.markerservice = color.bg
|
color.bg.markerservice = color.bg
|
||||||
|
|
||||||
color.bg.search.highlight = color.bg.highlight
|
color.bg.search.highlight = color.bg.find.highlight
|
||||||
color.bg.search.highlight.current.line = color.palette.yellow
|
color.bg.search.highlight.current.line = color.bg.find.highlight.active
|
||||||
|
|
||||||
|
|
||||||
color.fg.analysis.options.prototype = color.palette.crimson
|
color.fg.analysis.options.prototype = color.palette.crimson
|
||||||
color.fg.analysis.options.prototype.selected = color.palette.lightcoral
|
color.fg.analysis.options.prototype.selected = color.palette.lightcoral
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -20,11 +20,13 @@ import java.awt.event.*;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.text.BadLocationException;
|
import javax.swing.text.*;
|
||||||
import javax.swing.text.Document;
|
|
||||||
|
|
||||||
import docking.*;
|
import docking.*;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
|
import docking.action.builder.ActionBuilder;
|
||||||
|
import docking.widgets.FindDialog;
|
||||||
|
import docking.widgets.TextComponentSearcher;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import generic.theme.Gui;
|
import generic.theme.Gui;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
|
@ -53,13 +55,19 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
private ConsoleTextPane textPane;
|
private ConsoleTextPane textPane;
|
||||||
private JScrollPane scroller;
|
private JScrollPane scroller;
|
||||||
private JComponent component;
|
private JComponent component;
|
||||||
private boolean scrollLock = false;
|
|
||||||
private DockingAction clearAction;
|
|
||||||
private ToggleDockingAction scrollAction;
|
|
||||||
private Address currentAddress;
|
|
||||||
private PrintWriter stderr;
|
private PrintWriter stderr;
|
||||||
private PrintWriter stdin;
|
private PrintWriter stdin;
|
||||||
|
private boolean scrollLock = false;
|
||||||
|
|
||||||
|
private DockingAction clearAction;
|
||||||
|
private ToggleDockingAction scrollAction;
|
||||||
|
|
||||||
private Program currentProgram;
|
private Program currentProgram;
|
||||||
|
private Address currentAddress;
|
||||||
|
|
||||||
|
private FindDialog findDialog;
|
||||||
|
private TextComponentSearcher searcher;
|
||||||
|
|
||||||
public ConsoleComponentProvider(PluginTool tool, String owner) {
|
public ConsoleComponentProvider(PluginTool tool, String owner) {
|
||||||
super(tool, "Console", owner);
|
super(tool, "Console", owner);
|
||||||
|
@ -97,6 +105,10 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
textPane.dispose();
|
textPane.dispose();
|
||||||
stderr.close();
|
stderr.close();
|
||||||
stdin.close();
|
stdin.close();
|
||||||
|
|
||||||
|
if (findDialog != null) {
|
||||||
|
findDialog.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createOptions() {
|
private void createOptions() {
|
||||||
|
@ -117,70 +129,8 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
textPane.setName(textPaneName);
|
textPane.setName(textPaneName);
|
||||||
textPane.getAccessibleContext().setAccessibleName(textPaneName);
|
textPane.getAccessibleContext().setAccessibleName(textPaneName);
|
||||||
|
|
||||||
textPane.addMouseMotionListener(new MouseMotionAdapter() {
|
textPane.addMouseMotionListener(new CursorUpdateMouseMotionListener());
|
||||||
@Override
|
textPane.addMouseListener(new GoToMouseListener());
|
||||||
public void mouseMoved(MouseEvent e) {
|
|
||||||
if (currentProgram == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point hoverPoint = e.getPoint();
|
|
||||||
ConsoleWord word = getWordSeparatedByWhitespace(hoverPoint);
|
|
||||||
if (word == null) {
|
|
||||||
textPane.setCursor(Cursor.getDefaultCursor());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Address addr = currentProgram.getAddressFactory().getAddress(word.word);
|
|
||||||
if (addr != null || isSymbol(word.word)) {
|
|
||||||
textPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConsoleWord trimmedWord = word.getWordWithoutSpecialCharacters();
|
|
||||||
addr = currentProgram.getAddressFactory().getAddress(trimmedWord.word);
|
|
||||||
if (addr != null || isSymbol(trimmedWord.word)) {
|
|
||||||
textPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
textPane.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mousePressed(MouseEvent e) {
|
|
||||||
if (e.getClickCount() != 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (currentProgram == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GoToService gotoService = tool.getService(GoToService.class);
|
|
||||||
if (gotoService == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point clickPoint = e.getPoint();
|
|
||||||
ConsoleWord word = getWordSeparatedByWhitespace(clickPoint);
|
|
||||||
if (word == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Address addr = currentProgram.getAddressFactory().getAddress(word.word);
|
|
||||||
if (addr != null || isSymbol(word.word)) {
|
|
||||||
goTo(word);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConsoleWord trimmedWord = word.getWordWithoutSpecialCharacters();
|
|
||||||
addr = currentProgram.getAddressFactory().getAddress(trimmedWord.word);
|
|
||||||
if (addr == null && !isSymbol(trimmedWord.word)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
goTo(trimmedWord);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
scroller = new JScrollPane(textPane);
|
scroller = new JScrollPane(textPane);
|
||||||
scroller.setPreferredSize(new Dimension(200, 100));
|
scroller.setPreferredSize(new Dimension(200, 100));
|
||||||
|
@ -191,74 +141,12 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
tool.addComponentProvider(this, true);
|
tool.addComponentProvider(this, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void goTo(ConsoleWord word) {
|
|
||||||
|
|
||||||
GoToService gotoService = tool.getService(GoToService.class);
|
|
||||||
if (gotoService == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: must be case sensitive otherwise the service will report that it has
|
|
||||||
// processed the request even if there are no matches
|
|
||||||
boolean found =
|
|
||||||
gotoService.goToQuery(currentAddress, new QueryData(word.word, true), null, null);
|
|
||||||
if (found) {
|
|
||||||
select(word);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConsoleWord trimmedWord = word.getWordWithoutSpecialCharacters();
|
|
||||||
found = gotoService.goToQuery(currentAddress, new QueryData(trimmedWord.word, true), null,
|
|
||||||
null);
|
|
||||||
if (found) {
|
|
||||||
select(trimmedWord);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConsoleWord getWordSeparatedByWhitespace(Point p) {
|
|
||||||
int pos = textPane.viewToModel2D(p);
|
|
||||||
Document doc = textPane.getDocument();
|
|
||||||
int startIndex = pos;
|
|
||||||
int endIndex = pos;
|
|
||||||
try {
|
|
||||||
for (; startIndex > 0; --startIndex) {
|
|
||||||
char c = doc.getText(startIndex, 1).charAt(0);
|
|
||||||
if (Character.isWhitespace(c)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (; endIndex < doc.getLength() - 1; ++endIndex) {
|
|
||||||
char c = doc.getText(endIndex, 1).charAt(0);
|
|
||||||
if (Character.isWhitespace(c)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String text = doc.getText(startIndex + 1, endIndex - startIndex);
|
|
||||||
if (text == null || text.trim().length() == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new ConsoleWord(text.trim(), startIndex + 1, endIndex);
|
|
||||||
}
|
|
||||||
catch (BadLocationException ble) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSymbol(String word) {
|
private boolean isSymbol(String word) {
|
||||||
SymbolTable symbolTable = currentProgram.getSymbolTable();
|
SymbolTable symbolTable = currentProgram.getSymbolTable();
|
||||||
SymbolIterator symbolIterator = symbolTable.getSymbols(word);
|
SymbolIterator symbolIterator = symbolTable.getSymbols(word);
|
||||||
return symbolIterator.hasNext();
|
return symbolIterator.hasNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void select(ConsoleWord word) {
|
|
||||||
try {
|
|
||||||
textPane.select(word.startPosition, word.endPosition);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
// we are too lazy to verify our data before calling select--bleh
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createActions() {
|
private void createActions() {
|
||||||
clearAction = new DockingAction("Clear Console", getOwner()) {
|
clearAction = new DockingAction("Clear Console", getOwner()) {
|
||||||
|
|
||||||
|
@ -268,9 +156,7 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
clearAction.setDescription("Clear Console");
|
clearAction.setDescription("Clear Console");
|
||||||
// ACTIONS - auto generated
|
|
||||||
clearAction.setToolBarData(new ToolBarData(new GIcon("icon.plugin.console.clear"), null));
|
clearAction.setToolBarData(new ToolBarData(new GIcon("icon.plugin.console.clear"), null));
|
||||||
|
|
||||||
clearAction.setEnabled(true);
|
clearAction.setEnabled(true);
|
||||||
|
|
||||||
scrollAction = new ToggleDockingAction("Scroll Lock", getOwner()) {
|
scrollAction = new ToggleDockingAction("Scroll Lock", getOwner()) {
|
||||||
|
@ -282,14 +168,33 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
scrollAction.setDescription("Scroll Lock");
|
scrollAction.setDescription("Scroll Lock");
|
||||||
scrollAction.setToolBarData(
|
scrollAction.setToolBarData(
|
||||||
new ToolBarData(new GIcon("icon.plugin.console.scroll.lock"), null));
|
new ToolBarData(new GIcon("icon.plugin.console.scroll.lock"), null));
|
||||||
|
|
||||||
scrollAction.setEnabled(true);
|
scrollAction.setEnabled(true);
|
||||||
scrollAction.setSelected(scrollLock);
|
scrollAction.setSelected(scrollLock);
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
new ActionBuilder("Find", getOwner())
|
||||||
|
.keyBinding("Ctrl F")
|
||||||
|
.sharedKeyBinding()
|
||||||
|
.popupMenuPath("Find...")
|
||||||
|
.onAction(c -> {
|
||||||
|
showFindDialog();
|
||||||
|
})
|
||||||
|
.buildAndInstallLocal(this)
|
||||||
|
;
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
addLocalAction(scrollAction);
|
addLocalAction(scrollAction);
|
||||||
addLocalAction(clearAction);
|
addLocalAction(clearAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showFindDialog() {
|
||||||
|
if (findDialog == null) {
|
||||||
|
searcher = new TextComponentSearcher(textPane);
|
||||||
|
findDialog = new FindDialog("Find", searcher);
|
||||||
|
}
|
||||||
|
getTool().showDialog(findDialog);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMessage(String originator, String message) {
|
public void addMessage(String originator, String message) {
|
||||||
checkVisible();
|
checkVisible();
|
||||||
|
@ -304,26 +209,6 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addException(String originator, Exception e) {
|
public void addException(String originator, Exception e) {
|
||||||
try {
|
|
||||||
e.printStackTrace(stderr);
|
|
||||||
}
|
|
||||||
catch (Exception e1) {
|
|
||||||
//
|
|
||||||
// sometimes an exception will occur while printing
|
|
||||||
// the stack trace on an exception.
|
|
||||||
// if that happens catch it and manually print
|
|
||||||
// some information about it.
|
|
||||||
// see org.jruby.exceptions.RaiseException
|
|
||||||
//
|
|
||||||
stderr.println("Unexpected Exception: " + e.getMessage());
|
|
||||||
for (StackTraceElement stackTraceElement : e.getStackTrace()) {
|
|
||||||
stderr.println("\t" + stackTraceElement.toString());
|
|
||||||
}
|
|
||||||
stderr.println("Unexpected Exception: " + e1.getMessage());
|
|
||||||
for (StackTraceElement stackTraceElement : e1.getStackTrace()) {
|
|
||||||
stderr.println("\t" + stackTraceElement.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,6 +216,10 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
public void clearMessages() {
|
public void clearMessages() {
|
||||||
checkVisible();
|
checkVisible();
|
||||||
textPane.setText("");
|
textPane.setText("");
|
||||||
|
|
||||||
|
if (searcher != null) {
|
||||||
|
searcher.clearHighlights();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -383,22 +272,21 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
return textPane.getDocument().getLength();
|
return textPane.getDocument().getLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private void checkVisible() {
|
private void checkVisible() {
|
||||||
if (!isVisible()) {
|
if (!isVisible()) {
|
||||||
tool.showComponentProvider(this, true);
|
tool.showComponentProvider(this, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see docking.ComponentProvider#getComponent()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public JComponent getComponent() {
|
public JComponent getComponent() {
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConsoleTextPane getTextPane() {
|
||||||
|
return textPane;
|
||||||
|
}
|
||||||
|
|
||||||
public void setCurrentProgram(Program program) {
|
public void setCurrentProgram(Program program) {
|
||||||
currentProgram = program;
|
currentProgram = program;
|
||||||
}
|
}
|
||||||
|
@ -407,4 +295,136 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
currentAddress = address;
|
currentAddress = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ConsoleWord getWordSeparatedByWhitespace(JTextComponent textComponent, Point p) {
|
||||||
|
int pos = textComponent.viewToModel2D(p);
|
||||||
|
Document doc = textComponent.getDocument();
|
||||||
|
int startIndex = pos;
|
||||||
|
int endIndex = pos;
|
||||||
|
try {
|
||||||
|
for (; startIndex > 0; --startIndex) {
|
||||||
|
char c = doc.getText(startIndex, 1).charAt(0);
|
||||||
|
if (Character.isWhitespace(c)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; endIndex < doc.getLength() - 1; ++endIndex) {
|
||||||
|
char c = doc.getText(endIndex, 1).charAt(0);
|
||||||
|
if (Character.isWhitespace(c)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String text = doc.getText(startIndex + 1, endIndex - startIndex);
|
||||||
|
if (text == null || text.trim().length() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new ConsoleWord(text.trim(), startIndex + 1, endIndex);
|
||||||
|
}
|
||||||
|
catch (BadLocationException ble) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//=================================================================================================
|
||||||
|
// Inner Classes
|
||||||
|
//=================================================================================================
|
||||||
|
|
||||||
|
private class GoToMouseListener extends MouseAdapter {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (e.getClickCount() != 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentProgram == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GoToService gotoService = tool.getService(GoToService.class);
|
||||||
|
if (gotoService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point clickPoint = e.getPoint();
|
||||||
|
ConsoleWord word = getWordSeparatedByWhitespace(textPane, clickPoint);
|
||||||
|
if (word == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Address addr = currentProgram.getAddressFactory().getAddress(word.word);
|
||||||
|
if (addr != null || isSymbol(word.word)) {
|
||||||
|
goTo(word);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleWord trimmedWord = word.getWordWithoutSpecialCharacters();
|
||||||
|
addr = currentProgram.getAddressFactory().getAddress(trimmedWord.word);
|
||||||
|
if (addr == null && !isSymbol(trimmedWord.word)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
goTo(trimmedWord);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void goTo(ConsoleWord word) {
|
||||||
|
|
||||||
|
GoToService gotoService = tool.getService(GoToService.class);
|
||||||
|
if (gotoService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: must be case sensitive otherwise the service will report that it has
|
||||||
|
// processed the request even if there are no matches
|
||||||
|
boolean found =
|
||||||
|
gotoService.goToQuery(currentAddress, new QueryData(word.word, true), null, null);
|
||||||
|
if (found) {
|
||||||
|
select(word);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleWord trimmedWord = word.getWordWithoutSpecialCharacters();
|
||||||
|
found =
|
||||||
|
gotoService.goToQuery(currentAddress, new QueryData(trimmedWord.word, true), null,
|
||||||
|
null);
|
||||||
|
if (found) {
|
||||||
|
select(trimmedWord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void select(ConsoleWord word) {
|
||||||
|
try {
|
||||||
|
textPane.select(word.startPosition, word.endPosition);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// we are too lazy to verify our data before calling select--bleh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CursorUpdateMouseMotionListener extends MouseMotionAdapter {
|
||||||
|
@Override
|
||||||
|
public void mouseMoved(MouseEvent e) {
|
||||||
|
if (currentProgram == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point hoverPoint = e.getPoint();
|
||||||
|
ConsoleWord word = getWordSeparatedByWhitespace(textPane, hoverPoint);
|
||||||
|
if (word == null) {
|
||||||
|
textPane.setCursor(Cursor.getDefaultCursor());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Address addr = currentProgram.getAddressFactory().getAddress(word.word);
|
||||||
|
if (addr != null || isSymbol(word.word)) {
|
||||||
|
textPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleWord trimmedWord = word.getWordWithoutSpecialCharacters();
|
||||||
|
addr = currentProgram.getAddressFactory().getAddress(trimmedWord.word);
|
||||||
|
if (addr != null || isSymbol(trimmedWord.word)) {
|
||||||
|
textPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -45,7 +45,6 @@ class FlowArrowPanel extends JPanel {
|
||||||
private Point pendingMouseClickPoint;
|
private Point pendingMouseClickPoint;
|
||||||
|
|
||||||
FlowArrowPanel(FlowArrowPlugin p) {
|
FlowArrowPanel(FlowArrowPlugin p) {
|
||||||
super();
|
|
||||||
this.plugin = p;
|
this.plugin = p;
|
||||||
setMinimumSize(new Dimension(0, 0));
|
setMinimumSize(new Dimension(0, 0));
|
||||||
setPreferredSize(new Dimension(32, 1));
|
setPreferredSize(new Dimension(32, 1));
|
||||||
|
@ -164,7 +163,6 @@ class FlowArrowPanel extends JPanel {
|
||||||
ScrollingCallback callback = new ScrollingCallback(start, end);
|
ScrollingCallback callback = new ScrollingCallback(start, end);
|
||||||
Animator animator = AnimationUtils.executeSwingAnimationCallback(callback);
|
Animator animator = AnimationUtils.executeSwingAnimationCallback(callback);
|
||||||
callback.setAnimator(animator);
|
callback.setAnimator(animator);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processSingleClick(Point point) {
|
private void processSingleClick(Point point) {
|
||||||
|
@ -322,7 +320,9 @@ class FlowArrowPanel extends JPanel {
|
||||||
|
|
||||||
if (current.equals(end)) {
|
if (current.equals(end)) {
|
||||||
// we are done!
|
// we are done!
|
||||||
animator.stop();
|
if (animator != null) {
|
||||||
|
animator.stop();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@ import java.io.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.*;
|
||||||
import javax.swing.JComponent;
|
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
import docking.action.ToolBarData;
|
import docking.action.ToolBarData;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.action.builder.ActionBuilder;
|
||||||
|
import docking.widgets.*;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
@ -39,6 +39,9 @@ public class InterpreterComponentProvider extends ComponentProviderAdapter
|
||||||
private InterpreterConnection interpreter;
|
private InterpreterConnection interpreter;
|
||||||
private List<Callback> firstActivationCallbacks;
|
private List<Callback> firstActivationCallbacks;
|
||||||
|
|
||||||
|
private FindDialog findDialog;
|
||||||
|
private TextComponentSearcher searcher;
|
||||||
|
|
||||||
public InterpreterComponentProvider(InterpreterPanelPlugin plugin,
|
public InterpreterComponentProvider(InterpreterPanelPlugin plugin,
|
||||||
InterpreterConnection interpreter, boolean visible) {
|
InterpreterConnection interpreter, boolean visible) {
|
||||||
super(plugin.getTool(), interpreter.getTitle(), interpreter.getTitle());
|
super(plugin.getTool(), interpreter.getTitle(), interpreter.getTitle());
|
||||||
|
@ -72,8 +75,28 @@ public class InterpreterComponentProvider extends ComponentProviderAdapter
|
||||||
clearAction.setDescription("Clear Interpreter");
|
clearAction.setDescription("Clear Interpreter");
|
||||||
clearAction.setToolBarData(new ToolBarData(Icons.CLEAR_ICON, null));
|
clearAction.setToolBarData(new ToolBarData(Icons.CLEAR_ICON, null));
|
||||||
clearAction.setEnabled(true);
|
clearAction.setEnabled(true);
|
||||||
|
|
||||||
addLocalAction(clearAction);
|
addLocalAction(clearAction);
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
new ActionBuilder("Find", getOwner())
|
||||||
|
.keyBinding("Ctrl F")
|
||||||
|
.sharedKeyBinding()
|
||||||
|
.popupMenuPath("Find...")
|
||||||
|
.onAction(c -> {
|
||||||
|
showFindDialog();
|
||||||
|
})
|
||||||
|
.buildAndInstallLocal(this)
|
||||||
|
;
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showFindDialog() {
|
||||||
|
if (findDialog == null) {
|
||||||
|
JTextPane textPane = panel.getOutputTextPane();
|
||||||
|
searcher = new TextComponentSearcher(textPane);
|
||||||
|
findDialog = new FindDialog("Find", searcher);
|
||||||
|
}
|
||||||
|
getTool().showDialog(findDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -128,6 +151,10 @@ public class InterpreterComponentProvider extends ComponentProviderAdapter
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
panel.clear();
|
panel.clear();
|
||||||
|
|
||||||
|
if (searcher != null) {
|
||||||
|
searcher.clearHighlights();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -77,7 +77,6 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
private SimpleAttributeSet STDIN_SET;
|
private SimpleAttributeSet STDIN_SET;
|
||||||
|
|
||||||
private CompletionWindowTrigger completionWindowTrigger = CompletionWindowTrigger.TAB;
|
private CompletionWindowTrigger completionWindowTrigger = CompletionWindowTrigger.TAB;
|
||||||
private boolean highlightCompletion = false;
|
|
||||||
private int completionInsertionPosition;
|
private int completionInsertionPosition;
|
||||||
|
|
||||||
private boolean caretGuard = true;
|
private boolean caretGuard = true;
|
||||||
|
@ -298,12 +297,6 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
completionWindowTrigger =
|
completionWindowTrigger =
|
||||||
options.getEnum(COMPLETION_WINDOW_TRIGGER_LABEL, CompletionWindowTrigger.TAB);
|
options.getEnum(COMPLETION_WINDOW_TRIGGER_LABEL, CompletionWindowTrigger.TAB);
|
||||||
|
|
||||||
// TODO
|
|
||||||
// highlightCompletion =
|
|
||||||
// options.getBoolean(HIGHLIGHT_COMPLETION_OPTION_LABEL, DEFAULT_HIGHLIGHT_COMPLETION);
|
|
||||||
// options.setDescription(HIGHLIGHT_COMPLETION_OPTION_LABEL, HIGHLIGHT_COMPLETION_DESCRIPTION);
|
|
||||||
// options.addOptionsChangeListener(this);
|
|
||||||
|
|
||||||
options.addOptionsChangeListener(this);
|
options.addOptionsChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,10 +310,6 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
else if (optionName.equals(COMPLETION_WINDOW_TRIGGER_LABEL)) {
|
else if (optionName.equals(COMPLETION_WINDOW_TRIGGER_LABEL)) {
|
||||||
completionWindowTrigger = (CompletionWindowTrigger) newValue;
|
completionWindowTrigger = (CompletionWindowTrigger) newValue;
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
// else if (optionName.equals(HIGHLIGHT_COMPLETION_OPTION_LABEL)) {
|
|
||||||
// highlightCompletion = ((Boolean) newValue).booleanValue();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -480,6 +469,10 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
stdin.resetStream();
|
stdin.resetStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JTextPane getOutputTextPane() {
|
||||||
|
return outputTextPane;
|
||||||
|
}
|
||||||
|
|
||||||
public String getOutputText() {
|
public String getOutputText() {
|
||||||
return outputTextPane.getText();
|
return outputTextPane.getText();
|
||||||
}
|
}
|
||||||
|
@ -535,16 +528,8 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
text.substring(0, insertedTextStart) + insertion + text.substring(position);
|
text.substring(0, insertedTextStart) + insertion + text.substring(position);
|
||||||
setInputTextPaneText(inputText);
|
setInputTextPaneText(inputText);
|
||||||
|
|
||||||
/* Select what we inserted so that the user can easily
|
/* Then put the caret right after what we inserted. */
|
||||||
* get rid of what they did (in case of a mistake). */
|
inputTextPane.setCaretPosition(insertedTextEnd);
|
||||||
if (highlightCompletion) {
|
|
||||||
inputTextPane.setSelectionStart(insertedTextStart);
|
|
||||||
inputTextPane.moveCaretPosition(insertedTextEnd);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Then put the caret right after what we inserted. */
|
|
||||||
inputTextPane.setCaretPosition(insertedTextEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCompletionList();
|
updateCompletionList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -560,7 +560,7 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// the number of columns determines the default width of the add/edit label dialog
|
// the number of columns determines the default width of the add/edit label dialog
|
||||||
labelNameChoices.setColumnCount(20);
|
labelNameChoices.setColumns(20);
|
||||||
labelNameChoices.setName("label.name.choices");
|
labelNameChoices.setName("label.name.choices");
|
||||||
GhidraComboBox<NamespaceWrapper> comboBox = new GhidraComboBox<>();
|
GhidraComboBox<NamespaceWrapper> comboBox = new GhidraComboBox<>();
|
||||||
comboBox.setEnterKeyForwarding(true);
|
comboBox.setEnterKeyForwarding(true);
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -32,8 +32,6 @@ import ghidra.util.task.SwingUpdateManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic text pane that is used as a console to which text can be written.
|
* A generic text pane that is used as a console to which text can be written.
|
||||||
*
|
|
||||||
* There is not test for this class, but it is indirectly tested by FrontEndGuiTest.
|
|
||||||
*/
|
*/
|
||||||
public class ConsoleTextPane extends JTextPane implements OptionsChangeListener {
|
public class ConsoleTextPane extends JTextPane implements OptionsChangeListener {
|
||||||
|
|
||||||
|
@ -211,7 +209,7 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
|
||||||
outputAttributes = new GAttributes(font, new GColor("color.fg.consoletextpane"));
|
outputAttributes = new GAttributes(font, new GColor("color.fg.consoletextpane"));
|
||||||
outputAttributes.addAttribute(CUSTOM_ATTRIBUTE_KEY, OUTPUT_ATTRIBUTE_VALUE);
|
outputAttributes.addAttribute(CUSTOM_ATTRIBUTE_KEY, OUTPUT_ATTRIBUTE_VALUE);
|
||||||
|
|
||||||
errorAttributes = new GAttributes(font, new GColor("color.fg.error.consoletextpane"));
|
errorAttributes = new GAttributes(font, new GColor("color.fg.consoletextpane.error"));
|
||||||
errorAttributes.addAttribute(CUSTOM_ATTRIBUTE_KEY, ERROR_ATTRIBUTE_VALUE);
|
errorAttributes.addAttribute(CUSTOM_ATTRIBUTE_KEY, ERROR_ATTRIBUTE_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,482 @@
|
||||||
|
/* ###
|
||||||
|
* 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.plugin.core.console;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.text.*;
|
||||||
|
import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter;
|
||||||
|
import javax.swing.text.Highlighter.Highlight;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.Level;
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import docking.action.DockingActionIf;
|
||||||
|
import docking.util.AnimationUtils;
|
||||||
|
import docking.widgets.FindDialog;
|
||||||
|
import docking.widgets.TextComponentSearcher;
|
||||||
|
import generic.jar.ResourceFile;
|
||||||
|
import generic.theme.GColor;
|
||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.framework.Application;
|
||||||
|
import ghidra.framework.main.ConsoleTextPane;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.database.ProgramDB;
|
||||||
|
import ghidra.test.*;
|
||||||
|
|
||||||
|
public class ConsolePluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
|
private TestEnv env;
|
||||||
|
private ProgramDB program;
|
||||||
|
private PluginTool tool;
|
||||||
|
private ConsoleComponentProvider provider;
|
||||||
|
private ConsoleTextPane textPane;
|
||||||
|
private FindDialog findDialog;
|
||||||
|
private ConsolePlugin plugin;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
|
||||||
|
// turn off debug and info log statements that make the console noisy
|
||||||
|
setLogLevel(GhidraScript.class, Level.ERROR);
|
||||||
|
setLogLevel(ScriptTaskListener.class, Level.ERROR);
|
||||||
|
|
||||||
|
env = new TestEnv();
|
||||||
|
ToyProgramBuilder builder = new ToyProgramBuilder("sample", true);
|
||||||
|
program = builder.getProgram();
|
||||||
|
tool = env.launchDefaultTool(program);
|
||||||
|
|
||||||
|
plugin = env.getPlugin(ConsolePlugin.class);
|
||||||
|
provider = (ConsoleComponentProvider) tool.getComponentProvider("Console");
|
||||||
|
textPane = provider.getTextPane();
|
||||||
|
|
||||||
|
ResourceFile resourceFile =
|
||||||
|
Application.getModuleFile("Base", "ghidra_scripts/HelloWorldScript.java");
|
||||||
|
File scriptFile = resourceFile.getFile(true);
|
||||||
|
env.runScript(scriptFile);
|
||||||
|
|
||||||
|
AnimationUtils.setAnimationEnabled(false);
|
||||||
|
|
||||||
|
placeCursorAtBeginning();
|
||||||
|
findDialog = showFindDialog();
|
||||||
|
String searchText = "Hello";
|
||||||
|
find(searchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
close(findDialog);
|
||||||
|
env.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindHighlights() throws Exception {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
verfyHighlightColor(matches);
|
||||||
|
|
||||||
|
close(findDialog);
|
||||||
|
verifyDefaultBackgroundColorForAllText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindHighlights_ChangeSearchText() throws Exception {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
verfyHighlightColor(matches);
|
||||||
|
|
||||||
|
// Change the search text after the first search and make sure the new text is found and
|
||||||
|
// highlighted correctly.
|
||||||
|
String newSearchText = "java";
|
||||||
|
runSwing(() -> findDialog.setSearchText(newSearchText));
|
||||||
|
pressButtonByText(findDialog, "Next");
|
||||||
|
matches = getMatches();
|
||||||
|
assertEquals(3, matches.size());
|
||||||
|
verfyHighlightColor(matches);
|
||||||
|
|
||||||
|
close(findDialog);
|
||||||
|
verifyDefaultBackgroundColorForAllText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindHighlights_ChangeDocumentText() throws Exception {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
verfyHighlightColor(matches);
|
||||||
|
|
||||||
|
runSwing(() -> textPane.setText("This is some\nnew text."));
|
||||||
|
|
||||||
|
verifyDefaultBackgroundColorForAllText();
|
||||||
|
assertSearchModelHasStaleSearchResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMovingCursorUpdatesActiveHighlight() {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
TestTextMatch first = matches.get(0);
|
||||||
|
TestTextMatch second = matches.get(1);
|
||||||
|
TestTextMatch third = matches.get(2);
|
||||||
|
TestTextMatch last = matches.get(3);
|
||||||
|
|
||||||
|
placeCursonInMatch(second);
|
||||||
|
assertActiveHighlight(second);
|
||||||
|
|
||||||
|
placeCursonInMatch(third);
|
||||||
|
assertActiveHighlight(third);
|
||||||
|
|
||||||
|
placeCursonInMatch(first);
|
||||||
|
assertActiveHighlight(first);
|
||||||
|
|
||||||
|
placeCursonInMatch(last);
|
||||||
|
assertActiveHighlight(last);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindNext_ChangeDocumentText() throws Exception {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
TestTextMatch first = matches.get(0);
|
||||||
|
TestTextMatch second = matches.get(1);
|
||||||
|
|
||||||
|
assertCursorInMatch(first);
|
||||||
|
assertActiveHighlight(first);
|
||||||
|
|
||||||
|
next();
|
||||||
|
assertCursorInMatch(second);
|
||||||
|
assertActiveHighlight(second);
|
||||||
|
|
||||||
|
// Append text to the end of the document. This will cause the matches to be recalculated.
|
||||||
|
// The caret will remain on the current match.
|
||||||
|
appendText(" Hello, this is some\nnew text. Hello");
|
||||||
|
assertSearchModelHasStaleSearchResults();
|
||||||
|
|
||||||
|
// Pressing next will perform the search again. The caret is still at the position of the
|
||||||
|
// second match. That match will be found and highlighted again. (This will make the search
|
||||||
|
// appear as though the Next button did not move to the next match. Not sure if this is
|
||||||
|
// worth worrying about.)
|
||||||
|
next();
|
||||||
|
|
||||||
|
matches = getMatches();
|
||||||
|
assertEquals(6, matches.size()); // 4 old matches plus 2 new matches
|
||||||
|
second = matches.get(1);
|
||||||
|
assertCursorInMatch(second);
|
||||||
|
assertActiveHighlight(second);
|
||||||
|
|
||||||
|
next(); // third
|
||||||
|
next(); // fourth
|
||||||
|
next(); // fifth
|
||||||
|
next(); // sixth
|
||||||
|
TestTextMatch last = matches.get(5); // search wrapped
|
||||||
|
assertCursorInMatch(last);
|
||||||
|
assertActiveHighlight(last);
|
||||||
|
|
||||||
|
close(findDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindNext() throws Exception {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
TestTextMatch first = matches.get(0);
|
||||||
|
TestTextMatch second = matches.get(1);
|
||||||
|
TestTextMatch third = matches.get(2);
|
||||||
|
TestTextMatch last = matches.get(3);
|
||||||
|
|
||||||
|
assertCursorInMatch(first);
|
||||||
|
assertActiveHighlight(first);
|
||||||
|
|
||||||
|
placeCursonInMatch(second);
|
||||||
|
assertActiveHighlight(second);
|
||||||
|
|
||||||
|
next();
|
||||||
|
|
||||||
|
assertCursorInMatch(third);
|
||||||
|
assertActiveHighlight(third);
|
||||||
|
|
||||||
|
next();
|
||||||
|
|
||||||
|
assertCursorInMatch(last);
|
||||||
|
assertActiveHighlight(last);
|
||||||
|
|
||||||
|
next();
|
||||||
|
|
||||||
|
assertCursorInMatch(first);
|
||||||
|
assertActiveHighlight(first);
|
||||||
|
|
||||||
|
close(findDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindNext_MoveCaret() throws Exception {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
TestTextMatch first = matches.get(0);
|
||||||
|
TestTextMatch third = matches.get(2);
|
||||||
|
TestTextMatch last = matches.get(3);
|
||||||
|
|
||||||
|
assertCursorInMatch(first);
|
||||||
|
assertActiveHighlight(first);
|
||||||
|
|
||||||
|
placeCursonInMatch(third);
|
||||||
|
assertActiveHighlight(third);
|
||||||
|
|
||||||
|
next();
|
||||||
|
|
||||||
|
assertCursorInMatch(last);
|
||||||
|
assertActiveHighlight(last);
|
||||||
|
|
||||||
|
close(findDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindPrevious() throws Exception {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
TestTextMatch first = matches.get(0);
|
||||||
|
TestTextMatch second = matches.get(1);
|
||||||
|
TestTextMatch third = matches.get(2);
|
||||||
|
TestTextMatch last = matches.get(3);
|
||||||
|
|
||||||
|
assertCursorInMatch(first);
|
||||||
|
assertActiveHighlight(first);
|
||||||
|
|
||||||
|
previous();
|
||||||
|
|
||||||
|
assertCursorInMatch(last);
|
||||||
|
assertActiveHighlight(last);
|
||||||
|
|
||||||
|
previous();
|
||||||
|
|
||||||
|
assertCursorInMatch(third);
|
||||||
|
assertActiveHighlight(third);
|
||||||
|
|
||||||
|
previous();
|
||||||
|
|
||||||
|
assertCursorInMatch(second);
|
||||||
|
assertActiveHighlight(second);
|
||||||
|
|
||||||
|
previous();
|
||||||
|
|
||||||
|
assertCursorInMatch(first);
|
||||||
|
assertActiveHighlight(first);
|
||||||
|
|
||||||
|
close(findDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindPrevious_MoveCaret() throws Exception {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
TestTextMatch first = matches.get(0);
|
||||||
|
TestTextMatch second = matches.get(1);
|
||||||
|
TestTextMatch third = matches.get(2);
|
||||||
|
|
||||||
|
assertCursorInMatch(first);
|
||||||
|
assertActiveHighlight(first);
|
||||||
|
|
||||||
|
placeCursonInMatch(third);
|
||||||
|
assertActiveHighlight(third);
|
||||||
|
|
||||||
|
previous();
|
||||||
|
|
||||||
|
assertCursorInMatch(second);
|
||||||
|
assertActiveHighlight(second);
|
||||||
|
|
||||||
|
close(findDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClear() throws Exception {
|
||||||
|
|
||||||
|
List<TestTextMatch> matches = getMatches();
|
||||||
|
assertEquals(4, matches.size());
|
||||||
|
verfyHighlightColor(matches);
|
||||||
|
|
||||||
|
clear();
|
||||||
|
|
||||||
|
assertSearchModelHasNoSearchResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendText(String text) {
|
||||||
|
runSwing(() -> {
|
||||||
|
|
||||||
|
Document document = textPane.getDocument();
|
||||||
|
int length = document.getLength();
|
||||||
|
try {
|
||||||
|
document.insertString(length, text, null);
|
||||||
|
}
|
||||||
|
catch (BadLocationException e) {
|
||||||
|
failWithException("Failed to append text", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
waitForSwing(); // wait for the buffered response
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clear() {
|
||||||
|
DockingActionIf action = getAction(plugin, "Clear Console");
|
||||||
|
performAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void next() {
|
||||||
|
pressButtonByText(findDialog, "Next");
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void previous() {
|
||||||
|
pressButtonByText(findDialog, "Previous");
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSearchModelHasNoSearchResults() {
|
||||||
|
TextComponentSearcher searcher =
|
||||||
|
(TextComponentSearcher) findDialog.getSearcher();
|
||||||
|
assertFalse(searcher.hasSearchResults());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSearchModelHasStaleSearchResults() {
|
||||||
|
TextComponentSearcher searcher =
|
||||||
|
(TextComponentSearcher) findDialog.getSearcher();
|
||||||
|
assertTrue(searcher.isStale());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertCursorInMatch(TestTextMatch match) {
|
||||||
|
int pos = runSwing(() -> textPane.getCaretPosition());
|
||||||
|
waitForSwing();
|
||||||
|
assertTrue("Caret position %s not in match %s".formatted(pos, match),
|
||||||
|
match.start <= pos && pos <= match.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertActiveHighlight(TestTextMatch match) {
|
||||||
|
|
||||||
|
GColor expectedHlColor = new GColor("color.bg.find.highlight.active");
|
||||||
|
assertActiveHighlight(match, expectedHlColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertActiveHighlight(TestTextMatch match, Color expectedHlColor) {
|
||||||
|
Highlight matchHighlight = runSwing(() -> {
|
||||||
|
|
||||||
|
Highlighter highlighter = textPane.getHighlighter();
|
||||||
|
Highlight[] highlights = highlighter.getHighlights();
|
||||||
|
for (Highlight hl : highlights) {
|
||||||
|
int start = hl.getStartOffset();
|
||||||
|
int end = hl.getEndOffset();
|
||||||
|
if (start == match.start && end == match.end) {
|
||||||
|
return hl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertNotNull(matchHighlight);
|
||||||
|
DefaultHighlightPainter painter = (DefaultHighlightPainter) matchHighlight.getPainter();
|
||||||
|
Color actualHlColor = painter.getColor();
|
||||||
|
assertEquals(expectedHlColor, actualHlColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void placeCursorAtBeginning() {
|
||||||
|
runSwing(() -> textPane.setCaretPosition(0));
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void placeCursonInMatch(TestTextMatch match) {
|
||||||
|
int pos = match.start;
|
||||||
|
runSwing(() -> textPane.setCaretPosition(pos));
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verfyHighlightColor(List<TestTextMatch> matches)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
GColor nonActiveHlColor = new GColor("color.bg.find.highlight");
|
||||||
|
GColor activeHlColor = new GColor("color.bg.find.highlight.active");
|
||||||
|
|
||||||
|
int caret = textPane.getCaretPosition();
|
||||||
|
for (TestTextMatch match : matches) {
|
||||||
|
Color expectedColor = nonActiveHlColor;
|
||||||
|
if (match.contains(caret)) {
|
||||||
|
expectedColor = activeHlColor;
|
||||||
|
}
|
||||||
|
assertActiveHighlight(match, expectedColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyDefaultBackgroundColorForAllText() throws Exception {
|
||||||
|
StyledDocument styledDocument = textPane.getStyledDocument();
|
||||||
|
verifyDefaultBackgroundColorForAllText(styledDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyDefaultBackgroundColorForAllText(StyledDocument document) throws Exception {
|
||||||
|
String text = document.getText(0, document.getLength());
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
AttributeSet charAttrs = document.getCharacterElement(i).getAttributes();
|
||||||
|
Color actualBgColor = StyleConstants.getBackground(charAttrs);
|
||||||
|
assertNotEquals(document, actualBgColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TestTextMatch> getMatches() {
|
||||||
|
|
||||||
|
String searchText = findDialog.getSearchText();
|
||||||
|
List<TestTextMatch> results = new ArrayList<>();
|
||||||
|
String text = runSwing(() -> textPane.getText());
|
||||||
|
int index = text.indexOf(searchText);
|
||||||
|
while (index != -1) {
|
||||||
|
results.add(new TestTextMatch(index, index + searchText.length()));
|
||||||
|
index = text.indexOf(searchText, index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void find(String text) {
|
||||||
|
runSwing(() -> findDialog.setSearchText(text));
|
||||||
|
pressButtonByText(findDialog, "Next");
|
||||||
|
waitForTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FindDialog showFindDialog() {
|
||||||
|
DockingActionIf action = getAction(tool, "ConsolePlugin", "Find");
|
||||||
|
performAction(action, false);
|
||||||
|
return waitForDialogComponent(FindDialog.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record TestTextMatch(int start, int end) {
|
||||||
|
|
||||||
|
boolean contains(int caret) {
|
||||||
|
return start <= caret && caret <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[" + start + ',' + end + ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -21,14 +21,13 @@ import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.swing.JFrame;
|
import javax.swing.JFrame;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.text.Document;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import generic.test.AbstractGuiTest;
|
import generic.test.AbstractGuiTest;
|
||||||
import ghidra.framework.plugintool.DummyPluginTool;
|
import ghidra.framework.plugintool.DummyPluginTool;
|
||||||
|
|
||||||
public class ConsoleTextPaneTest {
|
public class ConsoleTextPaneTest extends AbstractGuiTest {
|
||||||
|
|
||||||
private int runNumber = 1;
|
private int runNumber = 1;
|
||||||
|
|
||||||
|
@ -104,34 +103,36 @@ public class ConsoleTextPaneTest {
|
||||||
assertCaretAtBottom(text);
|
assertCaretAtBottom(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//=================================================================================================
|
||||||
|
// Private Methods
|
||||||
|
//=================================================================================================
|
||||||
|
|
||||||
private void setCaret(ConsoleTextPane text, int position) {
|
private void setCaret(ConsoleTextPane text, int position) {
|
||||||
swing(() -> text.setCaretPosition(position));
|
swing(() -> text.setCaretPosition(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertCaretAtTop(ConsoleTextPane text) {
|
private void assertCaretAtTop(ConsoleTextPane text) {
|
||||||
|
|
||||||
AbstractGuiTest.waitForSwing();
|
waitForSwing();
|
||||||
int expectedPosition = 0;
|
int expectedPosition = 0;
|
||||||
assertCaretPosition(text, expectedPosition);
|
assertCaretPosition(text, expectedPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertCaretAtBottom(ConsoleTextPane text) {
|
private void assertCaretAtBottom(ConsoleTextPane text) {
|
||||||
|
|
||||||
AbstractGuiTest.waitForSwing();
|
waitForSwing();
|
||||||
int expectedPosition = text.getDocument().getLength();
|
int expectedPosition = text.getDocument().getLength();
|
||||||
assertCaretPosition(text, expectedPosition);
|
assertCaretPosition(text, expectedPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertCaretPosition(ConsoleTextPane text, int expectedPosition) {
|
private void assertCaretPosition(ConsoleTextPane text, int expectedPosition) {
|
||||||
|
waitForSwing();
|
||||||
AbstractGuiTest.waitForSwing();
|
|
||||||
Document doc = text.getDocument();
|
|
||||||
int actualPosition = swing(() -> text.getCaretPosition());
|
int actualPosition = swing(() -> text.getCaretPosition());
|
||||||
assertEquals(expectedPosition, actualPosition);
|
assertEquals(expectedPosition, actualPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printEnoughLinesToOverflowTheMaxCharCount(ConsoleTextPane text) {
|
private void printEnoughLinesToOverflowTheMaxCharCount(ConsoleTextPane text) {
|
||||||
AbstractGuiTest.runSwing(() -> {
|
runSwing(() -> {
|
||||||
|
|
||||||
int charsWritten = 0;
|
int charsWritten = 0;
|
||||||
for (int i = 0; charsWritten < text.getMaximumCharacterLimit(); i++) {
|
for (int i = 0; charsWritten < text.getMaximumCharacterLimit(); i++) {
|
||||||
|
@ -145,10 +146,10 @@ public class ConsoleTextPaneTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void swing(Runnable r) {
|
private void swing(Runnable r) {
|
||||||
AbstractGuiTest.runSwing(r);
|
runSwing(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T swing(Supplier<T> s) {
|
private <T> T swing(Supplier<T> s) {
|
||||||
return AbstractGuiTest.runSwing(s);
|
return runSwing(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -35,11 +35,11 @@ import ghidra.util.datastruct.Duo.Side;
|
||||||
|
|
||||||
public class DecompilerDiffViewFindAction extends DockingAction {
|
public class DecompilerDiffViewFindAction extends DockingAction {
|
||||||
|
|
||||||
private Duo<FindDialog> findDialogs;
|
private Duo<FindDialog> findDialogs = new Duo<>();
|
||||||
private PluginTool tool;
|
private PluginTool tool;
|
||||||
|
|
||||||
public DecompilerDiffViewFindAction(String owner, PluginTool tool) {
|
public DecompilerDiffViewFindAction(String owner, PluginTool tool) {
|
||||||
super("Find", owner, true);
|
super("Find", owner, KeyBindingType.SHARED);
|
||||||
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
||||||
setPopupMenuData(new MenuData(new String[] { "Find..." }, "Decompile"));
|
setPopupMenuData(new MenuData(new String[] { "Find..." }, "Decompile"));
|
||||||
setKeyBindingData(
|
setKeyBindingData(
|
||||||
|
@ -48,6 +48,12 @@ public class DecompilerDiffViewFindAction extends DockingAction {
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
findDialogs.each(dialog -> dialog.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAddToPopup(ActionContext context) {
|
public boolean isAddToPopup(ActionContext context) {
|
||||||
return (context instanceof DualDecompilerActionContext);
|
return (context instanceof DualDecompilerActionContext);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.decompiler.component;
|
package ghidra.app.decompiler.component;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -26,10 +27,10 @@ import docking.widgets.FindDialog;
|
||||||
import docking.widgets.SearchLocation;
|
import docking.widgets.SearchLocation;
|
||||||
import docking.widgets.button.GButton;
|
import docking.widgets.button.GButton;
|
||||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||||
import docking.widgets.table.AbstractDynamicTableColumnStub;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.table.TableColumnDescriptor;
|
|
||||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
|
import ghidra.app.plugin.core.decompile.actions.DecompilerSearchLocation;
|
||||||
import ghidra.app.plugin.core.decompile.actions.DecompilerSearcher;
|
import ghidra.app.plugin.core.decompile.actions.DecompilerSearcher;
|
||||||
|
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext;
|
||||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||||
import ghidra.app.util.HelpTopics;
|
import ghidra.app.util.HelpTopics;
|
||||||
import ghidra.app.util.query.TableService;
|
import ghidra.app.util.query.TableService;
|
||||||
|
@ -43,11 +44,14 @@ import ghidra.util.Msg;
|
||||||
import ghidra.util.datastruct.Accumulator;
|
import ghidra.util.datastruct.Accumulator;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.table.*;
|
import ghidra.util.table.*;
|
||||||
|
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
|
||||||
|
import ghidra.util.table.column.GColumnRenderer;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class DecompilerFindDialog extends FindDialog {
|
public class DecompilerFindDialog extends FindDialog {
|
||||||
|
|
||||||
private DecompilerPanel decompilerPanel;
|
private DecompilerPanel decompilerPanel;
|
||||||
|
private GButton showAllButton;
|
||||||
|
|
||||||
public DecompilerFindDialog(DecompilerPanel decompilerPanel) {
|
public DecompilerFindDialog(DecompilerPanel decompilerPanel) {
|
||||||
super("Decompiler Find Text", new DecompilerSearcher(decompilerPanel));
|
super("Decompiler Find Text", new DecompilerSearcher(decompilerPanel));
|
||||||
|
@ -55,7 +59,7 @@ public class DecompilerFindDialog extends FindDialog {
|
||||||
|
|
||||||
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind"));
|
||||||
|
|
||||||
GButton showAllButton = new GButton("Search All");
|
showAllButton = new GButton("Search All");
|
||||||
showAllButton.addActionListener(e -> showAll());
|
showAllButton.addActionListener(e -> showAll());
|
||||||
|
|
||||||
// move this button to the end
|
// move this button to the end
|
||||||
|
@ -65,7 +69,16 @@ public class DecompilerFindDialog extends FindDialog {
|
||||||
addButton(dismissButton);
|
addButton(dismissButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void enableButtons(boolean b) {
|
||||||
|
super.enableButtons(b);
|
||||||
|
showAllButton.setEnabled(b);
|
||||||
|
}
|
||||||
|
|
||||||
private void showAll() {
|
private void showAll() {
|
||||||
|
|
||||||
|
String searchText = getSearchText();
|
||||||
|
|
||||||
close();
|
close();
|
||||||
|
|
||||||
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
|
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
|
||||||
|
@ -78,7 +91,7 @@ public class DecompilerFindDialog extends FindDialog {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SearchLocation> results = searcher.searchAll(getSearchText(), useRegex());
|
List<SearchLocation> results = searcher.searchAll(searchText, useRegex());
|
||||||
if (!results.isEmpty()) {
|
if (!results.isEmpty()) {
|
||||||
// save off searches that find results so users can reuse them later
|
// save off searches that find results so users can reuse them later
|
||||||
storeSearchText(getSearchText());
|
storeSearchText(getSearchText());
|
||||||
|
@ -127,12 +140,6 @@ public class DecompilerFindDialog extends FindDialog {
|
||||||
provider.setTabText("'%s'".formatted(getSearchText()));
|
provider.setTabText("'%s'".formatted(getSearchText()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void dialogClosed() {
|
|
||||||
// clear the search results when the dialog is closed
|
|
||||||
decompilerPanel.setSearchResults(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
//=================================================================================================
|
//=================================================================================================
|
||||||
// Inner Classes
|
// Inner Classes
|
||||||
//=================================================================================================
|
//=================================================================================================
|
||||||
|
@ -202,18 +209,57 @@ public class DecompilerFindDialog extends FindDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ContextColumn
|
private class ContextColumn
|
||||||
extends AbstractDynamicTableColumnStub<DecompilerSearchLocation, String> {
|
extends
|
||||||
|
AbstractDynamicTableColumnStub<DecompilerSearchLocation, LocationReferenceContext> {
|
||||||
|
|
||||||
|
private GColumnRenderer<LocationReferenceContext> renderer = new ContextCellRenderer();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getValue(DecompilerSearchLocation rowObject, Settings settings,
|
public LocationReferenceContext getValue(DecompilerSearchLocation rowObject,
|
||||||
|
Settings settings,
|
||||||
ServiceProvider sp) throws IllegalArgumentException {
|
ServiceProvider sp) throws IllegalArgumentException {
|
||||||
return rowObject.getTextLine();
|
|
||||||
|
LocationReferenceContext context = rowObject.getContext();
|
||||||
|
return context;
|
||||||
|
// return rowObject.getTextLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return "Context";
|
return "Context";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GColumnRenderer<LocationReferenceContext> getColumnRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ContextCellRenderer
|
||||||
|
extends AbstractGhidraColumnRenderer<LocationReferenceContext> {
|
||||||
|
|
||||||
|
{
|
||||||
|
// the context uses html
|
||||||
|
setHTMLRenderingEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
super.getTableCellRendererComponent(data);
|
||||||
|
|
||||||
|
DecompilerSearchLocation match = (DecompilerSearchLocation) data.getRowObject();
|
||||||
|
LocationReferenceContext context = match.getContext();
|
||||||
|
String text = context.getBoldMatchingText();
|
||||||
|
setText(text);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilterString(LocationReferenceContext context, Settings settings) {
|
||||||
|
return context.getPlainText();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -18,18 +18,22 @@ package ghidra.app.plugin.core.decompile.actions;
|
||||||
import docking.widgets.CursorPosition;
|
import docking.widgets.CursorPosition;
|
||||||
import docking.widgets.SearchLocation;
|
import docking.widgets.SearchLocation;
|
||||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||||
|
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext;
|
||||||
|
|
||||||
public class DecompilerSearchLocation extends SearchLocation {
|
public class DecompilerSearchLocation extends SearchLocation {
|
||||||
|
|
||||||
private final FieldLocation fieldLocation;
|
private final FieldLocation fieldLocation;
|
||||||
private String textLine;
|
private String textLine;
|
||||||
|
private LocationReferenceContext context;
|
||||||
|
|
||||||
public DecompilerSearchLocation(FieldLocation fieldLocation, int startIndexInclusive,
|
public DecompilerSearchLocation(FieldLocation fieldLocation, int startIndexInclusive,
|
||||||
int endIndexInclusive, String searchText, boolean forwardDirection, String textLine) {
|
int endIndexInclusive, String searchText, boolean forwardDirection, String textLine,
|
||||||
|
LocationReferenceContext context) {
|
||||||
|
|
||||||
super(startIndexInclusive, endIndexInclusive, searchText, forwardDirection);
|
super(startIndexInclusive, endIndexInclusive, searchText, forwardDirection);
|
||||||
this.fieldLocation = fieldLocation;
|
this.fieldLocation = fieldLocation;
|
||||||
this.textLine = textLine;
|
this.textLine = textLine;
|
||||||
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FieldLocation getFieldLocation() {
|
public FieldLocation getFieldLocation() {
|
||||||
|
@ -40,6 +44,10 @@ public class DecompilerSearchLocation extends SearchLocation {
|
||||||
return textLine;
|
return textLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocationReferenceContext getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CursorPosition getCursorPosition() {
|
public CursorPosition getCursorPosition() {
|
||||||
return new DecompilerCursorPosition(fieldLocation);
|
return new DecompilerCursorPosition(fieldLocation);
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -25,6 +25,8 @@ import docking.widgets.fieldpanel.support.FieldLocation;
|
||||||
import docking.widgets.fieldpanel.support.RowColLocation;
|
import docking.widgets.fieldpanel.support.RowColLocation;
|
||||||
import ghidra.app.decompiler.component.ClangTextField;
|
import ghidra.app.decompiler.component.ClangTextField;
|
||||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||||
|
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext;
|
||||||
|
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContextBuilder;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.UserSearchUtils;
|
import ghidra.util.UserSearchUtils;
|
||||||
|
|
||||||
|
@ -84,6 +86,16 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||||
decompilerPanel.setSearchResults(location);
|
decompilerPanel.setSearchResults(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHighlights() {
|
||||||
|
decompilerPanel.setSearchResults(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
clearHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SearchLocation search(String text, CursorPosition position, boolean searchForward,
|
public SearchLocation search(String text, CursorPosition position, boolean searchForward,
|
||||||
boolean useRegex) {
|
boolean useRegex) {
|
||||||
|
@ -160,7 +172,6 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||||
results.add(searchLocation);
|
results.add(searchLocation);
|
||||||
|
|
||||||
FieldLocation last = searchLocation.getFieldLocation();
|
FieldLocation last = searchLocation.getFieldLocation();
|
||||||
|
|
||||||
int line = last.getIndex().intValue();
|
int line = last.getIndex().intValue();
|
||||||
int field = 0; // there is only 1 field
|
int field = 0; // there is only 1 field
|
||||||
int row = 0; // there is only 1 row
|
int row = 0; // there is only 1 row
|
||||||
|
@ -260,14 +271,15 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||||
if (match == SearchMatch.NO_MATCH) {
|
if (match == SearchMatch.NO_MATCH) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String fullLine = field.getText();
|
||||||
if (i == line) { // cursor is on this line
|
if (i == line) { // cursor is on this line
|
||||||
//
|
//
|
||||||
// The match start for all lines without the cursor will be relative to the start
|
// 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,
|
// 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
|
// 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.
|
// compensate for the difference between the start of the line and the cursor.
|
||||||
//
|
//
|
||||||
String fullLine = field.getText();
|
|
||||||
int cursorOffset = fullLine.length() - partialLine.length();
|
int cursorOffset = fullLine.length() - partialLine.length();
|
||||||
match.start += cursorOffset;
|
match.start += cursorOffset;
|
||||||
match.end += cursorOffset;
|
match.end += cursorOffset;
|
||||||
|
@ -276,13 +288,26 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||||
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
|
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
|
||||||
FieldLocation fieldLocation =
|
FieldLocation fieldLocation =
|
||||||
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
|
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
|
||||||
|
LocationReferenceContext context = createContext(fullLine, match);
|
||||||
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
|
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||||
searchString, true, field.getText());
|
searchString, true, field.getText(), context);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LocationReferenceContext createContext(String line, SearchMatch match) {
|
||||||
|
LocationReferenceContextBuilder builder = new LocationReferenceContextBuilder();
|
||||||
|
int start = match.start;
|
||||||
|
int end = match.end;
|
||||||
|
builder.append(line.substring(0, start));
|
||||||
|
builder.appendMatch(line.substring(start, end));
|
||||||
|
if (end < line.length()) {
|
||||||
|
builder.append(line.substring(end));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
private DecompilerSearchLocation findPrevious(Function<String, SearchMatch> matcher,
|
private DecompilerSearchLocation findPrevious(Function<String, SearchMatch> matcher,
|
||||||
String searchString, FieldLocation currentLocation) {
|
String searchString, FieldLocation currentLocation) {
|
||||||
|
|
||||||
|
@ -291,16 +316,17 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||||
for (int i = line; i >= 0; i--) {
|
for (int i = line; i >= 0; i--) {
|
||||||
ClangTextField field = (ClangTextField) fields.get(i);
|
ClangTextField field = (ClangTextField) fields.get(i);
|
||||||
String textLine = substring(field, (i == line) ? currentLocation : null, false);
|
String textLine = substring(field, (i == line) ? currentLocation : null, false);
|
||||||
|
|
||||||
SearchMatch match = matcher.apply(textLine);
|
SearchMatch match = matcher.apply(textLine);
|
||||||
if (match != SearchMatch.NO_MATCH) {
|
if (match == SearchMatch.NO_MATCH) {
|
||||||
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
|
continue;
|
||||||
FieldLocation fieldLocation =
|
|
||||||
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
|
|
||||||
|
|
||||||
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
|
|
||||||
searchString, false, field.getText());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FieldLineLocation lineInfo = getFieldIndexFromOffset(match.start, field);
|
||||||
|
FieldLocation fieldLocation =
|
||||||
|
new FieldLocation(i, lineInfo.fieldNumber(), 0, lineInfo.column());
|
||||||
|
LocationReferenceContext context = createContext(field.getText(), match);
|
||||||
|
return new DecompilerSearchLocation(fieldLocation, match.start, match.end - 1,
|
||||||
|
searchString, false, field.getText(), context);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -317,7 +343,6 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
String partialText = textField.getText();
|
String partialText = textField.getText();
|
||||||
|
|
||||||
if (forwardSearch) {
|
if (forwardSearch) {
|
||||||
|
|
||||||
int nextCol = location.getCol();
|
int nextCol = location.getCol();
|
||||||
|
@ -365,6 +390,5 @@ public class DecompilerSearcher implements FindDialogSearcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record FieldLineLocation(int fieldNumber, int column) {
|
private record FieldLineLocation(int fieldNumber, int column) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -20,8 +20,7 @@ import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import docking.action.KeyBindingData;
|
import docking.action.*;
|
||||||
import docking.action.MenuData;
|
|
||||||
import docking.widgets.FindDialog;
|
import docking.widgets.FindDialog;
|
||||||
import ghidra.app.decompiler.component.DecompilerFindDialog;
|
import ghidra.app.decompiler.component.DecompilerFindDialog;
|
||||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||||
|
@ -40,6 +39,11 @@ public class FindAction extends AbstractDecompilerAction {
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyBindingType getKeyBindingType() {
|
||||||
|
return KeyBindingType.SHARED;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
if (findDialog != null) {
|
if (findDialog != null) {
|
||||||
|
|
|
@ -22,6 +22,9 @@ color.bg.highlight = color.palette.lemonchiffon
|
||||||
|
|
||||||
color.bg.currentline = color.palette.aliceblue
|
color.bg.currentline = color.palette.aliceblue
|
||||||
|
|
||||||
|
color.bg.find.highlight = color.palette.yellow
|
||||||
|
color.bg.find.highlight.active = color.palette.orange
|
||||||
|
|
||||||
color.bg.textfield.hint.valid = color.bg
|
color.bg.textfield.hint.valid = color.bg
|
||||||
color.bg.textfield.hint.invalid = color.palette.mistyrose
|
color.bg.textfield.hint.invalid = color.palette.mistyrose
|
||||||
color.fg.textfield.hint = color.fg.messages.hint
|
color.fg.textfield.hint = color.fg.messages.hint
|
||||||
|
@ -184,7 +187,8 @@ font.wizard.border.title = sansserif-plain-10
|
||||||
|
|
||||||
color.fg.filterfield = color.palette.darkslategray
|
color.fg.filterfield = color.palette.darkslategray
|
||||||
|
|
||||||
color.bg.highlight = #703401 // orangish
|
color.bg.highlight = #67582A // olivish
|
||||||
|
color.bg.find.highlight.active = #A24E05 // orangish
|
||||||
|
|
||||||
color.bg.filechooser.shortcut = [color]system.color.bg.view
|
color.bg.filechooser.shortcut = [color]system.color.bg.view
|
||||||
|
|
||||||
|
|
|
@ -725,6 +725,12 @@ public class DialogComponentProvider
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Callback animatorFinishedCallback = () -> {
|
||||||
|
statusLabel.setVisible(true);
|
||||||
|
alertFinishedCallback.call();
|
||||||
|
isAlerting = false;
|
||||||
|
};
|
||||||
|
|
||||||
isAlerting = true;
|
isAlerting = true;
|
||||||
|
|
||||||
// Note: manually call validate() so the 'statusLabel' updates its bounds after
|
// Note: manually call validate() so the 'statusLabel' updates its bounds after
|
||||||
|
@ -733,14 +739,17 @@ public class DialogComponentProvider
|
||||||
mainPanel.validate();
|
mainPanel.validate();
|
||||||
statusLabel.setVisible(false); // disable painting in this dialog so we don't see double
|
statusLabel.setVisible(false); // disable painting in this dialog so we don't see double
|
||||||
Animator animator = AnimationUtils.pulseComponent(statusLabel, 1);
|
Animator animator = AnimationUtils.pulseComponent(statusLabel, 1);
|
||||||
animator.addTarget(new TimingTargetAdapter() {
|
if (animator == null) {
|
||||||
@Override
|
animatorFinishedCallback.call();
|
||||||
public void end() {
|
}
|
||||||
statusLabel.setVisible(true);
|
else {
|
||||||
alertFinishedCallback.call();
|
animator.addTarget(new TimingTargetAdapter() {
|
||||||
isAlerting = false;
|
@Override
|
||||||
}
|
public void end() {
|
||||||
});
|
animatorFinishedCallback.call();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Color getStatusColor(MessageType type) {
|
protected Color getStatusColor(MessageType type) {
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -101,7 +101,7 @@ public class DockingHelpBroker extends GHelpBroker {
|
||||||
@Override
|
@Override
|
||||||
protected void installHelpSearcher(JHelp jHelp, HelpModel helpModel) {
|
protected void installHelpSearcher(JHelp jHelp, HelpModel helpModel) {
|
||||||
helpModel.addHelpModelListener(helpModelListener);
|
helpModel.addHelpModelListener(helpModelListener);
|
||||||
new HelpViewSearcher(jHelp, helpModel);
|
new HelpViewSearcher(jHelp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -18,27 +18,21 @@ package docking.help;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import java.awt.geom.Rectangle2D;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.Enumeration;
|
||||||
import java.util.regex.*;
|
|
||||||
|
|
||||||
import javax.help.*;
|
import javax.help.*;
|
||||||
import javax.help.DefaultHelpModel.DefaultHighlight;
|
|
||||||
import javax.help.search.SearchEngine;
|
import javax.help.search.SearchEngine;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.text.BadLocationException;
|
|
||||||
import javax.swing.text.Document;
|
|
||||||
|
|
||||||
import docking.DockingUtils;
|
import docking.DockingUtils;
|
||||||
import docking.DockingWindowManager;
|
import docking.DockingWindowManager;
|
||||||
import docking.actions.KeyBindingUtils;
|
import docking.actions.KeyBindingUtils;
|
||||||
import docking.widgets.*;
|
import docking.widgets.FindDialog;
|
||||||
|
import docking.widgets.TextComponentSearcher;
|
||||||
import generic.util.WindowUtilities;
|
import generic.util.WindowUtilities;
|
||||||
import ghidra.util.Msg;
|
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
import ghidra.util.task.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables the Find Dialog for searching through the current page of a help document.
|
* Enables the Find Dialog for searching through the current page of a help document.
|
||||||
|
@ -51,49 +45,19 @@ class HelpViewSearcher {
|
||||||
private static KeyStroke FIND_KEYSTROKE =
|
private static KeyStroke FIND_KEYSTROKE =
|
||||||
KeyStroke.getKeyStroke(KeyEvent.VK_F, DockingUtils.CONTROL_KEY_MODIFIER_MASK);
|
KeyStroke.getKeyStroke(KeyEvent.VK_F, DockingUtils.CONTROL_KEY_MODIFIER_MASK);
|
||||||
|
|
||||||
private Comparator<SearchHit> searchResultComparator =
|
|
||||||
(o1, o2) -> o1.getBegin() - o2.getBegin();
|
|
||||||
|
|
||||||
private Comparator<? super SearchHit> searchResultReverseComparator =
|
|
||||||
(o1, o2) -> o2.getBegin() - o1.getBegin();
|
|
||||||
|
|
||||||
private JHelp jHelp;
|
private JHelp jHelp;
|
||||||
private SearchEngine searchEngine;
|
private SearchEngine searchEngine;
|
||||||
private HelpModel helpModel;
|
|
||||||
|
|
||||||
private JEditorPane htmlEditorPane;
|
private JEditorPane htmlEditorPane;
|
||||||
|
|
||||||
private FindDialog findDialog;
|
private FindDialog findDialog;
|
||||||
|
|
||||||
private boolean startSearchFromBeginning;
|
HelpViewSearcher(JHelp jHelp) {
|
||||||
private boolean settingHighlights;
|
|
||||||
|
|
||||||
HelpViewSearcher(JHelp jHelp, HelpModel helpModel) {
|
|
||||||
this.jHelp = jHelp;
|
this.jHelp = jHelp;
|
||||||
this.helpModel = helpModel;
|
|
||||||
|
|
||||||
findDialog = new FindDialog(DIALOG_TITLE_PREFIX, new Searcher()) {
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
super.close();
|
|
||||||
clearHighlights();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// URL startURL = helpModel.getCurrentURL();
|
|
||||||
// if (isValidHelpURL(startURL)) {
|
|
||||||
// currentPageURL = startURL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
grabSearchEngine();
|
grabSearchEngine();
|
||||||
|
|
||||||
JHelpContentViewer contentViewer = jHelp.getContentViewer();
|
JHelpContentViewer contentViewer = jHelp.getContentViewer();
|
||||||
contentViewer.addTextHelpModelListener(e -> {
|
|
||||||
if (settingHighlights) {
|
|
||||||
return; // ignore our changes
|
|
||||||
}
|
|
||||||
clearSearchState();
|
|
||||||
});
|
|
||||||
|
|
||||||
contentViewer.addHelpModelListener(e -> {
|
contentViewer.addHelpModelListener(e -> {
|
||||||
URL url = e.getURL();
|
URL url = e.getURL();
|
||||||
|
@ -102,24 +66,22 @@ class HelpViewSearcher {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// currentPageURL = url;
|
|
||||||
|
|
||||||
String file = url.getFile();
|
String file = url.getFile();
|
||||||
int separatorIndex = file.lastIndexOf(File.separator);
|
int separatorIndex = file.lastIndexOf(File.separator);
|
||||||
file = file.substring(separatorIndex + 1);
|
file = file.substring(separatorIndex + 1);
|
||||||
findDialog.setTitle(DIALOG_TITLE_PREFIX + file);
|
findDialog.setTitle(DIALOG_TITLE_PREFIX + file);
|
||||||
|
|
||||||
clearSearchState(); // new page
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// note: see HTMLEditorKit$LinkController.mouseMoved() for inspiration
|
// note: see HTMLEditorKit$LinkController.mouseMoved() for inspiration
|
||||||
htmlEditorPane = getHTMLEditorPane(contentViewer);
|
htmlEditorPane = getHTMLEditorPane(contentViewer);
|
||||||
|
|
||||||
|
TextComponentSearcher searcher = new TextComponentSearcher(htmlEditorPane);
|
||||||
|
findDialog = new FindDialog(DIALOG_TITLE_PREFIX, searcher);
|
||||||
|
|
||||||
htmlEditorPane.addMouseListener(new MouseAdapter() {
|
htmlEditorPane.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
htmlEditorPane.getCaret().setVisible(true);
|
htmlEditorPane.getCaret().setVisible(true);
|
||||||
startSearchFromBeginning = false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -214,14 +176,6 @@ class HelpViewSearcher {
|
||||||
return (JEditorPane) viewport.getView();
|
return (JEditorPane) viewport.getView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearSearchState() {
|
|
||||||
startSearchFromBeginning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearHighlights() {
|
|
||||||
((TextHelpModel) helpModel).removeAllHighlights();
|
|
||||||
}
|
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Inner Classes
|
// Inner Classes
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
@ -239,156 +193,6 @@ class HelpViewSearcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Searcher implements FindDialogSearcher {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CursorPosition getCursorPosition() {
|
|
||||||
if (startSearchFromBeginning) {
|
|
||||||
startSearchFromBeginning = false;
|
|
||||||
return new CursorPosition(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int caretPosition = htmlEditorPane.getCaretPosition();
|
|
||||||
return new CursorPosition(caretPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CursorPosition getStart() {
|
|
||||||
return new CursorPosition(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CursorPosition getEnd() {
|
|
||||||
int length = htmlEditorPane.getDocument().getLength();
|
|
||||||
return new CursorPosition(length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCursorPosition(CursorPosition position) {
|
|
||||||
int cursorPosition = position.getPosition();
|
|
||||||
htmlEditorPane.setCaretPosition(cursorPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void highlightSearchResults(SearchLocation location) {
|
|
||||||
if (location == null) {
|
|
||||||
((TextHelpModel) helpModel).setHighlights(new DefaultHighlight[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = location.getStartIndexInclusive();
|
|
||||||
DefaultHighlight[] h = new DefaultHighlight[] {
|
|
||||||
new DefaultHighlight(start, location.getEndIndexInclusive()) };
|
|
||||||
|
|
||||||
// using setHighlights() instead of removeAll + add
|
|
||||||
// avoids one highlighting event
|
|
||||||
try {
|
|
||||||
settingHighlights = true;
|
|
||||||
((TextHelpModel) helpModel).setHighlights(h);
|
|
||||||
htmlEditorPane.getCaret().setVisible(true); // bug
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
settingHighlights = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Rectangle2D rectangle = htmlEditorPane.modelToView2D(start);
|
|
||||||
htmlEditorPane.scrollRectToVisible(rectangle.getBounds());
|
|
||||||
}
|
|
||||||
catch (BadLocationException e) {
|
|
||||||
// shouldn't happen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SearchLocation search(String text, CursorPosition cursorPosition,
|
|
||||||
boolean searchForward, boolean useRegex) {
|
|
||||||
ScreenSearchTask searchTask = new ScreenSearchTask(text, useRegex);
|
|
||||||
new TaskLauncher(searchTask, htmlEditorPane);
|
|
||||||
|
|
||||||
List<SearchHit> searchResults = searchTask.getSearchResults();
|
|
||||||
int position = cursorPosition.getPosition(); // move to the next item
|
|
||||||
|
|
||||||
if (searchForward) {
|
|
||||||
Collections.sort(searchResults, searchResultComparator);
|
|
||||||
for (SearchHit searchHit : searchResults) {
|
|
||||||
int begin = searchHit.getBegin();
|
|
||||||
if (begin <= position) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return new SearchLocation(begin, searchHit.getEnd(), text, searchForward);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Collections.sort(searchResults, searchResultReverseComparator);
|
|
||||||
for (SearchHit searchHit : searchResults) {
|
|
||||||
int begin = searchHit.getBegin();
|
|
||||||
if (begin >= position) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return new SearchLocation(begin, searchHit.getEnd(), text, searchForward);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null; // no more matches in the current direction
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ScreenSearchTask extends Task {
|
|
||||||
|
|
||||||
private String text;
|
|
||||||
private List<SearchHit> searchHits = new ArrayList<>();
|
|
||||||
private boolean useRegex;
|
|
||||||
|
|
||||||
ScreenSearchTask(String text, boolean useRegex) {
|
|
||||||
super("Help Search Task", true, false, true, true);
|
|
||||||
this.text = text;
|
|
||||||
this.useRegex = useRegex;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(TaskMonitor monitor) {
|
|
||||||
Document document = htmlEditorPane.getDocument();
|
|
||||||
try {
|
|
||||||
String screenText = document.getText(0, document.getLength());
|
|
||||||
|
|
||||||
if (useRegex) {
|
|
||||||
Pattern pattern =
|
|
||||||
Pattern.compile(text, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
|
|
||||||
Matcher matcher = pattern.matcher(screenText);
|
|
||||||
while (matcher.find()) {
|
|
||||||
int start = matcher.start();
|
|
||||||
int end = matcher.end();
|
|
||||||
searchHits.add(new SearchHit(1D, start, end));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int start = 0;
|
|
||||||
int wordOffset = text.length();
|
|
||||||
while (wordOffset < document.getLength()) {
|
|
||||||
String searchFor = screenText.substring(start, wordOffset);
|
|
||||||
if (text.compareToIgnoreCase(searchFor) == 0) { //Case insensitive
|
|
||||||
searchHits.add(new SearchHit(1D, start, wordOffset));
|
|
||||||
}
|
|
||||||
start++;
|
|
||||||
wordOffset++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (BadLocationException e) {
|
|
||||||
// shouldn't happen
|
|
||||||
Msg.debug(this, "Unexpected exception retrieving help text", e);
|
|
||||||
}
|
|
||||||
catch (PatternSyntaxException e) {
|
|
||||||
Msg.showError(this, htmlEditorPane, "Regular Expression Syntax Error",
|
|
||||||
e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SearchHit> getSearchResults() {
|
|
||||||
return searchHits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//
|
//
|
||||||
// private class IndexerSearchTask extends Task {
|
// private class IndexerSearchTask extends Task {
|
||||||
//
|
//
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -27,10 +27,14 @@ import docking.ReusableDialogComponentProvider;
|
||||||
import docking.widgets.button.GRadioButton;
|
import docking.widgets.button.GRadioButton;
|
||||||
import docking.widgets.combobox.GhidraComboBox;
|
import docking.widgets.combobox.GhidraComboBox;
|
||||||
import docking.widgets.label.GLabel;
|
import docking.widgets.label.GLabel;
|
||||||
|
import utility.function.Callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dialog used to perform text searches on a text display.
|
||||||
|
*/
|
||||||
public class FindDialog extends ReusableDialogComponentProvider {
|
public class FindDialog extends ReusableDialogComponentProvider {
|
||||||
|
|
||||||
private GhidraComboBox<String> comboBox;
|
protected GhidraComboBox<String> comboBox;
|
||||||
|
|
||||||
protected FindDialogSearcher searcher;
|
protected FindDialogSearcher searcher;
|
||||||
private JButton nextButton;
|
private JButton nextButton;
|
||||||
|
@ -38,6 +42,8 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||||
private JRadioButton stringRadioButton;
|
private JRadioButton stringRadioButton;
|
||||||
private JRadioButton regexRadioButton;
|
private JRadioButton regexRadioButton;
|
||||||
|
|
||||||
|
private Callback closedCallback = Callback.dummy();
|
||||||
|
|
||||||
public FindDialog(String title, FindDialogSearcher searcher) {
|
public FindDialog(String title, FindDialogSearcher searcher) {
|
||||||
super(title, false, true, true, true);
|
super(title, false, true, true, true);
|
||||||
this.searcher = searcher;
|
this.searcher = searcher;
|
||||||
|
@ -46,6 +52,16 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||||
buildButtons();
|
buildButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
searcher.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClosedCallback(Callback c) {
|
||||||
|
this.closedCallback = Callback.dummyIfNull(c);
|
||||||
|
}
|
||||||
|
|
||||||
private void buildButtons() {
|
private void buildButtons() {
|
||||||
nextButton = new JButton("Next");
|
nextButton = new JButton("Next");
|
||||||
nextButton.setMnemonic('N');
|
nextButton.setMnemonic('N');
|
||||||
|
@ -113,7 +129,7 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||||
return mainPanel;
|
return mainPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableButtons(boolean b) {
|
protected void enableButtons(boolean b) {
|
||||||
nextButton.setEnabled(b);
|
nextButton.setEnabled(b);
|
||||||
previousButton.setEnabled(b);
|
previousButton.setEnabled(b);
|
||||||
}
|
}
|
||||||
|
@ -130,6 +146,8 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||||
@Override
|
@Override
|
||||||
protected void dialogClosed() {
|
protected void dialogClosed() {
|
||||||
comboBox.setText("");
|
comboBox.setText("");
|
||||||
|
searcher.clearHighlights();
|
||||||
|
closedCallback.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void next() {
|
public void next() {
|
||||||
|
@ -206,7 +224,10 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||||
// -don't allow searching again while notifying
|
// -don't allow searching again while notifying
|
||||||
// -make sure the user can see it
|
// -make sure the user can see it
|
||||||
enableButtons(false);
|
enableButtons(false);
|
||||||
alertMessage(() -> enableButtons(true));
|
alertMessage(() -> {
|
||||||
|
String text = comboBox.getText();
|
||||||
|
enableButtons(text.length() != 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -214,6 +235,10 @@ public class FindDialog extends ReusableDialogComponentProvider {
|
||||||
clearStatusText();
|
clearStatusText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FindDialogSearcher getSearcher() {
|
||||||
|
return searcher;
|
||||||
|
}
|
||||||
|
|
||||||
String getText() {
|
String getText() {
|
||||||
if (isVisible()) {
|
if (isVisible()) {
|
||||||
return comboBox.getText();
|
return comboBox.getText();
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -60,6 +60,11 @@ public interface FindDialogSearcher {
|
||||||
*/
|
*/
|
||||||
public void highlightSearchResults(SearchLocation location);
|
public void highlightSearchResults(SearchLocation location);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears any active highlights.
|
||||||
|
*/
|
||||||
|
public void clearHighlights();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a search for the next item in the given direction starting at the given cursor
|
* Perform a search for the next item in the given direction starting at the given cursor
|
||||||
* position.
|
* position.
|
||||||
|
@ -83,4 +88,11 @@ public interface FindDialogSearcher {
|
||||||
public default List<SearchLocation> searchAll(String text, boolean useRegex) {
|
public default List<SearchLocation> searchAll(String text, boolean useRegex) {
|
||||||
throw new UnsupportedOperationException("Search All is not defined for this searcher");
|
throw new UnsupportedOperationException("Search All is not defined for this searcher");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes this searcher. This does nothing by default.
|
||||||
|
*/
|
||||||
|
public default void dispose() {
|
||||||
|
// stub
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,536 @@
|
||||||
|
/* ###
|
||||||
|
* 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 docking.widgets;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.regex.*;
|
||||||
|
|
||||||
|
import javax.swing.JEditorPane;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import generic.theme.GColor;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.UserSearchUtils;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to find text matches in the given {@link TextComponent}. This class will search for all
|
||||||
|
* matches and cache the results for future requests when the user presses Next or Previous. All
|
||||||
|
* matches will be highlighted in the text component. The match containing the cursor will be a
|
||||||
|
* different highlight color than the others. When the find dialog is closed, all highlights are
|
||||||
|
* removed.
|
||||||
|
*/
|
||||||
|
public class TextComponentSearcher implements FindDialogSearcher {
|
||||||
|
|
||||||
|
private Color highlightColor = new GColor("color.bg.find.highlight");
|
||||||
|
private Color activeHighlightColor = new GColor("color.bg.find.highlight.active");
|
||||||
|
|
||||||
|
private JEditorPane editorPane;
|
||||||
|
private DocumentListener documentListener = new DocumentChangeListener();
|
||||||
|
|
||||||
|
private CaretListener caretListener = new CaretChangeListener();
|
||||||
|
private SwingUpdateManager caretUpdater = new SwingUpdateManager(() -> updateActiveHighlight());
|
||||||
|
private volatile boolean isUpdatingCaretInternally;
|
||||||
|
|
||||||
|
private SearchResults searchResults;
|
||||||
|
|
||||||
|
public TextComponentSearcher(JEditorPane editorPane) {
|
||||||
|
this.editorPane = editorPane;
|
||||||
|
|
||||||
|
if (editorPane == null) {
|
||||||
|
return; // some clients initialize without an editor pane
|
||||||
|
}
|
||||||
|
|
||||||
|
Document document = editorPane.getDocument();
|
||||||
|
document.addDocumentListener(documentListener);
|
||||||
|
|
||||||
|
editorPane.addCaretListener(caretListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEditorPane(JEditorPane editorPane) {
|
||||||
|
if (this.editorPane != editorPane) {
|
||||||
|
markResultsStale();
|
||||||
|
}
|
||||||
|
this.editorPane = editorPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JEditorPane getEditorPane() {
|
||||||
|
return editorPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
caretUpdater.dispose();
|
||||||
|
|
||||||
|
Document document = editorPane.getDocument();
|
||||||
|
document.removeDocumentListener(documentListener);
|
||||||
|
|
||||||
|
clearHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHighlights() {
|
||||||
|
if (searchResults != null) {
|
||||||
|
searchResults.removeHighlights();
|
||||||
|
searchResults = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSearchResults() {
|
||||||
|
return searchResults != null && !searchResults.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStale() {
|
||||||
|
return searchResults != null && searchResults.isStale();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markResultsStale() {
|
||||||
|
if (searchResults != null) {
|
||||||
|
searchResults.setStale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateActiveHighlight() {
|
||||||
|
if (searchResults == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = editorPane.getCaretPosition();
|
||||||
|
searchResults.updateActiveMatch(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCaretPositionInternally(int pos) {
|
||||||
|
isUpdatingCaretInternally = true;
|
||||||
|
try {
|
||||||
|
editorPane.setCaretPosition(pos);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isUpdatingCaretInternally = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CursorPosition getCursorPosition() {
|
||||||
|
int pos = editorPane.getCaretPosition();
|
||||||
|
return new CursorPosition(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCursorPosition(CursorPosition position) {
|
||||||
|
int pos = position.getPosition();
|
||||||
|
editorPane.setCaretPosition(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CursorPosition getStart() {
|
||||||
|
return new CursorPosition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CursorPosition getEnd() {
|
||||||
|
int length = editorPane.getDocument().getLength();
|
||||||
|
return new CursorPosition(length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void highlightSearchResults(SearchLocation location) {
|
||||||
|
|
||||||
|
if (location == null) {
|
||||||
|
clearHighlights();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextComponentSearchLocation textLocation = (TextComponentSearchLocation) location;
|
||||||
|
FindMatch match = textLocation.getMatch();
|
||||||
|
searchResults.setActiveMatch(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchLocation search(String text, CursorPosition cursorPosition,
|
||||||
|
boolean searchForward, boolean useRegex) {
|
||||||
|
|
||||||
|
updateSearchResults(text, useRegex);
|
||||||
|
|
||||||
|
int pos = cursorPosition.getPosition();
|
||||||
|
int searchStart = getSearchStart(pos, searchForward);
|
||||||
|
|
||||||
|
FindMatch match = searchResults.getNextMatch(searchStart, searchForward);
|
||||||
|
if (match == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextComponentSearchLocation(match.getStart(), match.getEnd(), text,
|
||||||
|
searchForward, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSearchResults(String text, boolean useRegex) {
|
||||||
|
if (searchResults != null) {
|
||||||
|
if (!searchResults.isInvalid(text)) {
|
||||||
|
return; // the current results are still valid
|
||||||
|
}
|
||||||
|
|
||||||
|
searchResults.removeHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchTask searchTask = new SearchTask(text, useRegex);
|
||||||
|
TaskLauncher.launch(searchTask);
|
||||||
|
searchResults = searchTask.getSearchResults();
|
||||||
|
searchResults.applyHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSearchStart(int startPosition, boolean isForward) {
|
||||||
|
|
||||||
|
FindMatch activeMatch = searchResults.getActiveMatch();
|
||||||
|
if (activeMatch == null) {
|
||||||
|
return startPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastMatchStart = activeMatch.getStart();
|
||||||
|
if (startPosition != lastMatchStart) {
|
||||||
|
return startPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always prefer the caret position, unless it aligns with the previous match. By
|
||||||
|
// moving it forward one we will continue our search, as opposed to always matching
|
||||||
|
// the same hit.
|
||||||
|
if (isForward) {
|
||||||
|
return startPosition + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// backwards
|
||||||
|
if (startPosition == 0) {
|
||||||
|
return editorPane.getText().length();
|
||||||
|
}
|
||||||
|
return startPosition - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=================================================================================================
|
||||||
|
// Inner Classes
|
||||||
|
//=================================================================================================
|
||||||
|
|
||||||
|
private class SearchResults {
|
||||||
|
|
||||||
|
private TreeMap<Integer, FindMatch> matchesByPosition;
|
||||||
|
private FindMatch activeMatch;
|
||||||
|
private boolean isStale;
|
||||||
|
private String searchText;
|
||||||
|
|
||||||
|
SearchResults(String searchText, TreeMap<Integer, FindMatch> matchesByPosition) {
|
||||||
|
this.searchText = searchText;
|
||||||
|
this.matchesByPosition = matchesByPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isStale() {
|
||||||
|
return isStale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateActiveMatch(int pos) {
|
||||||
|
if (activeMatch != null) {
|
||||||
|
activeMatch.setActive(false);
|
||||||
|
activeMatch = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStale) {
|
||||||
|
// not way to easily change highlights for the caret position while we are stale,
|
||||||
|
// since the matches no longer match the document positions
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<FindMatch> it = matchesByPosition.values().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
FindMatch match = it.next();
|
||||||
|
boolean isActive = false;
|
||||||
|
if (match.contains(pos)) {
|
||||||
|
activeMatch = match;
|
||||||
|
isActive = true;
|
||||||
|
}
|
||||||
|
match.setActive(isActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FindMatch getActiveMatch() {
|
||||||
|
return activeMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
FindMatch getNextMatch(int searchStart, boolean searchForward) {
|
||||||
|
|
||||||
|
Entry<Integer, FindMatch> entry;
|
||||||
|
if (searchForward) {
|
||||||
|
entry = matchesByPosition.ceilingEntry(searchStart);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entry = matchesByPosition.floorEntry(searchStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry == null) {
|
||||||
|
return null; // no more matches in the current direction
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEmpty() {
|
||||||
|
return matchesByPosition.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStale() {
|
||||||
|
isStale = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isInvalid(String otherSearchText) {
|
||||||
|
if (isStale) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !searchText.equals(otherSearchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setActiveMatch(FindMatch match) {
|
||||||
|
if (activeMatch != null) {
|
||||||
|
activeMatch.setActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
activeMatch = match;
|
||||||
|
activeMatch.activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyHighlights() {
|
||||||
|
Collection<FindMatch> matches = matchesByPosition.values();
|
||||||
|
for (FindMatch match : matches) {
|
||||||
|
match.applyHighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeHighlights() {
|
||||||
|
|
||||||
|
activeMatch = null;
|
||||||
|
|
||||||
|
JEditorPane editor = editorPane;
|
||||||
|
Highlighter highlighter = editor.getHighlighter();
|
||||||
|
if (highlighter != null) {
|
||||||
|
highlighter.removeAllHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesByPosition.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TextComponentSearchLocation extends SearchLocation {
|
||||||
|
|
||||||
|
private FindMatch match;
|
||||||
|
|
||||||
|
public TextComponentSearchLocation(int start, int end,
|
||||||
|
String searchText, boolean forwardDirection, FindMatch match) {
|
||||||
|
super(start, end, searchText, forwardDirection);
|
||||||
|
this.match = match;
|
||||||
|
}
|
||||||
|
|
||||||
|
FindMatch getMatch() {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SearchTask extends Task {
|
||||||
|
|
||||||
|
private String searchText;
|
||||||
|
private TreeMap<Integer, FindMatch> searchHits = new TreeMap<>();
|
||||||
|
private boolean useRegex;
|
||||||
|
|
||||||
|
SearchTask(String searchText, boolean useRegex) {
|
||||||
|
super("Help Search Task", true, false, true, true);
|
||||||
|
this.searchText = searchText;
|
||||||
|
this.useRegex = useRegex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
|
|
||||||
|
String screenText;
|
||||||
|
try {
|
||||||
|
Document document = editorPane.getDocument();
|
||||||
|
screenText = document.getText(0, document.getLength());
|
||||||
|
}
|
||||||
|
catch (BadLocationException e) {
|
||||||
|
Msg.error(this, "Unable to get text for user find operation", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern pattern = createSearchPattern(searchText, useRegex);
|
||||||
|
Matcher matcher = pattern.matcher(screenText);
|
||||||
|
while (matcher.find()) {
|
||||||
|
monitor.checkCancelled();
|
||||||
|
int start = matcher.start();
|
||||||
|
int end = matcher.end();
|
||||||
|
FindMatch match = new FindMatch(searchText, start, end);
|
||||||
|
searchHits.put(start, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pattern createSearchPattern(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, editorPane, "Regular Expression Syntax Error",
|
||||||
|
e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserSearchUtils.createPattern(searchString, false, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchResults getSearchResults() {
|
||||||
|
return new SearchResults(searchText, searchHits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FindMatch {
|
||||||
|
|
||||||
|
private String text;
|
||||||
|
private int start;
|
||||||
|
private int end;
|
||||||
|
private boolean isActive;
|
||||||
|
|
||||||
|
// this tag is a way to remove an installed highlight
|
||||||
|
private Object lastHighlightTag;
|
||||||
|
|
||||||
|
FindMatch(String text, int start, int end) {
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean contains(int pos) {
|
||||||
|
// exclusive of end so the cursor behind the match does is not in the highlight
|
||||||
|
return start <= pos && pos < end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calls setActive() and moves the caret position */
|
||||||
|
void activate() {
|
||||||
|
setActive(true);
|
||||||
|
setCaretPositionInternally(start);
|
||||||
|
scrollToVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes this match active and updates the highlight color
|
||||||
|
* @param b true for active
|
||||||
|
*/
|
||||||
|
void setActive(boolean b) {
|
||||||
|
isActive = b;
|
||||||
|
applyHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scrollToVisible() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Rectangle startR = editorPane.modelToView2D(start).getBounds();
|
||||||
|
Rectangle endR = editorPane.modelToView2D(end).getBounds();
|
||||||
|
endR.width += 20; // a little extra space so the view is not right at the text end
|
||||||
|
Rectangle union = startR.union(endR);
|
||||||
|
editorPane.scrollRectToVisible(union);
|
||||||
|
}
|
||||||
|
catch (BadLocationException e) {
|
||||||
|
Msg.debug(this, "Exception scrolling to text", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[" + start + ',' + end + "] " + text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyHighlight() {
|
||||||
|
Highlighter highlighter = editorPane.getHighlighter();
|
||||||
|
if (highlighter == null) {
|
||||||
|
highlighter = new DefaultHighlighter();
|
||||||
|
editorPane.setHighlighter(highlighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Highlighter.HighlightPainter painter =
|
||||||
|
new DefaultHighlighter.DefaultHighlightPainter(
|
||||||
|
isActive ? activeHighlightColor : highlightColor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (lastHighlightTag != null) {
|
||||||
|
highlighter.removeHighlight(lastHighlightTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastHighlightTag = highlighter.addHighlight(start, end, painter);
|
||||||
|
}
|
||||||
|
catch (BadLocationException e) {
|
||||||
|
Msg.debug(this, "Exception adding highlight", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DocumentChangeListener implements DocumentListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(DocumentEvent e) {
|
||||||
|
// this allows the previous search results to stay visible until a new find is requested
|
||||||
|
markResultsStale();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e) {
|
||||||
|
markResultsStale();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e) {
|
||||||
|
// ignore attribute changes since they don't affect the text content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CaretChangeListener implements CaretListener {
|
||||||
|
|
||||||
|
private int lastPos = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void caretUpdate(CaretEvent e) {
|
||||||
|
int pos = e.getDot();
|
||||||
|
if (isUpdatingCaretInternally) {
|
||||||
|
lastPos = pos;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos == lastPos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastPos = pos;
|
||||||
|
caretUpdater.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -108,12 +108,16 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUI(ComboBoxUI ui) {
|
public void setUI(ComboBoxUI ui) {
|
||||||
|
|
||||||
|
int oldColumns = getColumns();
|
||||||
|
|
||||||
super.setUI(ui);
|
super.setUI(ui);
|
||||||
// this gets called during construction and during theming changes. It always
|
|
||||||
|
// This gets called during construction and during theming changes. It always
|
||||||
// creates a new editor and any listeners or documents set on the current editor are
|
// creates a new editor and any listeners or documents set on the current editor are
|
||||||
// lost. So to combat this, we install the pass through listeners here instead of
|
// lost. So to combat this, we install the pass through listeners here instead of
|
||||||
// in the init() method. We also reset the document if the client ever called the
|
// in the init() method. We also reset the document if the client ever called the
|
||||||
// setDocument() method
|
// setDocument() method.
|
||||||
|
|
||||||
installPassThroughListeners();
|
installPassThroughListeners();
|
||||||
|
|
||||||
|
@ -134,6 +138,13 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As mentioned above, the default editor gets replaced. In that case, restore the columns
|
||||||
|
// if the client has set the value.
|
||||||
|
if (oldColumns > 0) {
|
||||||
|
JTextField tf = getTextField();
|
||||||
|
tf.setColumns(oldColumns);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -189,14 +200,36 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
*
|
*
|
||||||
* @param columnCount The number of columns for the text field editor
|
* @param columnCount The number of columns for the text field editor
|
||||||
* @see JTextField#setColumns(int)
|
* @see JTextField#setColumns(int)
|
||||||
|
* @deprecated use {@link #setColumns(int)}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = true, since = "11.3")
|
||||||
public void setColumnCount(int columnCount) {
|
public void setColumnCount(int columnCount) {
|
||||||
JTextField textField = getTextField();
|
setColumns(columnCount);
|
||||||
textField.setColumns(columnCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects the text in the text field editor usd by this combo box.
|
* Sets the number of column's in the editor's component (JTextField).
|
||||||
|
* @param columns the number of columns to show
|
||||||
|
* @see JTextField#setColumns(int)
|
||||||
|
*/
|
||||||
|
public void setColumns(int columns) {
|
||||||
|
JTextField textField = getTextField();
|
||||||
|
textField.setColumns(columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getColumns() {
|
||||||
|
ComboBoxEditor currentEditor = getEditor();
|
||||||
|
if (currentEditor != null) {
|
||||||
|
Object object = currentEditor.getEditorComponent();
|
||||||
|
if (object instanceof JTextField textField) {
|
||||||
|
return textField.getColumns();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the text in the text field editor used by this combo box.
|
||||||
*
|
*
|
||||||
* @see JTextField#selectAll()
|
* @see JTextField#selectAll()
|
||||||
*/
|
*/
|
||||||
|
@ -297,16 +330,6 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
|
||||||
docListeners.remove(l);
|
docListeners.remove(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the number of column's in the editor's component (JTextField).
|
|
||||||
* @param columns the number of columns to show
|
|
||||||
* @see JTextField#setColumns(int)
|
|
||||||
*/
|
|
||||||
public void setColumns(int columns) {
|
|
||||||
JTextField textField = getTextField();
|
|
||||||
textField.setColumns(columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for associating a label with the editor component.
|
* Convenience method for associating a label with the editor component.
|
||||||
* @param label the label to associate
|
* @param label the label to associate
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -63,6 +63,11 @@ public class FindDialogTest {
|
||||||
// stub
|
// stub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearHighlights() {
|
||||||
|
// stub
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SearchLocation search(String text, CursorPosition cursorPosition,
|
public SearchLocation search(String text, CursorPosition cursorPosition,
|
||||||
boolean searchForward, boolean useRegex) {
|
boolean searchForward, boolean useRegex) {
|
||||||
|
|
|
@ -473,12 +473,12 @@ public class AbstractGuiTest extends AbstractGenericTest {
|
||||||
public static AbstractButton findAbstractButtonByName(Container container, String name) {
|
public static AbstractButton findAbstractButtonByName(Container container, String name) {
|
||||||
Component[] comp = container.getComponents();
|
Component[] comp = container.getComponents();
|
||||||
for (Component element : comp) {
|
for (Component element : comp) {
|
||||||
if ((element instanceof AbstractButton) &&
|
if ((element instanceof AbstractButton button) &&
|
||||||
name.equals(((AbstractButton) element).getName())) {
|
name.equals(button.getName())) {
|
||||||
return (AbstractButton) element;
|
return button;
|
||||||
}
|
}
|
||||||
else if (element instanceof Container) {
|
else if (element instanceof Container subContainer) {
|
||||||
AbstractButton b = findAbstractButtonByName((Container) element, name);
|
AbstractButton b = findAbstractButtonByName(subContainer, name);
|
||||||
if (b != null) {
|
if (b != null) {
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue