mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-4637: Preview terminal contents in failure dialog.
This commit is contained in:
parent
7a83880ddc
commit
0c6fceed61
8 changed files with 116 additions and 26 deletions
|
@ -17,15 +17,39 @@ package ghidra.debug.api.tracermi;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.services.Terminal;
|
||||
|
||||
/**
|
||||
* A terminal with some back-end element attached to it
|
||||
*/
|
||||
public interface TerminalSession extends AutoCloseable {
|
||||
@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
|
||||
*
|
||||
* @throws IOException if an I/O issue occurs during termination
|
||||
*/
|
||||
void terminate() throws IOException;
|
||||
|
||||
|
@ -34,7 +58,9 @@ public interface TerminalSession extends AutoCloseable {
|
|||
*
|
||||
* @return true for terminated, false for active
|
||||
*/
|
||||
boolean isTerminated();
|
||||
default boolean isTerminated() {
|
||||
return terminal().isTerminated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a human-readable description of the session
|
||||
|
@ -42,4 +68,22 @@ public interface TerminalSession extends AutoCloseable {
|
|||
* @return the 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,12 @@ public interface TraceRmiLaunchOffer {
|
|||
this.exception = exception;
|
||||
}
|
||||
|
||||
public void showTerminals() {
|
||||
for (TerminalSession session : sessions.values()) {
|
||||
session.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (connection != null) {
|
||||
|
|
|
@ -67,12 +67,6 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
|
||||
protected record PtyTerminalSession(Terminal terminal, Pty pty, PtySession session,
|
||||
Thread waiter) implements TerminalSession {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
terminate();
|
||||
terminal.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() throws IOException {
|
||||
terminal.terminated();
|
||||
|
@ -81,11 +75,6 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
waiter.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return terminal.isTerminated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return session.description();
|
||||
|
@ -94,23 +83,12 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
|
||||
protected record NullPtyTerminalSession(Terminal terminal, Pty pty, String name)
|
||||
implements TerminalSession {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
terminate();
|
||||
terminal.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() throws IOException {
|
||||
terminal.terminated();
|
||||
pty.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return terminal.isTerminated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return name;
|
||||
|
@ -700,6 +678,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
|||
if (prompt) {
|
||||
switch (promptError(result)) {
|
||||
case KEEP:
|
||||
result.showTerminals();
|
||||
return result;
|
||||
case RETRY:
|
||||
try {
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
*/
|
||||
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.stream.Collectors;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.debug.api.tracermi.TerminalSession;
|
||||
|
@ -26,7 +29,7 @@ import ghidra.util.HelpLocation;
|
|||
public class LaunchFailureDialog extends OptionDialog {
|
||||
private static final String MSGPAT_PART_TOP = """
|
||||
<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>
|
||||
|
||||
|
@ -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_WITHOUT_RESOURCES = MSGPAT_PART_TOP;
|
||||
private static final int MAX_TERM_CONTENT_LINES = 2;
|
||||
|
||||
public enum ErrPromptResponse {
|
||||
KEEP, RETRY, TERMINATE;
|
||||
|
@ -90,6 +94,25 @@ public class LaunchFailureDialog extends OptionDialog {
|
|||
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) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Entry<String, TerminalSession> ent : result.sessions().entrySet()) {
|
||||
|
@ -100,6 +123,9 @@ public class LaunchFailureDialog extends OptionDialog {
|
|||
if (session.isTerminated()) {
|
||||
sb.append(" (Terminated)");
|
||||
}
|
||||
sb.append("<div>\n");
|
||||
sb.append(htmlContent(session));
|
||||
sb.append("</div>");
|
||||
sb.append("</li>\n");
|
||||
}
|
||||
if (result.acceptor() != null) {
|
||||
|
|
|
@ -139,4 +139,9 @@ public class DefaultTerminal implements Terminal {
|
|||
public void setTerminateAction(Runnable action) {
|
||||
Swing.runIfSwingOrRunLater(() -> provider.setTerminateAction(action));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toFront() {
|
||||
provider.toFront();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -802,7 +802,8 @@ public class VtBuffer {
|
|||
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);
|
||||
gatherLineText(buf, startRow, startCol, endRow, endCol, i, line, lineSep);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@ public interface Terminal extends AutoCloseable {
|
|||
|
||||
/**
|
||||
* @see #injectDisplayOutput(ByteBuffer)
|
||||
*
|
||||
* @param arr the array of bytes to inject
|
||||
*/
|
||||
default void injectDisplayOutput(byte[] arr) {
|
||||
injectDisplayOutput(ByteBuffer.wrap(arr));
|
||||
|
@ -87,6 +89,9 @@ public interface Terminal extends AutoCloseable {
|
|||
|
||||
/**
|
||||
* @see #setFixedSize(short, short)
|
||||
*
|
||||
* @param cols the number of columns
|
||||
* @param rows the number of rows
|
||||
*/
|
||||
default void setFixedSize(int cols, int rows) {
|
||||
setFixedSize((short) cols, (short) rows);
|
||||
|
@ -102,6 +107,8 @@ public interface Terminal extends AutoCloseable {
|
|||
*
|
||||
* <p>
|
||||
* 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);
|
||||
|
||||
|
@ -210,4 +217,9 @@ public interface Terminal extends AutoCloseable {
|
|||
* @return true for terminated, false for active
|
||||
*/
|
||||
boolean isTerminated();
|
||||
|
||||
/**
|
||||
* Bring the terminal to the front of the UI
|
||||
*/
|
||||
void toFront();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
return "\033[" +
|
||||
IntStream.of(params).mapToObj(Integer::toString).collect(Collectors.joining(";")) + f;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue