mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
GP-5970 - Scripting - A print/println methods to color console output
This commit is contained in:
parent
6eab6693fc
commit
73bdee2546
13 changed files with 501 additions and 119 deletions
|
@ -17,7 +17,7 @@ package ghidra.app.plugin.core.console;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import java.io.PrintWriter;
|
import java.io.*;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.text.*;
|
import javax.swing.text.*;
|
||||||
|
@ -29,6 +29,7 @@ import docking.widgets.FindDialog;
|
||||||
import docking.widgets.TextComponentSearcher;
|
import docking.widgets.TextComponentSearcher;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import generic.theme.Gui;
|
import generic.theme.Gui;
|
||||||
|
import ghidra.app.script.DecoratingPrintWriter;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.HelpTopics;
|
import ghidra.app.util.HelpTopics;
|
||||||
import ghidra.framework.main.ConsoleTextPane;
|
import ghidra.framework.main.ConsoleTextPane;
|
||||||
|
@ -95,8 +96,8 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
stderr = new PrintWriter(new ConsoleWriter(this, true));
|
stderr = new ConsolePrintWriter(true);
|
||||||
stdin = new PrintWriter(new ConsoleWriter(this, false));
|
stdin = new ConsolePrintWriter(false);
|
||||||
|
|
||||||
/* call this before build() -- we get our Font here */
|
/* call this before build() -- we get our Font here */
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
|
@ -230,6 +231,11 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
textPane.addPartialMessage(msg);
|
textPane.addPartialMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void print(String msg, Color c) {
|
||||||
|
checkVisible();
|
||||||
|
textPane.addPartialMessage(msg, c);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void printError(String errmsg) {
|
public void printError(String errmsg) {
|
||||||
checkVisible();
|
checkVisible();
|
||||||
|
@ -330,6 +336,84 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement
|
||||||
// Inner Classes
|
// Inner Classes
|
||||||
//=================================================================================================
|
//=================================================================================================
|
||||||
|
|
||||||
|
private class ConsolePrintWriter extends DecoratingPrintWriter {
|
||||||
|
|
||||||
|
private ColoringConsoleWriter writer;
|
||||||
|
|
||||||
|
ConsolePrintWriter(boolean error) {
|
||||||
|
this(new ColoringConsoleWriter(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConsolePrintWriter(ColoringConsoleWriter writer) {
|
||||||
|
super(writer);
|
||||||
|
this.writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void println(String s, Color c) {
|
||||||
|
try {
|
||||||
|
writer.setColor(c);
|
||||||
|
print(s);
|
||||||
|
println();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
writer.setColor(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void print(String s, Color c) {
|
||||||
|
try {
|
||||||
|
writer.setColor(c);
|
||||||
|
print(s);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
writer.setColor(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ColoringConsoleWriter extends Writer {
|
||||||
|
|
||||||
|
private Color color;
|
||||||
|
private boolean error;
|
||||||
|
|
||||||
|
public ColoringConsoleWriter(boolean error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setColor(Color color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(char[] cbuf, int off, int len) throws IOException {
|
||||||
|
String s = new String(cbuf, off, len);
|
||||||
|
if (error) {
|
||||||
|
printError(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color == null) {
|
||||||
|
print(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print(s, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
// stub
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
clearMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private class GoToMouseListener extends MouseAdapter {
|
private class GoToMouseListener extends MouseAdapter {
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
|
||||||
* 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 ghidra.app.services.ConsoleService;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Writer;
|
|
||||||
|
|
||||||
class ConsoleWriter extends Writer {
|
|
||||||
private ConsoleService console;
|
|
||||||
private boolean error;
|
|
||||||
|
|
||||||
ConsoleWriter(ConsoleService console, boolean error) {
|
|
||||||
super();
|
|
||||||
this.console = console;
|
|
||||||
this.error = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see java.io.Writer#close()
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
console.clearMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see java.io.Writer#flush()
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void flush() throws IOException {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see java.io.Writer#write(char[], int, int)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void write(char[] cbuf, int off, int len) throws IOException {
|
|
||||||
String str = new String(cbuf, off, len);
|
|
||||||
if (error) {
|
|
||||||
console.printError(str);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.print(str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,7 +35,7 @@ import ghidra.app.plugin.core.console.CodeCompletion;
|
||||||
*/
|
*/
|
||||||
public class CodeCompletionWindow extends JDialog {
|
public class CodeCompletionWindow extends JDialog {
|
||||||
|
|
||||||
private static final String FONT_ID = "font.plugin.terminal.completion.list";
|
static final String FONT_ID = "font.plugin.terminal.completion.list";
|
||||||
|
|
||||||
protected final InterpreterPanel console;
|
protected final InterpreterPanel console;
|
||||||
protected final JTextPane outputTextField;
|
protected final JTextPane outputTextField;
|
||||||
|
@ -394,6 +394,10 @@ class CodeCompletionListSelectionModel extends DefaultListSelectionModel {
|
||||||
*/
|
*/
|
||||||
class CodeCompletionListCellRenderer extends GListCellRenderer<CodeCompletion> {
|
class CodeCompletionListCellRenderer extends GListCellRenderer<CodeCompletion> {
|
||||||
|
|
||||||
|
CodeCompletionListCellRenderer() {
|
||||||
|
setBaseFontId(CodeCompletionWindow.FONT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getItemText(CodeCompletion value) {
|
protected String getItemText(CodeCompletion value) {
|
||||||
return value.getDescription();
|
return value.getDescription();
|
||||||
|
@ -421,7 +425,6 @@ class CodeCompletionListCellRenderer extends GListCellRenderer<CodeCompletion> {
|
||||||
}
|
}
|
||||||
|
|
||||||
component.setEnabled(list.isEnabled());
|
component.setEnabled(list.isEnabled());
|
||||||
component.setFont(list.getFont());
|
|
||||||
component.setComponentOrientation(list.getComponentOrientation());
|
component.setComponentOrientation(list.getComponentOrientation());
|
||||||
Border border = null;
|
Border border = null;
|
||||||
if (cellHasFocus) {
|
if (cellHasFocus) {
|
||||||
|
|
|
@ -26,11 +26,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.text.*;
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import org.apache.commons.io.output.WriterOutputStream;
|
||||||
|
|
||||||
import docking.DockingUtils;
|
import docking.DockingUtils;
|
||||||
import docking.actions.KeyBindingUtils;
|
import docking.actions.KeyBindingUtils;
|
||||||
import generic.theme.*;
|
import generic.theme.*;
|
||||||
import generic.util.WindowUtilities;
|
import generic.util.WindowUtilities;
|
||||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||||
|
import ghidra.app.script.DecoratingPrintWriter;
|
||||||
import ghidra.framework.options.OptionsChangeListener;
|
import ghidra.framework.options.OptionsChangeListener;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -69,8 +72,12 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
/* junit */ IPStdin stdin;
|
/* junit */ IPStdin stdin;
|
||||||
private OutputStream stdout;
|
private OutputStream stdout;
|
||||||
private OutputStream stderr;
|
private OutputStream stderr;
|
||||||
private PrintWriter outWriter;
|
private InterpreterPrintWriter outWriter;
|
||||||
private PrintWriter errWriter;
|
private InterpreterPrintWriter errWriter;
|
||||||
|
|
||||||
|
private AnsiRenderer stdErrRenderer = new AnsiRenderer();
|
||||||
|
private AnsiRenderer stdInRenderer = new AnsiRenderer();
|
||||||
|
private AnsiRenderer stdOutRenderer = new AnsiRenderer();
|
||||||
|
|
||||||
private SimpleAttributeSet STDOUT_SET;
|
private SimpleAttributeSet STDOUT_SET;
|
||||||
private SimpleAttributeSet STDERR_SET;
|
private SimpleAttributeSet STDERR_SET;
|
||||||
|
@ -129,11 +136,12 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
outputScrollPane.setFocusable(false);
|
outputScrollPane.setFocusable(false);
|
||||||
promptTextPane.setFocusable(false);
|
promptTextPane.setFocusable(false);
|
||||||
|
|
||||||
|
outWriter = new InterpreterPrintWriter(TextType.STDOUT);
|
||||||
|
errWriter = new InterpreterPrintWriter(TextType.STDERR);
|
||||||
|
|
||||||
stdin = new IPStdin();
|
stdin = new IPStdin();
|
||||||
stdout = new IPOut(TextType.STDOUT);
|
stdout = outWriter.asOutputStream();
|
||||||
stderr = new IPOut(TextType.STDERR);
|
stderr = errWriter.asOutputStream();
|
||||||
outWriter = new PrintWriter(stdout, true);
|
|
||||||
errWriter = new PrintWriter(stderr, true);
|
|
||||||
|
|
||||||
outputTextPane.setEditable(false);
|
outputTextPane.setEditable(false);
|
||||||
promptTextPane.setEditable(false);
|
promptTextPane.setEditable(false);
|
||||||
|
@ -270,7 +278,6 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
private void updateFontAttributes(Font font) {
|
private void updateFontAttributes(Font font) {
|
||||||
Font boldFont = font.deriveFont(Font.BOLD);
|
Font boldFont = font.deriveFont(Font.BOLD);
|
||||||
|
|
||||||
STDOUT_SET = new GAttributes(font, NORMAL_COLOR);
|
|
||||||
STDOUT_SET = new GAttributes(font, NORMAL_COLOR);
|
STDOUT_SET = new GAttributes(font, NORMAL_COLOR);
|
||||||
STDERR_SET = new GAttributes(font, ERROR_COLOR);
|
STDERR_SET = new GAttributes(font, ERROR_COLOR);
|
||||||
STDIN_SET = new GAttributes(boldFont, NORMAL_COLOR);
|
STDIN_SET = new GAttributes(boldFont, NORMAL_COLOR);
|
||||||
|
@ -410,11 +417,7 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
outputTextPane.setCaretPosition(Math.max(0, outputTextPane.getDocument().getLength()));
|
outputTextPane.setCaretPosition(Math.max(0, outputTextPane.getDocument().getLength()));
|
||||||
}
|
}
|
||||||
|
|
||||||
AnsiRenderer stdErrRenderer = new AnsiRenderer();
|
private void addText(String text, TextType type) {
|
||||||
AnsiRenderer stdInRenderer = new AnsiRenderer();
|
|
||||||
AnsiRenderer stdOutRenderer = new AnsiRenderer();
|
|
||||||
|
|
||||||
void addText(String text, TextType type) {
|
|
||||||
SimpleAttributeSet attributes;
|
SimpleAttributeSet attributes;
|
||||||
AnsiRenderer renderer;
|
AnsiRenderer renderer;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -438,29 +441,23 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
repositionScrollpane();
|
repositionScrollpane();
|
||||||
}
|
}
|
||||||
catch (BadLocationException e) {
|
catch (BadLocationException e) {
|
||||||
Msg.error(this, "internal document positioning error", e);
|
// shouldn't happen
|
||||||
|
Msg.error(this, "Document positioning error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class IPOut extends OutputStream {
|
private void addText(String text, Color c) {
|
||||||
TextType type;
|
|
||||||
byte[] buffer = new byte[1];
|
|
||||||
|
|
||||||
IPOut(TextType type) {
|
SimpleAttributeSet attributes = new GAttributes(getFont(), c);
|
||||||
this.type = type;
|
|
||||||
|
try {
|
||||||
|
StyledDocument document = outputTextPane.getStyledDocument();
|
||||||
|
stdOutRenderer.renderString(document, text, attributes);
|
||||||
|
repositionScrollpane();
|
||||||
}
|
}
|
||||||
|
catch (BadLocationException e) {
|
||||||
@Override
|
// shouldn't happen
|
||||||
public void write(int b) throws IOException {
|
Msg.error(this, "Document positioning error", e);
|
||||||
buffer[0] = (byte) b;
|
|
||||||
String text = new String(buffer);
|
|
||||||
addText(text, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(byte[] b, int off, int len) throws IOException {
|
|
||||||
String text = new String(b, off, len);
|
|
||||||
addText(text, type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,6 +562,102 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
|
||||||
// Inner Classes
|
// Inner Classes
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
|
private class InterpreterPrintWriter extends DecoratingPrintWriter {
|
||||||
|
|
||||||
|
private InterpreterConsoleWriter writer;
|
||||||
|
|
||||||
|
InterpreterPrintWriter(TextType type) {
|
||||||
|
this(new InterpreterConsoleWriter(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
private InterpreterPrintWriter(InterpreterConsoleWriter writer) {
|
||||||
|
super(writer);
|
||||||
|
this.writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream asOutputStream() {
|
||||||
|
try {
|
||||||
|
return WriterOutputStream.builder().setWriter(writer).getOutputStream();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
Msg.error(this, "Unable to create output stream", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void println(String s, Color c) {
|
||||||
|
try {
|
||||||
|
writer.setColor(c);
|
||||||
|
print(s);
|
||||||
|
println();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
writer.setColor(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void print(String s, Color c) {
|
||||||
|
try {
|
||||||
|
writer.setColor(c);
|
||||||
|
print(s);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
writer.setColor(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InterpreterConsoleWriter extends Writer {
|
||||||
|
|
||||||
|
private Color color;
|
||||||
|
|
||||||
|
TextType type;
|
||||||
|
byte[] buffer = new byte[1];
|
||||||
|
|
||||||
|
public InterpreterConsoleWriter(TextType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setColor(Color color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
buffer[0] = (byte) b;
|
||||||
|
String text = new String(buffer);
|
||||||
|
if (color != null) {
|
||||||
|
addText(text, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addText(text, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(char[] b, int off, int len) throws IOException {
|
||||||
|
String text = new String(b, off, len);
|
||||||
|
if (color != null) {
|
||||||
|
addText(text, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addText(text, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
// stub
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link InputStream} that has as its source text strings being pushed into
|
* An {@link InputStream} that has as its source text strings being pushed into
|
||||||
* it by a thread, and being read by another thread.
|
* it by a thread, and being read by another thread.
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -487,7 +487,6 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider {
|
||||||
// this will overwrite any changes--be sure to resolve that before calling this method!
|
// this will overwrite any changes--be sure to resolve that before calling this method!
|
||||||
try {
|
try {
|
||||||
loadScript(scriptSourceFile);
|
loadScript(scriptSourceFile);
|
||||||
fileHash = MD5Utilities.getMD5Hash(scriptSourceFile.getFile(false));
|
|
||||||
clearChanges();
|
clearChanges();
|
||||||
refreshAction();
|
refreshAction();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* ###
|
||||||
|
* 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.script;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A print writer that allows clients to specify the text color.
|
||||||
|
*/
|
||||||
|
public abstract class DecoratingPrintWriter extends PrintWriter {
|
||||||
|
|
||||||
|
public DecoratingPrintWriter(Writer out) {
|
||||||
|
super(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a line of text with the given color.
|
||||||
|
* @param s the text
|
||||||
|
* @param c the color
|
||||||
|
*/
|
||||||
|
public abstract void println(String s, Color c);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print text with the given color.
|
||||||
|
* @param s the text
|
||||||
|
* @param c the color
|
||||||
|
*/
|
||||||
|
public abstract void print(String s, Color c);
|
||||||
|
}
|
|
@ -1042,6 +1042,58 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the {@link #decorateOutput optionally} {@link #decorate(String) decorated} message
|
||||||
|
* followed by a line feed to this script's {@code stdout} {@link PrintWriter}, which is set by
|
||||||
|
* {@link #set(GhidraState, ScriptControls)}.
|
||||||
|
* <p>
|
||||||
|
* Additionally, the always {@link #decorate(String) decorated} message is written to Ghidra's
|
||||||
|
* log.
|
||||||
|
*
|
||||||
|
* @param message the message to print
|
||||||
|
* @param color the color for the text
|
||||||
|
*/
|
||||||
|
public void println(String message, Color color) {
|
||||||
|
|
||||||
|
String decoratedMessage = decorate(message);
|
||||||
|
|
||||||
|
Msg.info(GhidraScript.class, new ScriptMessage(decoratedMessage));
|
||||||
|
|
||||||
|
if (writer instanceof DecoratingPrintWriter scriptWriter) {
|
||||||
|
scriptWriter.println(decorateOutput ? decoratedMessage : message, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writer != null) {
|
||||||
|
writer.println(decorateOutput ? decoratedMessage : message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the undecorated message with no newline to this script's {@code stdout}
|
||||||
|
* {@link PrintWriter}, which is set by {@link #set(GhidraState, ScriptControls)}.
|
||||||
|
* <p>
|
||||||
|
* Additionally, the undecorated message is written to Ghidra's log.
|
||||||
|
*
|
||||||
|
* @param message the message to print
|
||||||
|
* @param color the color for the text
|
||||||
|
*/
|
||||||
|
public void print(String message, Color color) {
|
||||||
|
|
||||||
|
String decoratedMessage = decorate(message);
|
||||||
|
|
||||||
|
Msg.info(GhidraScript.class, new ScriptMessage(decoratedMessage));
|
||||||
|
|
||||||
|
if (writer instanceof DecoratingPrintWriter scriptWriter) {
|
||||||
|
scriptWriter.print(decorateOutput ? decoratedMessage : message, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writer != null) {
|
||||||
|
writer.print(decorateOutput ? decoratedMessage : message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prints the undecorated {@link java.util.Formatter formatted message} to this script's
|
* Prints the undecorated {@link java.util.Formatter formatted message} to this script's
|
||||||
* {@code stdout} {@link PrintWriter}, which is set by
|
* {@code stdout} {@link PrintWriter}, which is set by
|
||||||
|
|
|
@ -97,7 +97,7 @@ public class ScriptControls {
|
||||||
* @param monitor A cancellable monitor
|
* @param monitor A cancellable monitor
|
||||||
*/
|
*/
|
||||||
public ScriptControls(InterpreterConsole console, TaskMonitor monitor) {
|
public ScriptControls(InterpreterConsole console, TaskMonitor monitor) {
|
||||||
this(console.getStdOut(), console.getStdErr(), monitor);
|
this(console.getOutWriter(), console.getErrWriter(), monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -15,11 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.services;
|
package ghidra.app.services;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.console.ConsolePlugin;
|
import ghidra.app.plugin.core.console.ConsolePlugin;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic console interface allowing any plugin to print
|
* Generic console interface allowing any plugin to print
|
||||||
* messages to console window.
|
* messages to console window.
|
||||||
|
@ -109,8 +109,6 @@ public interface ConsoleService {
|
||||||
* please throw {@link UnsupportedOperationException}.
|
* please throw {@link UnsupportedOperationException}.
|
||||||
*
|
*
|
||||||
* @return number of characters >= 0
|
* @return number of characters >= 0
|
||||||
*
|
|
||||||
* @throws UnsupportedOperationException
|
|
||||||
*/
|
*/
|
||||||
public int getTextLength();
|
public int getTextLength();
|
||||||
|
|
||||||
|
@ -128,8 +126,6 @@ public interface ConsoleService {
|
||||||
* @param length the length of the desired string >= 0
|
* @param length the length of the desired string >= 0
|
||||||
*
|
*
|
||||||
* @return the text, in a String of length >= 0
|
* @return the text, in a String of length >= 0
|
||||||
*
|
|
||||||
* @throws UnsupportedOperationException
|
|
||||||
*/
|
*/
|
||||||
public String getText(int offset, int length);
|
public String getText(int offset, int length);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.main;
|
package ghidra.framework.main;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.swing.JTextPane;
|
import javax.swing.JTextPane;
|
||||||
import javax.swing.text.*;
|
import javax.swing.text.*;
|
||||||
|
@ -83,6 +85,10 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
|
||||||
doAddMessage(new MessageWrapper(message));
|
doAddMessage(new MessageWrapper(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addPartialMessage(String message, Color c) {
|
||||||
|
doAddMessage(new MessageWrapper(message, getFont(), c));
|
||||||
|
}
|
||||||
|
|
||||||
public void addErrorMessage(String message) {
|
public void addErrorMessage(String message) {
|
||||||
doAddMessage(new ErrorMessage(message));
|
doAddMessage(new ErrorMessage(message));
|
||||||
}
|
}
|
||||||
|
@ -280,15 +286,21 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
private static class MessageWrapper {
|
private static class MessageWrapper {
|
||||||
private final StringBuilder message;
|
protected final StringBuilder message;
|
||||||
|
private Color color;
|
||||||
|
private Font font;
|
||||||
|
|
||||||
private MessageWrapper(String message) {
|
private MessageWrapper(String message) {
|
||||||
if (message == null) {
|
Objects.requireNonNull(message, "Attempted to log a null message");
|
||||||
throw new AssertException("Attempted to log a null message.");
|
|
||||||
}
|
|
||||||
this.message = new StringBuilder(message);
|
this.message = new StringBuilder(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MessageWrapper(String message, Font font, Color color) {
|
||||||
|
this(message);
|
||||||
|
this.font = Objects.requireNonNull(font);
|
||||||
|
this.color = Objects.requireNonNull(color);
|
||||||
|
}
|
||||||
|
|
||||||
CharSequence getMessage() {
|
CharSequence getMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
@ -297,13 +309,31 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
|
||||||
if (getClass() != other.getClass()) {
|
if (getClass() != other.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Objects.equals(color, other.color)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
message.append(other.message);
|
message.append(other.message);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AttributeSet getAttributes() {
|
AttributeSet getAttributes() {
|
||||||
|
if (color != null) {
|
||||||
|
GAttributes attrs = new GAttributes(font, color);
|
||||||
|
attrs.addAttribute(CUSTOM_ATTRIBUTE_KEY, OUTPUT_ATTRIBUTE_VALUE);
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
return outputAttributes;
|
return outputAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (color == null) {
|
||||||
|
return message.toString();
|
||||||
|
}
|
||||||
|
return "[color=" + color + "] " + message.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ErrorMessage extends MessageWrapper {
|
private static class ErrorMessage extends MessageWrapper {
|
||||||
|
@ -315,6 +345,10 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
|
||||||
AttributeSet getAttributes() {
|
AttributeSet getAttributes() {
|
||||||
return errorAttributes;
|
return errorAttributes;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[error] " + message.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1199,6 +1199,34 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
||||||
assertTrue("Timed-out waiting for cancelled script to complete", success);
|
assertTrue("Timed-out waiting for cancelled script to complete", success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void runScript(ResourceFile scriptFile) throws Exception {
|
||||||
|
|
||||||
|
GhidraScriptProvider scriptProvider = GhidraScriptUtil.getProvider(scriptFile);
|
||||||
|
GhidraScript script =
|
||||||
|
scriptProvider.getScriptInstance(scriptFile, new PrintWriter(System.err));
|
||||||
|
|
||||||
|
Task task = new RunScriptTask(script, plugin.getCurrentState(), console);
|
||||||
|
task.addTaskListener(provider.getTaskListener());
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
task.addTaskListener(new TaskListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void taskCompleted(Task t) {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void taskCancelled(Task t) {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TaskLauncher.launch(task);
|
||||||
|
|
||||||
|
latch.await(TASK_RUN_SCRIPT_TIMEOUT_SECS, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
protected void startRunScriptTask(GhidraScript script) throws Exception {
|
protected void startRunScriptTask(GhidraScript script) throws Exception {
|
||||||
Task task = new RunScriptTask(script, plugin.getCurrentState(), console);
|
Task task = new RunScriptTask(script, plugin.getCurrentState(), console);
|
||||||
task.addTaskListener(provider.getTaskListener());
|
task.addTaskListener(provider.getTaskListener());
|
||||||
|
|
|
@ -17,9 +17,12 @@ package ghidra.app.plugin.core.script;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
import org.apache.logging.log4j.Level;
|
import org.apache.logging.log4j.Level;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -490,6 +493,113 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
||||||
"*2*", output);
|
"*2*", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScriptPrintWithColor() throws Exception {
|
||||||
|
|
||||||
|
// create a script
|
||||||
|
ResourceFile newScriptFile = createTempScriptFile("LineColoringScript");
|
||||||
|
String filename = newScriptFile.getName();
|
||||||
|
String className = filename.replaceAll("\\.java", "");
|
||||||
|
|
||||||
|
String text1 = "This is black, ";
|
||||||
|
String text2 = "this is blue, and ";
|
||||||
|
String text3 = "this is red.\\n";
|
||||||
|
String line2 = "This is the default color.";
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
String newScript = """
|
||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
public class %s extends GhidraScript {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
|
||||||
|
print("%s");
|
||||||
|
print("%s", Color.BLUE);
|
||||||
|
print("%s", Color.RED);
|
||||||
|
print("%s");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
""".formatted(className, text1, text2, text3, line2);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
writeStringToFile(newScriptFile, newScript);
|
||||||
|
|
||||||
|
runScript(newScriptFile);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertConsoleTextColor(text1, Color.BLACK);
|
||||||
|
assertConsoleTextColor(text2, Color.BLUE);
|
||||||
|
assertConsoleTextColor(text3, Color.RED);
|
||||||
|
assertConsoleTextColor(text2, Color.BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScriptPrintlnWithColor() throws Exception {
|
||||||
|
|
||||||
|
// create a script
|
||||||
|
ResourceFile newScriptFile = createTempScriptFile("LineColoringScript");
|
||||||
|
String filename = newScriptFile.getName();
|
||||||
|
String className = filename.replaceAll("\\.java", "");
|
||||||
|
|
||||||
|
String line1 = "1 This is a default line";
|
||||||
|
String line2 = "2 This is a blue line";
|
||||||
|
String line3 = "3 This is a red line";
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
String newScript = """
|
||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
public class %s extends GhidraScript {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
|
||||||
|
println("%s");
|
||||||
|
println("%s", Color.BLUE);
|
||||||
|
println("%s", Color.RED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
""".formatted(className, line1, line2, line3);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
writeStringToFile(newScriptFile, newScript);
|
||||||
|
|
||||||
|
runScript(newScriptFile);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
assertConsoleTextColor(line1, Color.BLACK);
|
||||||
|
assertConsoleTextColor(line2, Color.BLUE);
|
||||||
|
assertConsoleTextColor(line3, Color.RED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertConsoleTextColor(String text, Color expectedFgColor) {
|
||||||
|
String fullText = runSwing(() -> consoleTextPane.getText());
|
||||||
|
|
||||||
|
// We have 2 layers of newlines in the test. A '\\n' that gets written to file as Java
|
||||||
|
// code. That then gets compiled and written out as a newline. Our 'text' value passed
|
||||||
|
// here is that original '\\n'. We are trying to compare that against what ends up in the
|
||||||
|
// console, which has gone through 2 string interpretations to end up as a standard newline.
|
||||||
|
// Strip off the '\\n' from the original input text before looking for it in the console.
|
||||||
|
String visibleText = text.replaceAll("\\\\n", "");
|
||||||
|
int start = fullText.indexOf(visibleText);
|
||||||
|
int end = visibleText.length();
|
||||||
|
|
||||||
|
runSwing(() -> {
|
||||||
|
StyledDocument styledDocument = (StyledDocument) consoleTextPane.getDocument();
|
||||||
|
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
Element element = styledDocument.getCharacterElement(i);
|
||||||
|
AttributeSet attrs = element.getAttributes();
|
||||||
|
Color actualFgColor = (Color) attrs.getAttribute(StyleConstants.Foreground);
|
||||||
|
assertEquals(expectedFgColor, actualFgColor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Path getBinDirFromScriptFile(ResourceFile sourceFile) {
|
private Path getBinDirFromScriptFile(ResourceFile sourceFile) {
|
||||||
ResourceFile tmpSourceDir = sourceFile.getParentFile();
|
ResourceFile tmpSourceDir = sourceFile.getParentFile();
|
||||||
String tmpSymbolicName = GhidraSourceBundle.sourceDirHash(tmpSourceDir);
|
String tmpSymbolicName = GhidraSourceBundle.sourceDirHash(tmpSourceDir);
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package generic.theme;
|
package generic.theme;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
|
|
||||||
import javax.swing.text.SimpleAttributeSet;
|
import javax.swing.text.SimpleAttributeSet;
|
||||||
|
@ -32,7 +33,7 @@ public class GAttributes extends SimpleAttributeSet {
|
||||||
this(f, null);
|
this(f, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GAttributes(Font f, GColor c) {
|
public GAttributes(Font f, Color c) {
|
||||||
addAttribute(StyleConstants.FontFamily, f.getFamily());
|
addAttribute(StyleConstants.FontFamily, f.getFamily());
|
||||||
addAttribute(StyleConstants.FontSize, f.getSize());
|
addAttribute(StyleConstants.FontSize, f.getSize());
|
||||||
addAttribute(StyleConstants.Bold, f.isBold());
|
addAttribute(StyleConstants.Bold, f.isBold());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue