GP-4289 - added option to wrap operands on semicolons to put sub-instructions on their own line within the operand field

This commit is contained in:
ghidragon
2024-04-04 11:38:55 -04:00
parent 5bf2f6b9ca
commit 60cd82e471
20 changed files with 221 additions and 138 deletions
@@ -932,7 +932,12 @@
from the <I><A href="#Listing_Display">Listing Display</A></I> options panel; select from the <I><A href="#Listing_Display">Listing Display</A></I> options panel; select
<I>Underline</I> from the <I>Screen Element</I> list.</P> <I>Underline</I> from the <I>Screen Element</I> list.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
</BLOCKQUOTE> <P><B>Wrap on Semicolons -</B> Option to wrap operand fields on semicolons. Some processors
have multiple sub instructions encoded at the same address. Normally, these are shown on
one line and the additional instructions are all shown within the operand field and
separated by semicolons. With this option on, each follow on instruction will be displayed
on its own line within the operand field.
</P> </BLOCKQUOTE>
<H3><A name="Pcode_Field"></A>PCode Field</H3> <H3><A name="Pcode_Field"></A>PCode Field</H3>
@@ -55,7 +55,8 @@ public class PcodeFieldFactory extends FieldFactory {
} }
public PcodeFieldFactory(String name, FieldFormatModel model, public PcodeFieldFactory(String name, FieldFormatModel model,
ListingHighlightProvider highlightProvider, Options displayOptions, Options fieldOptions) { ListingHighlightProvider highlightProvider, Options displayOptions,
Options fieldOptions) {
super(name, model, highlightProvider, displayOptions, fieldOptions); super(name, model, highlightProvider, displayOptions, fieldOptions);
setWidth(300); setWidth(300);
@@ -67,7 +68,8 @@ public class PcodeFieldFactory extends FieldFactory {
} }
@Override @Override
public FieldFactory newInstance(FieldFormatModel myModel, ListingHighlightProvider highlightProvider, public FieldFactory newInstance(FieldFormatModel myModel,
ListingHighlightProvider highlightProvider,
ToolOptions options, ToolOptions fieldOptions) { ToolOptions options, ToolOptions fieldOptions) {
return new PcodeFieldFactory(FIELD_NAME, myModel, highlightProvider, options, fieldOptions); return new PcodeFieldFactory(FIELD_NAME, myModel, highlightProvider, options, fieldOptions);
} }
@@ -81,7 +83,7 @@ public class PcodeFieldFactory extends FieldFactory {
} }
Instruction instr = (Instruction) obj; Instruction instr = (Instruction) obj;
ArrayList<TextFieldElement> elements = new ArrayList<>(); List<FieldElement> elements = new ArrayList<>();
List<AttributedString> pcodeListing = formatter.formatOps(instr.getProgram().getLanguage(), List<AttributedString> pcodeListing = formatter.formatOps(instr.getProgram().getLanguage(),
instr.getProgram().getAddressFactory(), Arrays.asList(instr.getPcode(true))); instr.getProgram().getAddressFactory(), Arrays.asList(instr.getPcode(true)));
@@ -91,9 +93,8 @@ public class PcodeFieldFactory extends FieldFactory {
} }
if (elements.size() > 0) { if (elements.size() > 0) {
FieldElement[] textElements = elements.toArray(new FieldElement[elements.size()]); return ListingTextField.createMultilineTextField(this, proxy, elements,
return ListingTextField.createMultilineTextField(this, proxy, textElements, startX + varWidth, width, hlProvider);
startX + varWidth, width, Integer.MAX_VALUE, hlProvider);
} }
return null; return null;
} }
@@ -50,7 +50,8 @@ public class AssignedVariableFieldFactory extends FieldFactory {
* @param displayOptions the Options for display properties. * @param displayOptions the Options for display properties.
* @param fieldOptions the Options for field specific properties. * @param fieldOptions the Options for field specific properties.
*/ */
private AssignedVariableFieldFactory(FieldFormatModel model, ListingHighlightProvider hsProvider, private AssignedVariableFieldFactory(FieldFormatModel model,
ListingHighlightProvider hsProvider,
Options displayOptions, Options fieldOptions) { Options displayOptions, Options fieldOptions) {
super(FIELD_NAME, model, hsProvider, displayOptions, fieldOptions); super(FIELD_NAME, model, hsProvider, displayOptions, fieldOptions);
} }
@@ -70,7 +71,7 @@ public class AssignedVariableFieldFactory extends FieldFactory {
} }
CodeUnit cu = (CodeUnit) obj; CodeUnit cu = (CodeUnit) obj;
ArrayList<FieldElement> elemenetList = new ArrayList<>(); ArrayList<FieldElement> elements = new ArrayList<>();
Function f = cu.getProgram().getFunctionManager().getFunctionContaining(cu.getMinAddress()); Function f = cu.getProgram().getFunctionManager().getFunctionContaining(cu.getMinAddress());
if (f != null) { if (f != null) {
@@ -89,19 +90,16 @@ public class AssignedVariableFieldFactory extends FieldFactory {
buf.append(var.getName()); buf.append(var.getName());
AttributedString as = new AttributedString(buf.toString(), AttributedString as = new AttributedString(buf.toString(),
FunctionColors.VARIABLE_ASSIGNED, getMetrics()); FunctionColors.VARIABLE_ASSIGNED, getMetrics());
elemenetList.add(new TextFieldElement(as, 0, 0)); elements.add(new TextFieldElement(as, 0, 0));
} }
} }
} }
if (elemenetList.size() == 0) { if (elements.isEmpty()) {
return null; return null;
} }
FieldElement[] elements = new FieldElement[elemenetList.size()];
elemenetList.toArray(elements);
return ListingTextField.createMultilineTextField(this, proxy, elements, startX + varWidth, return ListingTextField.createMultilineTextField(this, proxy, elements, startX + varWidth,
width, elements.length + 1, hlProvider); width, hlProvider);
} }
@Override @Override
@@ -134,7 +132,8 @@ public class AssignedVariableFieldFactory extends FieldFactory {
} }
@Override @Override
public FieldFactory newInstance(FieldFormatModel formatModel, ListingHighlightProvider hsProvider, public FieldFactory newInstance(FieldFormatModel formatModel,
ListingHighlightProvider hsProvider,
ToolOptions displayOptions, ToolOptions fieldOptions) { ToolOptions displayOptions, ToolOptions fieldOptions) {
return new AssignedVariableFieldFactory(formatModel, hsProvider, displayOptions, return new AssignedVariableFieldFactory(formatModel, hsProvider, displayOptions,
fieldOptions); fieldOptions);
@@ -292,11 +292,7 @@ public class EolCommentFieldFactory extends FieldFactory {
elementList.addAll(elements); elementList.addAll(elements);
} }
FieldElement[] fieldElements = elementList.toArray(new FieldElement[elementList.size()]); return ListingTextField.createMultilineTextField(this, proxy, elementList, x, width,
if (fieldElements.length == 0) {
return null;
}
return ListingTextField.createMultilineTextField(this, proxy, fieldElements, x, width,
maxDisplayLines, hlProvider); maxDisplayLines, hlProvider);
} }
@@ -16,6 +16,8 @@
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.fieldpanel.field.AttributedString; import docking.widgets.fieldpanel.field.AttributedString;
import docking.widgets.fieldpanel.field.FieldElement; import docking.widgets.fieldpanel.field.FieldElement;
@@ -70,16 +72,16 @@ public class FunctionRepeatableCommentFieldFactory extends FieldFactory {
Function f = (Function) obj; Function f = (Function) obj;
Program program = f.getProgram(); Program program = f.getProgram();
String[] commentArr = f.getRepeatableCommentAsArray(); String[] commentArr = f.getRepeatableCommentAsArray();
FieldElement[] fields = new FieldElement[commentArr.length]; List<FieldElement> fields = new ArrayList<>();
AttributedString prototype = AttributedString prototype =
new AttributedString("prototype", CommentColors.REPEATABLE, getMetrics()); new AttributedString("prototype", CommentColors.REPEATABLE, getMetrics());
for (int i = 0; i < commentArr.length; i++) { for (int i = 0; i < commentArr.length; i++) {
fields[i] = CommentUtils.parseTextForAnnotations(commentArr[i], program, prototype, i); fields.add(CommentUtils.parseTextForAnnotations(commentArr[i], program, prototype, i));
} }
if (commentArr.length > 0) { if (commentArr.length > 0) {
return ListingTextField.createMultilineTextField(this, proxy, fields, x, width, return ListingTextField.createMultilineTextField(this, proxy, fields, x, width,
Integer.MAX_VALUE, hlProvider); hlProvider);
} }
return null; return null;
} }
@@ -17,6 +17,8 @@ package ghidra.app.util.viewer.field;
import java.awt.Color; import java.awt.Color;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation; import docking.widgets.fieldpanel.support.FieldLocation;
@@ -52,7 +54,8 @@ public class InstructionMaskValueFieldFactory extends FieldFactory {
* @param displayOptions the Options for display properties. * @param displayOptions the Options for display properties.
* @param fieldOptions the Options for field specific properties. * @param fieldOptions the Options for field specific properties.
*/ */
private InstructionMaskValueFieldFactory(FieldFormatModel model, ListingHighlightProvider hsProvider, private InstructionMaskValueFieldFactory(FieldFormatModel model,
ListingHighlightProvider hsProvider,
Options displayOptions, Options fieldOptions) { Options displayOptions, Options fieldOptions) {
super(FIELD_NAME, model, hsProvider, displayOptions, fieldOptions); super(FIELD_NAME, model, hsProvider, displayOptions, fieldOptions);
} }
@@ -88,21 +91,21 @@ public class InstructionMaskValueFieldFactory extends FieldFactory {
} }
try { try {
FieldElement[] fieldElements = new FieldElement[2 * (operandCount + 1)]; List<FieldElement> elements = new ArrayList<>();
fieldElements[0] = elements.add(
getLine("M[m]: ", instructionMask.getBytes(), MaskColors.BITS, proxy, varWidth); getLine("M[m]: ", instructionMask.getBytes(), MaskColors.BITS, proxy, varWidth));
fieldElements[1] = elements.add(getLine("V[m]: ", instructionMask.applyMask(instr), MaskColors.VALUE,
getLine("V[m]: ", instructionMask.applyMask(instr), MaskColors.VALUE, proxy, proxy, varWidth));
varWidth);
for (int i = 0; i < operandCount; i++) { for (int i = 0; i < operandCount; i++) {
fieldElements[2 * (i + 1)] = getLine("M[" + i + "]: ", operandMasks[i].getBytes(), elements.add(getLine("M[" + i + "]: ", operandMasks[i].getBytes(), MaskColors.BITS,
MaskColors.BITS, proxy, varWidth); proxy, varWidth));
fieldElements[2 * (i + 1) + 1] = getLine("V[" + i + "]: ", elements.add(getLine("V[" + i + "]: ", operandMasks[i].applyMask(instr),
operandMasks[i].applyMask(instr), MaskColors.VALUE, proxy, varWidth); MaskColors.VALUE, proxy, varWidth));
} }
return ListingTextField.createMultilineTextField(this, proxy, fieldElements, return ListingTextField.createMultilineTextField(this, proxy, elements,
startX + varWidth, width, fieldElements.length, hlProvider); startX + varWidth, width, hlProvider);
} }
catch (MemoryAccessException e) { catch (MemoryAccessException e) {
return null; return null;
@@ -165,7 +168,8 @@ public class InstructionMaskValueFieldFactory extends FieldFactory {
} }
@Override @Override
public FieldFactory newInstance(FieldFormatModel formatModel, ListingHighlightProvider hsProvider, public FieldFactory newInstance(FieldFormatModel formatModel,
ListingHighlightProvider hsProvider,
ToolOptions toolOptions, ToolOptions fieldOptions) { ToolOptions toolOptions, ToolOptions fieldOptions) {
return new InstructionMaskValueFieldFactory(formatModel, hsProvider, toolOptions, return new InstructionMaskValueFieldFactory(formatModel, hsProvider, toolOptions,
fieldOptions); fieldOptions);
@@ -181,8 +181,7 @@ public class LabelFieldFactory extends FieldFactory {
return null; return null;
} }
FieldElement[] textElements = new FieldElement[length]; List<FieldElement> elements = new ArrayList<>(length);
int nextPos = 0;
if (hasOffcuts) { if (hasOffcuts) {
for (Address offcut : offcuts) { for (Address offcut : offcuts) {
@@ -193,7 +192,7 @@ public class LabelFieldFactory extends FieldFactory {
inspector.getOffcutSymbolColor(), inspector.getOffcutSymbolColor(),
getMetrics(inspector.getOffcutSymbolStyle()), false, null); getMetrics(inspector.getOffcutSymbolStyle()), false, null);
} }
textElements[nextPos++] = new TextFieldElement(as, nextPos, 0); elements.add(new TextFieldElement(as, elements.size(), 0));
} }
} }
@@ -206,11 +205,11 @@ public class LabelFieldFactory extends FieldFactory {
ColorAndStyle c = inspector.getColorAndStyle(symbol); ColorAndStyle c = inspector.getColorAndStyle(symbol);
AttributedString as = new AttributedString(icon, checkLabelString(symbol, prog), AttributedString as = new AttributedString(icon, checkLabelString(symbol, prog),
c.getColor(), getMetrics(c.getStyle()), false, null); c.getColor(), getMetrics(c.getStyle()), false, null);
textElements[nextPos++] = new TextFieldElement(as, nextPos, 0); elements.add(new TextFieldElement(as, elements.size(), 0));
} }
return ListingTextField.createMultilineTextField(this, proxy, textElements, x, width, return ListingTextField.createMultilineTextField(this, proxy, elements, x, width,
Integer.MAX_VALUE, hlProvider); hlProvider);
} }
private String getOffsetText(CodeUnit cu, Address currAddr, Address offcutAddress) { private String getOffsetText(CodeUnit cu, Address currAddr, Address offcutAddress) {
@@ -125,10 +125,10 @@ public class ListingTextField implements ListingField, TextField {
} }
/** /**
* Displays the given array of text, each on its own line. * Displays the given List of text elements, each on its own line.
* @param factory the field factory that generated this field * @param factory the field factory that generated this field
* @param proxy the object used to populate this field * @param proxy the object used to populate this field
* @param textElements the array of elements for the field. * @param textElements the list of text elements
* Each of these holds text, attributes and location information. * Each of these holds text, attributes and location information.
* @param startX the starting X position of the field * @param startX the starting X position of the field
* @param width the width of the field * @param width the width of the field
@@ -137,18 +137,36 @@ public class ListingTextField implements ListingField, TextField {
* @return the text field. * @return the text field.
*/ */
public static ListingTextField createMultilineTextField(FieldFactory factory, ProxyObj<?> proxy, public static ListingTextField createMultilineTextField(FieldFactory factory, ProxyObj<?> proxy,
FieldElement[] textElements, int startX, int width, int maxLines, List<FieldElement> textElements, int startX, int width, int maxLines,
ListingHighlightProvider provider) { ListingHighlightProvider provider) {
ListingFieldHighlightFactoryAdapter hlFactory = ListingFieldHighlightFactoryAdapter hlFactory =
new ListingFieldHighlightFactoryAdapter(provider); new ListingFieldHighlightFactoryAdapter(provider);
List<FieldElement> list = Arrays.asList(textElements);
TextField field = TextField field =
new VerticalLayoutTextField(list, startX, width, maxLines, hlFactory); new VerticalLayoutTextField(textElements, startX, width, maxLines, hlFactory);
ListingTextField listingField = new ListingTextField(factory, proxy, field, hlFactory); ListingTextField listingField = new ListingTextField(factory, proxy, field, hlFactory);
return listingField; return listingField;
} }
/**
* Displays the given List of text elements, each on its own line with no max line restriction
* @param factory the field factory that generated this field
* @param proxy the object used to populate this field
* @param textElements the list of text elements
* Each of these holds text, attributes and location information.
* @param startX the starting X position of the field
* @param width the width of the field
* @param provider the highlight provider
* @return the text field.
*/
public static ListingTextField createMultilineTextField(FieldFactory factory, ProxyObj<?> proxy,
List<FieldElement> textElements, int startX, int width,
ListingHighlightProvider provider) {
return ListingTextField.createMultilineTextField(factory, proxy, textElements, startX,
width, Integer.MAX_VALUE, provider);
}
protected ListingTextField(FieldFactory factory, ProxyObj<?> proxy, TextField field, protected ListingTextField(FieldFactory factory, ProxyObj<?> proxy, TextField field,
ListingFieldHighlightFactoryAdapter hlFactory) { ListingFieldHighlightFactoryAdapter hlFactory) {
this.factory = factory; this.factory = factory;
@@ -56,7 +56,8 @@ public class MemoryBlockStartFieldFactory extends FieldFactory {
* @param displayOptions the Options for display properties. * @param displayOptions the Options for display properties.
* @param fieldOptions the Options for field specific properties. * @param fieldOptions the Options for field specific properties.
*/ */
private MemoryBlockStartFieldFactory(FieldFormatModel model, ListingHighlightProvider hlProvider, private MemoryBlockStartFieldFactory(FieldFormatModel model,
ListingHighlightProvider hlProvider,
Options displayOptions, Options fieldOptions) { Options displayOptions, Options fieldOptions) {
super(FIELD_NAME, model, hlProvider, displayOptions, fieldOptions); super(FIELD_NAME, model, hlProvider, displayOptions, fieldOptions);
} }
@@ -85,7 +86,7 @@ public class MemoryBlockStartFieldFactory extends FieldFactory {
} }
// Convert the text to field elements. // Convert the text to field elements.
FieldElement[] elements = createFieldElements(attributedStrings); List<FieldElement> elements = createFieldElements(attributedStrings);
// And put the elements in a text field. // And put the elements in a text field.
ListingTextField ltf = ListingTextField.createMultilineTextField(this, proxy, elements, ListingTextField ltf = ListingTextField.createMultilineTextField(this, proxy, elements,
@@ -227,7 +228,7 @@ public class MemoryBlockStartFieldFactory extends FieldFactory {
return lines; return lines;
} }
private FieldElement[] createFieldElements(List<AttributedString> attributedStrings) { private List<FieldElement> createFieldElements(List<AttributedString> attributedStrings) {
List<FieldElement> elements = new ArrayList<>(); List<FieldElement> elements = new ArrayList<>();
int lineNum = 0; int lineNum = 0;
for (AttributedString line : attributedStrings) { for (AttributedString line : attributedStrings) {
@@ -235,11 +236,6 @@ public class MemoryBlockStartFieldFactory extends FieldFactory {
elements.add(blockElement); elements.add(blockElement);
lineNum++; lineNum++;
} }
return elements;
// Convert to an array
FieldElement[] elementsArray = new FieldElement[elements.size()];
elements.toArray(elementsArray);
return elementsArray;
} }
} }
@@ -89,7 +89,8 @@ public class OperandFieldFactory extends OperandFieldHelper {
} }
@Override @Override
public FieldFactory newInstance(FieldFormatModel formatModel, ListingHighlightProvider hsProvider, public FieldFactory newInstance(FieldFormatModel formatModel,
ListingHighlightProvider hsProvider,
ToolOptions displayOptions, ToolOptions fieldOptions) { ToolOptions displayOptions, ToolOptions fieldOptions) {
return new OperandFieldFactory(formatModel, hsProvider, displayOptions, fieldOptions); return new OperandFieldFactory(formatModel, hsProvider, displayOptions, fieldOptions);
} }
@@ -48,14 +48,17 @@ import ghidra.util.HelpLocation;
*/ */
abstract class OperandFieldHelper extends FieldFactory { abstract class OperandFieldHelper extends FieldFactory {
private final static String ENABLE_WORD_WRAP_MSG = private final static String ENABLE_WORD_WRAP_OPTION =
GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + FieldUtils.WORD_WRAP_OPTION_NAME; GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + FieldUtils.WORD_WRAP_OPTION_NAME;
private final static String MAX_DISPLAY_LINES_MSG = private final static String MAX_DISPLAY_LINES_OPTION =
GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + "Maximum Lines To Display"; GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + "Maximum Lines To Display";
private final static String UNDERLINE_OPTION = private final static String UNDERLINE_OPTION =
GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + "Underline References"; GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + "Underline References";
private final static String SPACE_AFTER_SEPARATOR_OPTION = private final static String SPACE_AFTER_SEPARATOR_OPTION =
GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + "Add Space After Separator"; GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + "Add Space After Separator";
private final static String WRAP_ON_SEMICOLON_OPTION =
GhidraOptions.OPERAND_GROUP_TITLE + Options.DELIMITER + "Wrap on Semicolons";
private final static OperandFieldElement LINE_BREAK = new OperandFieldElement(null, 0, 0, 0);
public enum UNDERLINE_CHOICE { public enum UNDERLINE_CHOICE {
Hidden, All, None Hidden, All, None
@@ -75,6 +78,7 @@ abstract class OperandFieldHelper extends FieldFactory {
private boolean isWordWrap = false; private boolean isWordWrap = false;
private int maxDisplayLines = 2; private int maxDisplayLines = 2;
private boolean spaceAfterSeparator = false; private boolean spaceAfterSeparator = false;
private boolean wrapOnSemicolon = false;
protected BrowserCodeUnitFormat codeUnitFormat; protected BrowserCodeUnitFormat codeUnitFormat;
private ChangeListener codeUnitFormatListener = e -> OperandFieldHelper.this.model.update(); private ChangeListener codeUnitFormatListener = e -> OperandFieldHelper.this.model.update();
@@ -102,9 +106,9 @@ abstract class OperandFieldHelper extends FieldFactory {
setOptions(displayOptions); setOptions(displayOptions);
HelpLocation hl = new HelpLocation("CodeBrowserPlugin", "Operands_Field"); HelpLocation hl = new HelpLocation("CodeBrowserPlugin", "Operands_Field");
fieldOptions.registerOption(ENABLE_WORD_WRAP_MSG, false, hl, fieldOptions.registerOption(ENABLE_WORD_WRAP_OPTION, false, hl,
"Enables word wrapping of strings in the operands field."); "Enables word wrapping of strings in the operands field.");
fieldOptions.registerOption(MAX_DISPLAY_LINES_MSG, 2, hl, fieldOptions.registerOption(MAX_DISPLAY_LINES_OPTION, 2, hl,
"The maximum number of lines used to display the strings in the operands field."); "The maximum number of lines used to display the strings in the operands field.");
fieldOptions.registerOption(UNDERLINE_OPTION, UNDERLINE_CHOICE.Hidden, hl, fieldOptions.registerOption(UNDERLINE_OPTION, UNDERLINE_CHOICE.Hidden, hl,
"Select 'All' to underline any operand field that has a reference; " + "Select 'All' to underline any operand field that has a reference; " +
@@ -112,16 +116,16 @@ abstract class OperandFieldHelper extends FieldFactory {
"select 'None' for no underlines."); "select 'None' for no underlines.");
fieldOptions.registerOption(SPACE_AFTER_SEPARATOR_OPTION, false, hl, fieldOptions.registerOption(SPACE_AFTER_SEPARATOR_OPTION, false, hl,
"Add space between separator and next operand"); "Add space between separator and next operand");
fieldOptions.registerOption(WRAP_ON_SEMICOLON_OPTION, false, hl,
"Wrap operand field on semicolons");
setMaximumLinesToDisplay(fieldOptions.getInt(MAX_DISPLAY_LINES_MSG, 2), fieldOptions); setMaximumLinesToDisplay(fieldOptions.getInt(MAX_DISPLAY_LINES_OPTION, 2), fieldOptions);
isWordWrap = fieldOptions.getBoolean(ENABLE_WORD_WRAP_MSG, false); isWordWrap = fieldOptions.getBoolean(ENABLE_WORD_WRAP_OPTION, false);
underlineChoice = fieldOptions.getEnum(UNDERLINE_OPTION, UNDERLINE_CHOICE.Hidden); underlineChoice = fieldOptions.getEnum(UNDERLINE_OPTION, UNDERLINE_CHOICE.Hidden);
spaceAfterSeparator = fieldOptions.getBoolean(SPACE_AFTER_SEPARATOR_OPTION, false); spaceAfterSeparator = fieldOptions.getBoolean(SPACE_AFTER_SEPARATOR_OPTION, false);
wrapOnSemicolon = fieldOptions.getBoolean(WRAP_ON_SEMICOLON_OPTION, false);
inspector = new SymbolInspector(displayOptions, null); inspector = new SymbolInspector(displayOptions, null);
fieldOptions.getOptions(GhidraOptions.OPERAND_GROUP_TITLE).setOptionsHelpLocation(hl);
// Create code unit format and associated options - listen for changes // Create code unit format and associated options - listen for changes
codeUnitFormat = new BrowserCodeUnitFormat(fieldOptions, true); codeUnitFormat = new BrowserCodeUnitFormat(fieldOptions, true);
@@ -141,22 +145,29 @@ abstract class OperandFieldHelper extends FieldFactory {
Object newValue) { Object newValue) {
boolean updateModel = false; boolean updateModel = false;
if (optionName.equals(MAX_DISPLAY_LINES_MSG)) { switch (optionName) {
setMaximumLinesToDisplay(((Integer) newValue).intValue(), options); case MAX_DISPLAY_LINES_OPTION:
updateModel = true; setMaximumLinesToDisplay(((Integer) newValue).intValue(), options);
} updateModel = true;
else if (optionName.equals(ENABLE_WORD_WRAP_MSG)) { break;
isWordWrap = ((Boolean) newValue).booleanValue(); case ENABLE_WORD_WRAP_OPTION:
updateModel = true; isWordWrap = ((Boolean) newValue).booleanValue();
} updateModel = true;
else if (optionName.equals(UNDERLINE_OPTION)) { break;
underlineChoice = (UNDERLINE_CHOICE) newValue; case UNDERLINE_OPTION:
updateModel = true; underlineChoice = (UNDERLINE_CHOICE) newValue;
} updateModel = true;
else if (optionName.equals(SPACE_AFTER_SEPARATOR_OPTION)) { break;
spaceAfterSeparator = ((Boolean) newValue).booleanValue(); case SPACE_AFTER_SEPARATOR_OPTION:
updateModel = true; spaceAfterSeparator = ((Boolean) newValue).booleanValue();
updateModel = true;
break;
case WRAP_ON_SEMICOLON_OPTION:
wrapOnSemicolon = ((Boolean) newValue).booleanValue();
updateModel = true;
break;
} }
if (updateModel) { if (updateModel) {
model.update(); model.update();
} }
@@ -165,19 +176,18 @@ abstract class OperandFieldHelper extends FieldFactory {
private void setMaximumLinesToDisplay(int maxLines, Options options) { private void setMaximumLinesToDisplay(int maxLines, Options options) {
if (maxLines < 1) { if (maxLines < 1) {
maxLines = 1; maxLines = 1;
options.setInt(MAX_DISPLAY_LINES_MSG, maxLines); options.setInt(MAX_DISPLAY_LINES_OPTION, maxLines);
} }
this.maxDisplayLines = maxLines; this.maxDisplayLines = maxLines;
} }
FieldLocation getFieldLocation(BigInteger index, int fieldNum, ListingField bf, int opIndex, FieldLocation getFieldLocation(BigInteger index, int fieldNum, ListingField field,
int column) { int opIndex, int column) {
if (bf instanceof ListingTextField) { if (field instanceof ListingTextField listingField) {
ListingTextField btf = (ListingTextField) bf; RowColLocation rcl = listingField.dataToScreenLocation(opIndex, column);
RowColLocation rcl = btf.dataToScreenLocation(opIndex, column);
return new FieldLocation(index, fieldNum, rcl.row(), rcl.col()); return new FieldLocation(index, fieldNum, rcl.row(), rcl.col());
} }
else if (bf instanceof ImageFactoryField) { else if (field instanceof ImageFactoryField) {
return new FieldLocation(index, fieldNum, 0, 0); return new FieldLocation(index, fieldNum, 0, 0);
} }
return null; return null;
@@ -435,14 +445,50 @@ abstract class OperandFieldHelper extends FieldFactory {
characterOffset = 0; characterOffset = 0;
} }
// There may be operands with no representation objects, so we don't want to create a composite field element. // There may be operands with no representation objects, so we don't want to create a
// composite field element.
if (elements.isEmpty()) { if (elements.isEmpty()) {
return null; return null;
} }
if (wrapOnSemicolon) {
List<FieldElement> lines = breakIntoLines(elements);
if (lines.size() == 1) {
return ListingTextField.createSingleLineTextField(this, proxy,
lines.get(0), startX + varWidth, width, hlProvider);
}
return ListingTextField.createMultilineTextField(this, proxy, lines, startX, width,
hlProvider);
}
return ListingTextField.createSingleLineTextField(this, proxy, return ListingTextField.createSingleLineTextField(this, proxy,
new CompositeFieldElement(elements), startX + varWidth, width, hlProvider); new CompositeFieldElement(elements), startX + varWidth, width, hlProvider);
} }
private List<FieldElement> breakIntoLines(List<OperandFieldElement> elements) {
// This method groups all elements between LINE_BREAK elements into composite elements
// where each composite element will be display on its own line.
//
// It does this by collecting elements in the lineElements list until it find a LINE_BREAK
List<FieldElement> fieldElements = new ArrayList<>();
List<OperandFieldElement> lineElements = new ArrayList<>();
for (OperandFieldElement operandFieldElement : elements) {
if (operandFieldElement == LINE_BREAK) {
if (!lineElements.isEmpty()) {
fieldElements.add(new CompositeFieldElement(lineElements));
lineElements.clear();
}
}
else {
lineElements.add(operandFieldElement);
}
}
if (!lineElements.isEmpty()) {
fieldElements.add(new CompositeFieldElement(lineElements));
lineElements.clear();
}
return fieldElements;
}
private int addElementsForOperand(Instruction inst, List<OperandFieldElement> elements, private int addElementsForOperand(Instruction inst, List<OperandFieldElement> elements,
int opIndex, OperandRepresentationList opRepList, int characterOffset) { int opIndex, OperandRepresentationList opRepList, int characterOffset) {
int subOpIndex = 0; int subOpIndex = 0;
@@ -493,7 +539,11 @@ abstract class OperandFieldHelper extends FieldFactory {
AttributedString as = new AttributedString(opElem.toString(), attributes.colorAttribute, AttributedString as = new AttributedString(opElem.toString(), attributes.colorAttribute,
getMetrics(attributes.styleAttribute), underline, ListingColors.UNDERLINE); getMetrics(attributes.styleAttribute), underline, ListingColors.UNDERLINE);
elements.add(new OperandFieldElement(as, opIndex, subOpIndex, characterOffset)); elements.add(new OperandFieldElement(as, opIndex, subOpIndex, characterOffset));
if (wrapOnSemicolon && opElem instanceof Character c && c == ';') {
elements.add(LINE_BREAK);
}
return characterOffset + as.length(); return characterOffset + as.length();
} }
@@ -742,5 +792,4 @@ abstract class OperandFieldHelper extends FieldFactory {
operandSubIndex, column); operandSubIndex, column);
} }
} }
} }
@@ -449,26 +449,26 @@ public class PostCommentFieldFactory extends FieldFactory {
(comments.length == 0 || alwaysShowAutomatic) ? autoComment.length : 0; (comments.length == 0 || alwaysShowAutomatic) ? autoComment.length : 0;
AttributedString prototypeString = AttributedString prototypeString =
new AttributedString("prototype", color, getMetrics()); new AttributedString("prototype", color, getMetrics());
FieldElement[] fields = int commentLineCount = comments.length + nLinesAfterBlocks + nLinesAutoComment;
new FieldElement[comments.length + nLinesAfterBlocks + nLinesAutoComment]; List<FieldElement> elements = new ArrayList<>(commentLineCount);
if (fields.length > 0) { if (commentLineCount > 0) {
for (int i = 0; i < nLinesAutoComment; i++) { for (int i = 0; i < nLinesAutoComment; i++) {
AttributedString as = new AttributedString(autoComment[i], AttributedString as = new AttributedString(autoComment[i],
CommentColors.AUTO, getMetrics(automaticCommentStyle), false, null); CommentColors.AUTO, getMetrics(automaticCommentStyle), false, null);
fields[i] = new TextFieldElement(as, i, 0); elements.add(new TextFieldElement(as, i, 0));
} }
for (int i = 0; i < comments.length; i++) { for (int i = 0; i < comments.length; i++) {
int index = nLinesAutoComment + i; int index = nLinesAutoComment + i;
fields[index] = CommentUtils.parseTextForAnnotations(comments[i], elements.add(CommentUtils.parseTextForAnnotations(comments[i],
instr.getProgram(), prototypeString, index); instr.getProgram(), prototypeString, index));
} }
for (int i = fields.length - nLinesAfterBlocks; i < fields.length; i++) { for (int i = commentLineCount - nLinesAfterBlocks; i < commentLineCount; i++) {
// add blank lines for end-of-block // add blank lines for end-of-block
AttributedString as = new AttributedString("", color, getMetrics()); AttributedString as = new AttributedString("", color, getMetrics());
fields[i] = new TextFieldElement(as, i, 0); elements.add(new TextFieldElement(as, i, 0));
} }
return ListingTextField.createMultilineTextField(this, proxy, fields, xStart, return ListingTextField.createMultilineTextField(this, proxy, elements, xStart,
width, Integer.MAX_VALUE, hlProvider); width, hlProvider);
} }
} }
} }
@@ -516,10 +516,9 @@ public class PostCommentFieldFactory extends FieldFactory {
fields.add(new TextFieldElement(as, fields.size(), 0)); fields.add(new TextFieldElement(as, fields.size(), 0));
} }
} }
FieldElement[] elements = fields.toArray(new FieldElement[fields.size()]);
return ListingTextField.createMultilineTextField(this, proxy, elements, xStart, width, return ListingTextField.createMultilineTextField(this, proxy, fields, xStart, width,
Integer.MAX_VALUE, hlProvider); hlProvider);
} }
private void init(Options options) { private void init(Options options) {
@@ -386,10 +386,8 @@ public class PreCommentFieldFactory extends FieldFactory {
fields = FieldUtils.wrap(fields, width); fields = FieldUtils.wrap(fields, width);
} }
FieldElement[] elements = fields.toArray(new FieldElement[fields.size()]); return ListingTextField.createMultilineTextField(this, proxy, fields, xStart, width,
hlProvider);
return ListingTextField.createMultilineTextField(this, proxy, elements, xStart, width,
Integer.MAX_VALUE, hlProvider);
} }
private void init(Options options) { private void init(Options options) {
@@ -199,14 +199,14 @@ public class RegisterFieldFactory extends FieldFactory {
return setRegisters; return setRegisters;
} }
private FieldElement[] getFieldElements(String[] registerStrings) { private List<FieldElement> getFieldElements(String[] registerStrings) {
FieldElement[] fieldElements = new FieldElement[registerStrings.length]; List<FieldElement> elements = new ArrayList<>(registerStrings.length);
for (int i = 0; i < registerStrings.length; i++) { for (int i = 0; i < registerStrings.length; i++) {
AttributedString str = AttributedString str =
new AttributedString(registerStrings[i], ListingColors.REGISTER, getMetrics()); new AttributedString(registerStrings[i], ListingColors.REGISTER, getMetrics());
fieldElements[i] = new TextFieldElement(str, i, 0); elements.add(new TextFieldElement(str, i, 0));
} }
return fieldElements; return elements;
} }
private ListingTextField getTextField(String[] registerStrings, ProxyObj<?> proxy, int xStart) { private ListingTextField getTextField(String[] registerStrings, ProxyObj<?> proxy, int xStart) {
@@ -214,9 +214,9 @@ public class RegisterFieldFactory extends FieldFactory {
return null; return null;
} }
FieldElement[] fieldElements = getFieldElements(registerStrings); List<FieldElement> elements = getFieldElements(registerStrings);
return ListingTextField.createMultilineTextField(this, proxy, fieldElements, xStart, width, return ListingTextField.createMultilineTextField(this, proxy, elements, xStart, width,
Integer.MAX_VALUE, hlProvider); hlProvider);
} }
private class RegComparator implements Comparator<Register> { private class RegComparator implements Comparator<Register> {
@@ -55,7 +55,8 @@ public class RegisterTransitionFieldFactory extends FieldFactory {
* @param displayOptions the Options for display properties. * @param displayOptions the Options for display properties.
* @param fieldOptions the Options for field specific properties. * @param fieldOptions the Options for field specific properties.
*/ */
private RegisterTransitionFieldFactory(FieldFormatModel model, ListingHighlightProvider hsProvider, private RegisterTransitionFieldFactory(FieldFormatModel model,
ListingHighlightProvider hsProvider,
Options displayOptions, Options fieldOptions) { Options displayOptions, Options fieldOptions) {
super(FIELD_NAME, model, hsProvider, displayOptions, fieldOptions); super(FIELD_NAME, model, hsProvider, displayOptions, fieldOptions);
initOptions(displayOptions, fieldOptions); initOptions(displayOptions, fieldOptions);
@@ -118,22 +119,22 @@ public class RegisterTransitionFieldFactory extends FieldFactory {
if (stackDepthStr != null) { if (stackDepthStr != null) {
numElements++; numElements++;
} }
FieldElement[] fieldElements = new FieldElement[numElements]; List<FieldElement> elements = new ArrayList<>(numElements);
for (int i = 0; i < numRegisters; i++) { for (int i = 0; i < numRegisters; i++) {
Register register = transitionRegisters.get(i); Register register = transitionRegisters.get(i);
AttributedString str = new AttributedString( AttributedString str = new AttributedString(
"assume " + register.getName() + " = " + "assume " + register.getName() + " = " +
getValueString(register, context, curAddress), getValueString(register, context, curAddress),
ListingColors.REGISTER, getMetrics()); ListingColors.REGISTER, getMetrics());
fieldElements[i] = new TextFieldElement(str, i, 0); elements.add(new TextFieldElement(str, i, 0));
} }
if (stackDepthStr != null) { if (stackDepthStr != null) {
AttributedString str = AttributedString str =
new AttributedString(stackDepthStr, ListingColors.REGISTER, getMetrics()); new AttributedString(stackDepthStr, ListingColors.REGISTER, getMetrics());
fieldElements[numRegisters] = new TextFieldElement(str, numRegisters, 0); elements.add(new TextFieldElement(str, numRegisters, 0));
} }
return ListingTextField.createMultilineTextField(this, proxy, fieldElements, return ListingTextField.createMultilineTextField(this, proxy, elements,
startX + varWidth, width, Integer.MAX_VALUE, hlProvider); startX + varWidth, width, hlProvider);
} }
private String getValueString(Register register, ProgramContext context, Address curAddress) { private String getValueString(Register register, ProgramContext context, Address curAddress) {
@@ -242,7 +243,8 @@ public class RegisterTransitionFieldFactory extends FieldFactory {
} }
@Override @Override
public FieldFactory newInstance(FieldFormatModel fieldFormatModel, ListingHighlightProvider hsProvider, public FieldFactory newInstance(FieldFormatModel fieldFormatModel,
ListingHighlightProvider hsProvider,
ToolOptions displayOptions, ToolOptions fieldOptions) { ToolOptions displayOptions, ToolOptions fieldOptions) {
return new RegisterTransitionFieldFactory(fieldFormatModel, hsProvider, displayOptions, return new RegisterTransitionFieldFactory(fieldFormatModel, hsProvider, displayOptions,
fieldOptions); fieldOptions);
@@ -16,6 +16,8 @@
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation; import docking.widgets.fieldpanel.support.FieldLocation;
@@ -78,14 +80,15 @@ public class SpaceFieldFactory extends FieldFactory {
else if (n < 0) { else if (n < 0) {
n = -n; n = -n;
} }
FieldElement[] fes = new FieldElement[n];
List<FieldElement> elements = new ArrayList<>(n);
AttributedString as = new AttributedString("", Colors.FOREGROUND, getMetrics()); AttributedString as = new AttributedString("", Colors.FOREGROUND, getMetrics());
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
fes[i] = new TextFieldElement(as, 0, 0); elements.add(new TextFieldElement(as, 0, 0));
} }
return ListingTextField.createMultilineTextField(this, proxy, fes, startX + varWidth, return ListingTextField.createMultilineTextField(this, proxy, elements,
width, n + 1, hlProvider); startX + varWidth, width, hlProvider);
} }
return null; return null;
@@ -16,6 +16,8 @@
package ghidra.app.util.viewer.field; package ghidra.app.util.viewer.field;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.field.*;
import docking.widgets.fieldpanel.support.FieldLocation; import docking.widgets.fieldpanel.support.FieldLocation;
@@ -69,15 +71,15 @@ public class VariableCommentFieldFactory extends AbstractVariableFieldFactory {
String comment = sv.getComment(); String comment = sv.getComment();
String[] comments = StringUtilities.toLines(comment); String[] comments = StringUtilities.toLines(comment);
if ((comments != null) && (comments.length > 0)) { if ((comments != null) && (comments.length > 0)) {
FieldElement[] fields = new FieldElement[comments.length]; List<FieldElement> elements = new ArrayList<>(comments.length);
for (int i = 0; i < comments.length; i++) { for (int i = 0; i < comments.length; i++) {
AttributedString as = AttributedString as =
new AttributedString(comments[i], getColor(sv), getMetrics(sv)); new AttributedString(comments[i], getColor(sv), getMetrics(sv));
fields[i] = new TextFieldElement(as, i, 0); elements.add(new TextFieldElement(as, i, 0));
} }
return ListingTextField.createMultilineTextField(this, proxy, fields, startX + varWidth, return ListingTextField.createMultilineTextField(this, proxy, elements,
width, Integer.MAX_VALUE, hlProvider); startX + varWidth, width, hlProvider);
} }
return null; return null;
} }
@@ -745,7 +745,7 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest
loadProgram(); loadProgram();
Options options = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); Options options = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
List<String> names = getOptionNames(options, "Operands Field"); List<String> names = getOptionNames(options, "Operands Field");
assertEquals(15, names.size()); assertEquals(16, names.size());
assertEquals("Operands Field.Add Space After Separator", names.get(0)); assertEquals("Operands Field.Add Space After Separator", names.get(0));
assertEquals("Operands Field.Always Show Primary Reference", names.get(1)); assertEquals("Operands Field.Always Show Primary Reference", names.get(1));
assertEquals("Operands Field.Display Abbreviated Default Label Names", names.get(2)); assertEquals("Operands Field.Display Abbreviated Default Label Names", names.get(2));
@@ -761,6 +761,7 @@ public class CodeBrowserOptionsTest extends AbstractGhidraHeadedIntegrationTest
assertEquals("Operands Field.Show Block Names", names.get(12)); assertEquals("Operands Field.Show Block Names", names.get(12));
assertEquals("Operands Field.Show Offcut Information", names.get(13)); assertEquals("Operands Field.Show Offcut Information", names.get(13));
assertEquals("Operands Field.Underline References", names.get(14)); assertEquals("Operands Field.Underline References", names.get(14));
assertEquals("Operands Field.Wrap on Semicolons", names.get(15));
NamespaceWrappedOption namespaceOption = NamespaceWrappedOption namespaceOption =
(NamespaceWrappedOption) options.getCustomOption(names.get(3), (NamespaceWrappedOption) options.getCustomOption(names.get(3),
@@ -492,11 +492,19 @@ public class VerticalLayoutTextField implements TextField {
@Override @Override
public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) { public RowColLocation dataToScreenLocation(int dataRow, int dataColumn) {
FieldRow fieldRow = getFieldRowFromDataRow(dataRow); // search each line looking for a match for the given row and column
TextField field = fieldRow.field; for (int i = 0; i < subFields.size(); i++) {
RowColLocation location = field.dataToScreenLocation(dataRow, dataColumn); FieldRow row = subFields.get(i);
int screenRow = fieldRow.screenRow; RowColLocation loc = row.field.dataToScreenLocation(dataRow, dataColumn);
return location.withRow(screenRow);
// A DefaultRowColLocation means that the line did not have an exact match for
// the dataRow and dataColumn, so need to keep looking at each line.
if (!(loc instanceof DefaultRowColLocation)) {
return new RowColLocation(i, loc.col());
}
}
// We did not find a match for the given row and column, so return a default location of 0,0
return new DefaultRowColLocation();
} }
@Override @Override
@@ -92,7 +92,7 @@ public class VerticalLayoutTextFieldTest extends AbstractGenericTest {
assertEquals(new RowColLocation(2, 0), field.dataToScreenLocation(2, 0)); assertEquals(new RowColLocation(2, 0), field.dataToScreenLocation(2, 0));
assertEquals(new RowColLocation(2, 4), field.dataToScreenLocation(2, 4)); assertEquals(new RowColLocation(2, 4), field.dataToScreenLocation(2, 4));
assertEquals(new RowColLocation(2, 12), field.dataToScreenLocation(2, 12)); assertEquals(new RowColLocation(2, 12), field.dataToScreenLocation(2, 12));
assertEquals(new DefaultRowColLocation(2, 12), field.dataToScreenLocation(2, 15)); assertEquals(new DefaultRowColLocation(0, 0), field.dataToScreenLocation(2, 15));
assertEquals(new RowColLocation(3, 0), field.dataToScreenLocation(3, 0)); assertEquals(new RowColLocation(3, 0), field.dataToScreenLocation(3, 0));
assertEquals(new RowColLocation(3, 4), field.dataToScreenLocation(3, 4)); assertEquals(new RowColLocation(3, 4), field.dataToScreenLocation(3, 4));