GP-4637: Preview terminal contents in failure dialog.

This commit is contained in:
Dan 2024-05-30 14:45:21 -04:00
parent 7a83880ddc
commit 0c6fceed61
8 changed files with 116 additions and 26 deletions

View file

@ -17,15 +17,39 @@ package ghidra.debug.api.tracermi;
import java.io.IOException; import java.io.IOException;
import ghidra.app.services.Terminal;
/** /**
* A terminal with some back-end element attached to it * A terminal with some back-end element attached to it
*/ */
public interface TerminalSession extends AutoCloseable { public interface TerminalSession extends AutoCloseable {
@Override @Override
void close() throws IOException; default void close() throws IOException {
terminate();
terminal().close();
}
/**
* The handle to the terminal
*
* @return the handle
*/
Terminal terminal();
/**
* Ensure the session is visible
*
* <p>
* The window should be displayed and brought to the front.
*/
default void show() {
terminal().toFront();
}
/** /**
* Terminate the session without closing the terminal * Terminate the session without closing the terminal
*
* @throws IOException if an I/O issue occurs during termination
*/ */
void terminate() throws IOException; void terminate() throws IOException;
@ -34,7 +58,9 @@ public interface TerminalSession extends AutoCloseable {
* *
* @return true for terminated, false for active * @return true for terminated, false for active
*/ */
boolean isTerminated(); default boolean isTerminated() {
return terminal().isTerminated();
}
/** /**
* Provide a human-readable description of the session * Provide a human-readable description of the session
@ -42,4 +68,22 @@ public interface TerminalSession extends AutoCloseable {
* @return the description * @return the description
*/ */
String description(); String description();
/**
* Get the terminal contents as a string (no attributes)
*
* @return the content
*/
default String content() {
return terminal().getFullText();
}
/**
* Get the current title of the terminal
*
* @return the title
*/
default String title() {
return terminal().getSubTitle();
}
} }

View file

@ -71,6 +71,12 @@ public interface TraceRmiLaunchOffer {
this.exception = exception; this.exception = exception;
} }
public void showTerminals() {
for (TerminalSession session : sessions.values()) {
session.show();
}
}
@Override @Override
public void close() throws Exception { public void close() throws Exception {
if (connection != null) { if (connection != null) {

View file

@ -67,12 +67,6 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
protected record PtyTerminalSession(Terminal terminal, Pty pty, PtySession session, protected record PtyTerminalSession(Terminal terminal, Pty pty, PtySession session,
Thread waiter) implements TerminalSession { Thread waiter) implements TerminalSession {
@Override
public void close() throws IOException {
terminate();
terminal.close();
}
@Override @Override
public void terminate() throws IOException { public void terminate() throws IOException {
terminal.terminated(); terminal.terminated();
@ -81,11 +75,6 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
waiter.interrupt(); waiter.interrupt();
} }
@Override
public boolean isTerminated() {
return terminal.isTerminated();
}
@Override @Override
public String description() { public String description() {
return session.description(); return session.description();
@ -94,23 +83,12 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
protected record NullPtyTerminalSession(Terminal terminal, Pty pty, String name) protected record NullPtyTerminalSession(Terminal terminal, Pty pty, String name)
implements TerminalSession { implements TerminalSession {
@Override
public void close() throws IOException {
terminate();
terminal.close();
}
@Override @Override
public void terminate() throws IOException { public void terminate() throws IOException {
terminal.terminated(); terminal.terminated();
pty.close(); pty.close();
} }
@Override
public boolean isTerminated() {
return terminal.isTerminated();
}
@Override @Override
public String description() { public String description() {
return name; return name;
@ -700,6 +678,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
if (prompt) { if (prompt) {
switch (promptError(result)) { switch (promptError(result)) {
case KEEP: case KEEP:
result.showTerminals();
return result; return result;
case RETRY: case RETRY:
try { try {

View file

@ -15,7 +15,10 @@
*/ */
package ghidra.app.plugin.core.debug.gui.tracermi.launcher; package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import java.util.Arrays;
import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.stream.Collectors;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import ghidra.debug.api.tracermi.TerminalSession; import ghidra.debug.api.tracermi.TerminalSession;
@ -26,7 +29,7 @@ import ghidra.util.HelpLocation;
public class LaunchFailureDialog extends OptionDialog { public class LaunchFailureDialog extends OptionDialog {
private static final String MSGPAT_PART_TOP = """ private static final String MSGPAT_PART_TOP = """
<html><body width="400px"> <html><body width="400px">
<h3>Failed to launch %s due to an exception:</h3> <h3>Failed to launch '%s' due to an exception:</h3>
<tt>%s</tt> <tt>%s</tt>
@ -56,6 +59,7 @@ public class LaunchFailureDialog extends OptionDialog {
"""; """;
private static final String MSGPAT_WITH_RESOURCES = MSGPAT_PART_TOP + MSGPAT_PART_RESOURCES; private static final String MSGPAT_WITH_RESOURCES = MSGPAT_PART_TOP + MSGPAT_PART_RESOURCES;
private static final String MSGPAT_WITHOUT_RESOURCES = MSGPAT_PART_TOP; private static final String MSGPAT_WITHOUT_RESOURCES = MSGPAT_PART_TOP;
private static final int MAX_TERM_CONTENT_LINES = 2;
public enum ErrPromptResponse { public enum ErrPromptResponse {
KEEP, RETRY, TERMINATE; KEEP, RETRY, TERMINATE;
@ -90,6 +94,25 @@ public class LaunchFailureDialog extends OptionDialog {
result.trace() != null; result.trace() != null;
} }
protected static String htmlContent(TerminalSession session) {
String content = session.content().trim();
List<String> lines = Arrays.asList(content.split("\n"));
String note = "";
if (lines.size() >= MAX_TERM_CONTENT_LINES) {
note = "(last %d lines)".formatted(MAX_TERM_CONTENT_LINES);
content = lines.subList(lines.size() - MAX_TERM_CONTENT_LINES, lines.size())
.stream()
.collect(Collectors.joining("\n"));
}
return """
<div style="font:bold;">Title: %s</div>%s
<div style="background:black;color:white;">
<pre>%s</pre>""".formatted(
session.title(),
note,
content);
}
protected static String htmlResources(LaunchResult result) { protected static String htmlResources(LaunchResult result) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (Entry<String, TerminalSession> ent : result.sessions().entrySet()) { for (Entry<String, TerminalSession> ent : result.sessions().entrySet()) {
@ -100,6 +123,9 @@ public class LaunchFailureDialog extends OptionDialog {
if (session.isTerminated()) { if (session.isTerminated()) {
sb.append(" (Terminated)"); sb.append(" (Terminated)");
} }
sb.append("<div>\n");
sb.append(htmlContent(session));
sb.append("</div>");
sb.append("</li>\n"); sb.append("</li>\n");
} }
if (result.acceptor() != null) { if (result.acceptor() != null) {

View file

@ -139,4 +139,9 @@ public class DefaultTerminal implements Terminal {
public void setTerminateAction(Runnable action) { public void setTerminateAction(Runnable action) {
Swing.runIfSwingOrRunLater(() -> provider.setTerminateAction(action)); Swing.runIfSwingOrRunLater(() -> provider.setTerminateAction(action));
} }
@Override
public void toFront() {
provider.toFront();
}
} }

View file

@ -802,7 +802,8 @@ public class VtBuffer {
i++; i++;
} }
} }
for (int i = Math.max(sbSize, startRow); i <= endRow; i++) { int lnSize = lines.size();
for (int i = Math.max(sbSize, startRow); i <= Math.min(endRow, sbSize + lnSize - 1); i++) {
VtLine line = lines.get(i - sbSize); VtLine line = lines.get(i - sbSize);
gatherLineText(buf, startRow, startCol, endRow, endCol, i, line, lineSep); gatherLineText(buf, startRow, startCol, endRow, endCol, i, line, lineSep);
} }

View file

@ -55,6 +55,8 @@ public interface Terminal extends AutoCloseable {
/** /**
* @see #injectDisplayOutput(ByteBuffer) * @see #injectDisplayOutput(ByteBuffer)
*
* @param arr the array of bytes to inject
*/ */
default void injectDisplayOutput(byte[] arr) { default void injectDisplayOutput(byte[] arr) {
injectDisplayOutput(ByteBuffer.wrap(arr)); injectDisplayOutput(ByteBuffer.wrap(arr));
@ -87,6 +89,9 @@ public interface Terminal extends AutoCloseable {
/** /**
* @see #setFixedSize(short, short) * @see #setFixedSize(short, short)
*
* @param cols the number of columns
* @param rows the number of rows
*/ */
default void setFixedSize(int cols, int rows) { default void setFixedSize(int cols, int rows) {
setFixedSize((short) cols, (short) rows); setFixedSize((short) cols, (short) rows);
@ -102,6 +107,8 @@ public interface Terminal extends AutoCloseable {
* *
* <p> * <p>
* This only affects the primary buffer. The alternate buffer has no scroll-back. * This only affects the primary buffer. The alternate buffer has no scroll-back.
*
* @param rows the number of scroll-back rows
*/ */
void setMaxScrollBackRows(int rows); void setMaxScrollBackRows(int rows);
@ -210,4 +217,9 @@ public interface Terminal extends AutoCloseable {
* @return true for terminated, false for active * @return true for terminated, false for active
*/ */
boolean isTerminated(); boolean isTerminated();
/**
* Bring the terminal to the front of the UI
*/
void toFront();
} }

View file

@ -482,6 +482,23 @@ public class TerminalProviderTest extends AbstractGhidraHeadedDebuggerTest {
} }
} }
@Test
@SuppressWarnings("resource")
public void testGetFullText() throws Exception {
terminalService = addPlugin(tool, TerminalPlugin.class);
try (DefaultTerminal term = (DefaultTerminal) terminalService
.createNullTerminal(Charset.forName("UTF-8"), buf -> {
})) {
term.setFixedSize(80, 25);
term.injectDisplayOutput(TEST_CONTENTS);
assertEquals("""
term Term
noterm""", term.getFullText().trim());
}
}
protected String csi(char f, int... params) { protected String csi(char f, int... params) {
return "\033[" + return "\033[" +
IntStream.of(params).mapToObj(Integer::toString).collect(Collectors.joining(";")) + f; IntStream.of(params).mapToObj(Integer::toString).collect(Collectors.joining(";")) + f;