diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java index a898297c02..87b7887196 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java @@ -17,7 +17,7 @@ package ghidra.app.plugin.core.console; import java.awt.*; import java.awt.event.*; -import java.io.PrintWriter; +import java.io.*; import javax.swing.*; import javax.swing.text.*; @@ -29,6 +29,7 @@ import docking.widgets.FindDialog; import docking.widgets.TextComponentSearcher; import generic.theme.GIcon; import generic.theme.Gui; +import ghidra.app.script.DecoratingPrintWriter; import ghidra.app.services.*; import ghidra.app.util.HelpTopics; import ghidra.framework.main.ConsoleTextPane; @@ -95,8 +96,8 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement } void init() { - stderr = new PrintWriter(new ConsoleWriter(this, true)); - stdin = new PrintWriter(new ConsoleWriter(this, false)); + stderr = new ConsolePrintWriter(true); + stdin = new ConsolePrintWriter(false); /* call this before build() -- we get our Font here */ setVisible(true); @@ -230,6 +231,11 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement textPane.addPartialMessage(msg); } + public void print(String msg, Color c) { + checkVisible(); + textPane.addPartialMessage(msg, c); + } + @Override public void printError(String errmsg) { checkVisible(); @@ -330,6 +336,84 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement // 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 { @Override public void mousePressed(MouseEvent e) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleWriter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleWriter.java deleted file mode 100644 index f408cea578..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleWriter.java +++ /dev/null @@ -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); - } - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java index a8402e26eb..4de7995424 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java @@ -4,9 +4,9 @@ * 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. @@ -35,7 +35,7 @@ import ghidra.app.plugin.core.console.CodeCompletion; */ 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 JTextPane outputTextField; @@ -394,6 +394,10 @@ class CodeCompletionListSelectionModel extends DefaultListSelectionModel { */ class CodeCompletionListCellRenderer extends GListCellRenderer { + CodeCompletionListCellRenderer() { + setBaseFontId(CodeCompletionWindow.FONT_ID); + } + @Override protected String getItemText(CodeCompletion value) { return value.getDescription(); @@ -421,7 +425,6 @@ class CodeCompletionListCellRenderer extends GListCellRenderer { } component.setEnabled(list.isEnabled()); - component.setFont(list.getFont()); component.setComponentOrientation(list.getComponentOrientation()); Border border = null; if (cellHasFocus) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java index 9a2f8cf60e..a23ec0db5c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java @@ -26,11 +26,14 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.*; import javax.swing.text.*; +import org.apache.commons.io.output.WriterOutputStream; + import docking.DockingUtils; import docking.actions.KeyBindingUtils; import generic.theme.*; import generic.util.WindowUtilities; import ghidra.app.plugin.core.console.CodeCompletion; +import ghidra.app.script.DecoratingPrintWriter; import ghidra.framework.options.OptionsChangeListener; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; @@ -69,8 +72,12 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { /* junit */ IPStdin stdin; private OutputStream stdout; private OutputStream stderr; - private PrintWriter outWriter; - private PrintWriter errWriter; + private InterpreterPrintWriter outWriter; + private InterpreterPrintWriter errWriter; + + private AnsiRenderer stdErrRenderer = new AnsiRenderer(); + private AnsiRenderer stdInRenderer = new AnsiRenderer(); + private AnsiRenderer stdOutRenderer = new AnsiRenderer(); private SimpleAttributeSet STDOUT_SET; private SimpleAttributeSet STDERR_SET; @@ -129,11 +136,12 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { outputScrollPane.setFocusable(false); promptTextPane.setFocusable(false); + outWriter = new InterpreterPrintWriter(TextType.STDOUT); + errWriter = new InterpreterPrintWriter(TextType.STDERR); + stdin = new IPStdin(); - stdout = new IPOut(TextType.STDOUT); - stderr = new IPOut(TextType.STDERR); - outWriter = new PrintWriter(stdout, true); - errWriter = new PrintWriter(stderr, true); + stdout = outWriter.asOutputStream(); + stderr = errWriter.asOutputStream(); outputTextPane.setEditable(false); promptTextPane.setEditable(false); @@ -270,7 +278,6 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { private void updateFontAttributes(Font font) { Font boldFont = font.deriveFont(Font.BOLD); - STDOUT_SET = new GAttributes(font, NORMAL_COLOR); STDOUT_SET = new GAttributes(font, NORMAL_COLOR); STDERR_SET = new GAttributes(font, ERROR_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())); } - AnsiRenderer stdErrRenderer = new AnsiRenderer(); - AnsiRenderer stdInRenderer = new AnsiRenderer(); - AnsiRenderer stdOutRenderer = new AnsiRenderer(); - - void addText(String text, TextType type) { + private void addText(String text, TextType type) { SimpleAttributeSet attributes; AnsiRenderer renderer; switch (type) { @@ -438,29 +441,23 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { repositionScrollpane(); } 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 { - TextType type; - byte[] buffer = new byte[1]; + private void addText(String text, Color c) { - IPOut(TextType type) { - this.type = type; + SimpleAttributeSet attributes = new GAttributes(getFont(), c); + + try { + StyledDocument document = outputTextPane.getStyledDocument(); + stdOutRenderer.renderString(document, text, attributes); + repositionScrollpane(); } - - @Override - public void write(int b) throws IOException { - 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); + catch (BadLocationException e) { + // shouldn't happen + Msg.error(this, "Document positioning error", e); } } @@ -565,6 +562,102 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { // 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 * it by a thread, and being read by another thread. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java index cf3adad9eb..04a45aabd7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java @@ -4,9 +4,9 @@ * 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. @@ -487,7 +487,6 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider { // this will overwrite any changes--be sure to resolve that before calling this method! try { loadScript(scriptSourceFile); - fileHash = MD5Utilities.getMD5Hash(scriptSourceFile.getFile(false)); clearChanges(); refreshAction(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/DecoratingPrintWriter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/DecoratingPrintWriter.java new file mode 100644 index 0000000000..35d4542aea --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/DecoratingPrintWriter.java @@ -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); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java index 987ba38f99..53c831b149 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java @@ -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)}. + *

+ * 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)}. + *

+ * 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 * {@code stdout} {@link PrintWriter}, which is set by diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java index a0be99ca14..d89b57a5e9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java @@ -97,7 +97,7 @@ public class ScriptControls { * @param monitor A cancellable monitor */ public ScriptControls(InterpreterConsole console, TaskMonitor monitor) { - this(console.getStdOut(), console.getStdErr(), monitor); + this(console.getOutWriter(), console.getErrWriter(), monitor); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ConsoleService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ConsoleService.java index 4f3cfad23d..9308981536 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ConsoleService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ConsoleService.java @@ -4,9 +4,9 @@ * 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. @@ -15,11 +15,11 @@ */ package ghidra.app.services; +import java.io.PrintWriter; + import ghidra.app.plugin.core.console.ConsolePlugin; import ghidra.framework.plugintool.ServiceInfo; -import java.io.PrintWriter; - /** * Generic console interface allowing any plugin to print * messages to console window. @@ -109,8 +109,6 @@ public interface ConsoleService { * please throw {@link UnsupportedOperationException}. * * @return number of characters >= 0 - * - * @throws UnsupportedOperationException */ public int getTextLength(); @@ -128,8 +126,6 @@ public interface ConsoleService { * @param length the length of the desired string >= 0 * * @return the text, in a String of length >= 0 - * - * @throws UnsupportedOperationException */ public String getText(int offset, int length); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java index 0898a3b0bd..9bc6df8b2f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java @@ -15,8 +15,10 @@ */ package ghidra.framework.main; +import java.awt.Color; import java.awt.Font; import java.util.LinkedList; +import java.util.Objects; import javax.swing.JTextPane; import javax.swing.text.*; @@ -83,6 +85,10 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener doAddMessage(new MessageWrapper(message)); } + public void addPartialMessage(String message, Color c) { + doAddMessage(new MessageWrapper(message, getFont(), c)); + } + public void addErrorMessage(String message) { doAddMessage(new ErrorMessage(message)); } @@ -280,15 +286,21 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener //================================================================================================== private static class MessageWrapper { - private final StringBuilder message; + protected final StringBuilder message; + private Color color; + private Font font; private MessageWrapper(String message) { - if (message == null) { - throw new AssertException("Attempted to log a null message."); - } + Objects.requireNonNull(message, "Attempted to log a null 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() { return message; } @@ -297,13 +309,31 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener if (getClass() != other.getClass()) { return false; } + + if (!Objects.equals(color, other.color)) { + return false; + } + message.append(other.message); return true; } AttributeSet getAttributes() { + if (color != null) { + GAttributes attrs = new GAttributes(font, color); + attrs.addAttribute(CUSTOM_ATTRIBUTE_KEY, OUTPUT_ATTRIBUTE_VALUE); + return attrs; + } return outputAttributes; } + + @Override + public String toString() { + if (color == null) { + return message.toString(); + } + return "[color=" + color + "] " + message.toString(); + } } private static class ErrorMessage extends MessageWrapper { @@ -315,6 +345,10 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener AttributeSet getAttributes() { return errorAttributes; } - } + @Override + public String toString() { + return "[error] " + message.toString(); + } + } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index e5a99e216b..aa3ba83861 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -1199,6 +1199,34 @@ public abstract class AbstractGhidraScriptMgrPluginTest 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 { Task task = new RunScriptTask(script, plugin.getCurrentState(), console); task.addTaskListener(provider.getTaskListener()); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java index f1d9b9a1f4..bcd2ea0ddf 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java @@ -17,9 +17,12 @@ package ghidra.app.plugin.core.script; import static org.junit.Assert.*; +import java.awt.Color; import java.io.*; import java.nio.file.Path; +import javax.swing.text.*; + import org.apache.logging.log4j.Level; import org.junit.Test; @@ -490,6 +493,113 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes "*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) { ResourceFile tmpSourceDir = sourceFile.getParentFile(); String tmpSymbolicName = GhidraSourceBundle.sourceDirHash(tmpSourceDir); diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/GAttributes.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/GAttributes.java index 61f6bce1d2..b14cc8f55e 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/GAttributes.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/GAttributes.java @@ -4,9 +4,9 @@ * 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. @@ -15,6 +15,7 @@ */ package generic.theme; +import java.awt.Color; import java.awt.Font; import javax.swing.text.SimpleAttributeSet; @@ -32,7 +33,7 @@ public class GAttributes extends SimpleAttributeSet { this(f, null); } - public GAttributes(Font f, GColor c) { + public GAttributes(Font f, Color c) { addAttribute(StyleConstants.FontFamily, f.getFamily()); addAttribute(StyleConstants.FontSize, f.getSize()); addAttribute(StyleConstants.Bold, f.isBold());